software copyright

This commit is contained in:
jiahong
2026-03-22 15:24:40 +08:00
parent e303bb868a
commit 60f336e345
155 changed files with 127262 additions and 0 deletions
@@ -0,0 +1,375 @@
/*
* 自然写互动课堂应用开发SDK软件 V1.0
* WritechSDK - SDK初始化与鉴权入口
*
* 功能说明:
* 1. SDK全局初始化(配置加载、模块注册)
* 2. 应用鉴权(AppKey/AppSecret验证)
* 3. 各子模块生命周期管理
* 4. 全局配置管理(服务器地址、超时、日志级别)
* 5. SDK版本信息与功能授权查询
*/
package com.writech.sdk.android;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 自然写SDK主入口类
* 使用前必须先调用 init() 方法进行初始化和鉴权
*
* 典型使用流程:
* 1. WritechSDK.init(context, config)
* 2. WritechSDK.getInstance().getPenManager().startScan()
* 3. WritechSDK.getInstance().getOCREngine().recognizeHandwriting(...)
*/
public class WritechSDK {
private static final String TAG = "WritechSDK";
/* SDK版本号 */
public static final String SDK_VERSION = "1.0.0";
/* SDK构建号 */
public static final int SDK_BUILD = 100;
/* 单例实例 */
private static volatile WritechSDK sInstance;
/* 是否已初始化 */
private static final AtomicBoolean sInitialized = new AtomicBoolean(false);
/* ========== 配置类 ========== */
/** SDK初始化配置 */
public static class Config {
/** 云平台API地址 */
public String cloudBaseUrl = "https://api.writech.com";
/** SDK应用标识(从自然写开放平台获取) */
public String appKey;
/** SDK应用密钥 */
public String appSecret;
/** 离线OCR模型文件路径(可选) */
public String offlineModelPath;
/** 是否启用调试日志 */
public boolean debugMode = false;
/** 笔迹数据本地缓存目录 */
public String cacheDir;
/** BLE扫描超时时间(毫秒) */
public int bleScanTimeout = 30000;
/** 网关自动发现 */
public boolean autoDiscoverGateway = true;
/** 最大同时连接笔数 */
public int maxPenConnections = 60;
}
/* ========== 成员变量 ========== */
private Context mContext;
private Config mConfig;
/* 各子模块实例 */
private PenManager mPenManager;
private StrokeCanvas mDefaultCanvas;
private OCREngine mOCREngine;
private GatewaySDK mGatewaySDK;
private CloudClient mCloudClient;
/* 鉴权状态 */
private boolean mIsAuthenticated = false;
private String mLicenseType; /* 授权类型: trial/standard/enterprise */
private long mLicenseExpireTime; /* 授权到期时间 */
/* 本地存储 */
private SharedPreferences mPrefs;
/* ========== 初始化入口 ========== */
/**
* 初始化SDK(必须在使用任何功能前调用)
*
* @param context Android上下文(Application级别)
* @param config SDK配置
* @return 初始化结果:true成功,false失败
*/
public static boolean init(Context context, Config config) {
if (sInitialized.getAndSet(true)) {
Log.w(TAG, "SDK已初始化,忽略重复调用");
return true;
}
if (context == null || config == null) {
Log.e(TAG, "初始化失败:context或config为null");
sInitialized.set(false);
return false;
}
if (config.appKey == null || config.appSecret == null) {
Log.e(TAG, "初始化失败:appKey或appSecret未配置");
sInitialized.set(false);
return false;
}
sInstance = new WritechSDK();
boolean success = sInstance.doInit(context, config);
if (!success) {
sInstance = null;
sInitialized.set(false);
}
return success;
}
/** 获取SDK单例 */
public static WritechSDK getInstance() {
if (sInstance == null) {
throw new IllegalStateException("WritechSDK未初始化,请先调用 WritechSDK.init()");
}
return sInstance;
}
/** 检查SDK是否已初始化 */
public static boolean isInitialized() {
return sInitialized.get();
}
/* ========== 内部初始化流程 ========== */
/** 执行具体的初始化逻辑 */
private boolean doInit(Context context, Config config) {
mContext = context.getApplicationContext();
mConfig = config;
mPrefs = mContext.getSharedPreferences("writech_sdk", Context.MODE_PRIVATE);
Log.i(TAG, "=== 自然写SDK V" + SDK_VERSION + " 初始化开始 ===");
Log.i(TAG, "云平台地址: " + config.cloudBaseUrl);
Log.i(TAG, "AppKey: " + config.appKey.substring(0, 8) + "****");
Log.i(TAG, "调试模式: " + config.debugMode);
/* 步骤1:应用鉴权(验证AppKey和AppSecret */
if (!authenticate(config.appKey, config.appSecret)) {
Log.e(TAG, "SDK鉴权失败,请检查AppKey和AppSecret");
return false;
}
/* 步骤2:初始化云平台客户端 */
mCloudClient = new CloudClient(config.cloudBaseUrl, config.appKey, config.appSecret);
/* 恢复本地缓存的令牌 */
restoreTokens();
/* 步骤3:初始化蓝牙笔管理器 */
mPenManager = new PenManager(mContext);
/* 步骤4:初始化OCR引擎 */
mOCREngine = new OCREngine(mContext, config.cloudBaseUrl, null);
if (config.offlineModelPath != null) {
mOCREngine.loadOfflineModel(config.offlineModelPath);
}
/* 步骤5:初始化网关SDK */
mGatewaySDK = new GatewaySDK(mContext);
if (config.autoDiscoverGateway) {
mGatewaySDK.startDiscovery();
}
Log.i(TAG, "=== 自然写SDK初始化完成 ===");
return true;
}
/* ========== 应用鉴权 ========== */
/**
* 验证AppKey和AppSecret的有效性
* 首次验证需要联网,之后缓存鉴权结果
*/
private boolean authenticate(String appKey, String appSecret) {
/* 检查本地缓存的鉴权结果 */
String cachedLicense = mPrefs.getString("license_type", null);
long cachedExpire = mPrefs.getLong("license_expire", 0);
if (cachedLicense != null && cachedExpire > System.currentTimeMillis()) {
mIsAuthenticated = true;
mLicenseType = cachedLicense;
mLicenseExpireTime = cachedExpire;
Log.i(TAG, "使用缓存鉴权结果: " + mLicenseType
+ ",到期: " + new java.util.Date(mLicenseExpireTime));
return true;
}
/* 在线鉴权 */
try {
String authUrl = mConfig.cloudBaseUrl + "/api/v1/sdk/authenticate";
String body = "{\"appKey\":\"" + appKey
+ "\",\"appSecret\":\"" + appSecret
+ "\",\"sdkVersion\":\"" + SDK_VERSION + "\"}";
/* 使用CloudClient的静态方法发送无认证请求 */
java.net.HttpURLConnection conn =
(java.net.HttpURLConnection) new java.net.URL(authUrl).openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setConnectTimeout(10000);
conn.getOutputStream().write(body.getBytes(java.nio.charset.StandardCharsets.UTF_8));
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
java.io.InputStream is = conn.getInputStream();
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
}
String response = baos.toString("UTF-8");
is.close();
conn.disconnect();
/* 解析鉴权结果 */
mLicenseType = extractJsonField(response, "licenseType");
String expireStr = extractJsonField(response, "expireTime");
if (mLicenseType != null) {
mLicenseExpireTime = expireStr != null ? Long.parseLong(expireStr)
: System.currentTimeMillis() + 365L * 24 * 3600 * 1000;
mIsAuthenticated = true;
/* 缓存鉴权结果 */
mPrefs.edit()
.putString("license_type", mLicenseType)
.putLong("license_expire", mLicenseExpireTime)
.apply();
Log.i(TAG, "在线鉴权成功: " + mLicenseType);
return true;
}
}
conn.disconnect();
} catch (Exception e) {
Log.w(TAG, "在线鉴权异常: " + e.getMessage());
/* 联网失败时允许离线试用(7天) */
mLicenseType = "trial";
mLicenseExpireTime = System.currentTimeMillis() + 7L * 24 * 3600 * 1000;
mIsAuthenticated = true;
Log.i(TAG, "离线模式,试用授权7天");
return true;
}
return false;
}
/** 恢复本地缓存的认证令牌 */
private void restoreTokens() {
String accessToken = mPrefs.getString("access_token", null);
String refreshToken = mPrefs.getString("refresh_token", null);
long expireTime = mPrefs.getLong("token_expire", 0);
if (accessToken != null && refreshToken != null) {
mCloudClient.setTokens(accessToken, refreshToken, expireTime);
Log.d(TAG, "已恢复缓存的认证令牌");
}
}
/* ========== 对外接口 ========== */
/** 获取笔管理器 */
public PenManager getPenManager() {
return mPenManager;
}
/** 获取OCR引擎 */
public OCREngine getOCREngine() {
return mOCREngine;
}
/** 获取网关SDK */
public GatewaySDK getGatewaySDK() {
return mGatewaySDK;
}
/** 获取云平台客户端 */
public CloudClient getCloudClient() {
return mCloudClient;
}
/** 获取SDK版本 */
public String getVersion() {
return SDK_VERSION;
}
/** 获取授权类型 */
public String getLicenseType() {
return mLicenseType;
}
/** 检查是否已鉴权 */
public boolean isAuthenticated() {
return mIsAuthenticated;
}
/** 用户登录(通过云平台认证) */
public boolean loginUser(String username, String password) {
try {
String response = mCloudClient.login(username, password);
String accessToken = extractJsonField(response, "accessToken");
String refreshToken = extractJsonField(response, "refreshToken");
if (accessToken != null) {
long expireTime = System.currentTimeMillis() + 30 * 60 * 1000;
mCloudClient.setTokens(accessToken, refreshToken, expireTime);
/* 缓存令牌 */
mPrefs.edit()
.putString("access_token", accessToken)
.putString("refresh_token", refreshToken)
.putLong("token_expire", expireTime)
.apply();
return true;
}
} catch (IOException e) {
Log.e(TAG, "登录失败: " + e.getMessage());
}
return false;
}
/* ========== 资源释放 ========== */
/** 释放SDK所有资源 */
public static void destroy() {
if (sInstance != null) {
if (sInstance.mGatewaySDK != null) sInstance.mGatewaySDK.destroy();
if (sInstance.mOCREngine != null) sInstance.mOCREngine.destroy();
if (sInstance.mPenManager != null) sInstance.mPenManager.destroy();
sInstance = null;
}
sInitialized.set(false);
Log.i(TAG, "WritechSDK已释放所有资源");
}
/** 从JSON提取字段值 */
private String extractJsonField(String json, String key) {
if (json == null) return null;
String search = "\"" + key + "\"";
int idx = json.indexOf(search);
if (idx < 0) return null;
int start = json.indexOf("\"", idx + search.length() + 1) + 1;
int end = json.indexOf("\"", start);
return (start > 0 && end > start) ? json.substring(start, end) : null;
}
}