Compare commits
	
		
			7 Commits
		
	
	
		
			apputils-v
			...
			apputils
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3eeb808e07 | |||
| 56a3e12521 | |||
|   | 6ce48c8881 | ||
| eb0881ae6b | |||
| c88dcd7316 | |||
|   | 0ab2fdea66 | ||
| ce83a08eb8 | 
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Fri Sep 26 21:10:11 HKT 2025 | ||||
| stageCount=1 | ||||
| #Mon Sep 29 01:16:05 HKT 2025 | ||||
| stageCount=3 | ||||
| libraryProject=libapputils | ||||
| baseVersion=15.10 | ||||
| publishVersion=15.10.0 | ||||
| publishVersion=15.10.2 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.10.1 | ||||
| baseBetaVersion=15.10.3 | ||||
|   | ||||
| @@ -21,7 +21,7 @@ android { | ||||
|  | ||||
| dependencies { | ||||
|     api fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     api 'cc.winboll.studio:libappbase:15.10.7' | ||||
|     api 'cc.winboll.studio:libappbase:15.10.9' | ||||
|      | ||||
|     // 二维码类库 | ||||
|     api 'com.google.zxing:core:3.4.1' | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Fri Sep 26 21:10:11 HKT 2025 | ||||
| stageCount=1 | ||||
| #Mon Sep 29 01:15:55 HKT 2025 | ||||
| stageCount=3 | ||||
| libraryProject=libapputils | ||||
| baseVersion=15.10 | ||||
| publishVersion=15.10.0 | ||||
| publishVersion=15.10.2 | ||||
| buildCount=0 | ||||
| baseBetaVersion=15.10.1 | ||||
| baseBetaVersion=15.10.3 | ||||
|   | ||||
| @@ -1,53 +0,0 @@ | ||||
| package cc.winboll.studio.libapputils.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/06/05 11:26 | ||||
|  */ | ||||
|  | ||||
| public class ResponseData { | ||||
|      | ||||
|     public static final String STATUS_SUCCESS = "success"; | ||||
|     public static final String STATUS_ERROR = "error"; | ||||
|      | ||||
|     private String status; | ||||
|     private String message; | ||||
|     private UserInfoModel data; | ||||
|      | ||||
|     public ResponseData() { | ||||
|         this.status = ""; | ||||
|         this.message = ""; | ||||
|         this.data = new UserInfoModel(); | ||||
|     } | ||||
|      | ||||
|     public ResponseData(String status, String message, UserInfoModel data) { | ||||
|         this.status = status; | ||||
|         this.message = message; | ||||
|         this.data = data; | ||||
|     } | ||||
|  | ||||
|     public void setStatus(String status) { | ||||
|         this.status = status; | ||||
|     } | ||||
|  | ||||
|     public String getStatus() { | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     public void setMessage(String message) { | ||||
|         this.message = message; | ||||
|     } | ||||
|  | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     public void setData(UserInfoModel data) { | ||||
|         this.data = data; | ||||
|     } | ||||
|  | ||||
|     public UserInfoModel getData() { | ||||
|         return data; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,92 +0,0 @@ | ||||
| package cc.winboll.studio.libapputils.models; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/06/04 19:14 | ||||
|  */ | ||||
| import android.util.JsonReader; | ||||
| import android.util.JsonWriter; | ||||
| import cc.winboll.studio.libappbase.BaseBean; | ||||
| import java.io.IOException; | ||||
|  | ||||
| public class UserInfoModel extends BaseBean { | ||||
|  | ||||
|     public static final String TAG = "UserInfoModel"; | ||||
|  | ||||
|     String username; | ||||
|     String password; | ||||
|     String token; | ||||
|  | ||||
|     public UserInfoModel() { | ||||
|         this.username = ""; | ||||
|         this.password = ""; | ||||
|         this.token = ""; | ||||
|     } | ||||
|  | ||||
|     public void setUsername(String username) { | ||||
|         this.username = username; | ||||
|     } | ||||
|  | ||||
|     public String getUsername() { | ||||
|         return username; | ||||
|     } | ||||
|  | ||||
|     public void setPassword(String password) { | ||||
|         this.password = password; | ||||
|     } | ||||
|  | ||||
|     public String getPassword() { | ||||
|         return password; | ||||
|     } | ||||
|  | ||||
|     public void setToken(String token) { | ||||
|         this.token = token; | ||||
|     } | ||||
|  | ||||
|     public String getToken() { | ||||
|         return token; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return UserInfoModel.class.getName(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { | ||||
|         super.writeThisToJsonWriter(jsonWriter); | ||||
|         jsonWriter.name("username").value(getUsername()); | ||||
|         jsonWriter.name("password").value(getPassword()); | ||||
|         jsonWriter.name("token").value(getToken()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { | ||||
|         if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { | ||||
|             if (name.equals("username")) { | ||||
|                 setUsername(jsonReader.nextString()); | ||||
|             } else if (name.equals("password")) { | ||||
|                 setPassword(jsonReader.nextString()); | ||||
|             } else if (name.equals("token")) { | ||||
|                 setToken(jsonReader.nextString()); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { | ||||
|         jsonReader.beginObject(); | ||||
|         while (jsonReader.hasNext()) { | ||||
|             String name = jsonReader.nextName(); | ||||
|             if (!initObjectsFromJsonReader(jsonReader, name)) { | ||||
|                 jsonReader.skipValue(); | ||||
|             } | ||||
|         } | ||||
|         // 结束 JSON 对象 | ||||
|         jsonReader.endObject(); | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @@ -1,222 +0,0 @@ | ||||
| package cc.winboll.studio.libapputils.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/06/04 13:36 | ||||
|  * @Describe RSA加密工具 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.util.Base64; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.security.KeyFactory; | ||||
| import java.security.KeyPair; | ||||
| import java.security.KeyPairGenerator; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.PublicKey; | ||||
| import java.security.spec.InvalidKeySpecException; | ||||
| import java.security.spec.PKCS8EncodedKeySpec; | ||||
| import java.security.spec.X509EncodedKeySpec; | ||||
| import java.util.Objects; | ||||
| import javax.crypto.Cipher; | ||||
|  | ||||
| public class RSAUtils { | ||||
|     private static final String TAG = "RSAUtils"; | ||||
|     private static final int KEY_SIZE = 2048; | ||||
|     private static final String KEY_ALGORITHM = "RSA"; | ||||
|     private static final String PUBLIC_KEY_FILE = "public.key"; | ||||
|     private static final String PRIVATE_KEY_FILE = "private.key"; | ||||
|     private static final String CIPHER_ALGORITHM = KEY_ALGORITHM + "/ECB/PKCS1Padding"; // 保留原加密方式 | ||||
|  | ||||
|     private final String keyPath; | ||||
|     private static volatile RSAUtils INSTANCE; | ||||
|  | ||||
|     /** | ||||
|      * 构造方法:初始化密钥存储路径(内部存储) | ||||
|      */ | ||||
|     private RSAUtils(Context context) { | ||||
|         keyPath = context.getFilesDir() + File.separator + "keys" + File.separator; // 修正路径格式 | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取单例实例 | ||||
|      */ | ||||
|     public static synchronized RSAUtils getInstance(Context context) { | ||||
|         if (INSTANCE == null) { | ||||
|             INSTANCE = new RSAUtils(context); | ||||
|         } | ||||
|         return INSTANCE; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查密钥文件是否存在 | ||||
|      */ | ||||
|     public boolean keysExist() { | ||||
|         File publicKeyFile = new File(keyPath + PUBLIC_KEY_FILE); | ||||
|         File privateKeyFile = new File(keyPath + PRIVATE_KEY_FILE); | ||||
|         return publicKeyFile.exists() && privateKeyFile.exists(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 生成密钥对并保存到文件 | ||||
|      */ | ||||
|     public void generateAndSaveKeys() throws Exception { | ||||
|         LogUtils.d(TAG, "开始生成 RSA 密钥对(2048位)"); | ||||
|         KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM); | ||||
|         generator.initialize(KEY_SIZE); | ||||
|         KeyPair keyPair = generator.generateKeyPair(); | ||||
|  | ||||
|         saveKey(PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded()); | ||||
|         saveKey(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded()); | ||||
|         LogUtils.d(TAG, "密钥对生成并保存成功"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取或生成密钥对(线程安全) | ||||
|      */ | ||||
|     public KeyPair getOrGenerateKeys() throws Exception { | ||||
|         if (!keysExist()) { | ||||
|             synchronized (RSAUtils.class) { // 双重检查锁,避免多线程重复生成 | ||||
|                 if (!keysExist()) { | ||||
|                     generateAndSaveKeys(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return readKeysFromFile(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从文件读取密钥对 | ||||
|      */ | ||||
|     private KeyPair readKeysFromFile() throws Exception { | ||||
|         LogUtils.d(TAG, "读取密钥对文件"); | ||||
|         try { | ||||
|             byte[] publicKeyBytes = readFileToBytes(keyPath + PUBLIC_KEY_FILE); | ||||
|             byte[] privateKeyBytes = readFileToBytes(keyPath + PRIVATE_KEY_FILE); | ||||
|  | ||||
|             X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes); | ||||
|             PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes); | ||||
|  | ||||
|             KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); | ||||
|             PublicKey publicKey = factory.generatePublic(publicSpec); | ||||
|             PrivateKey privateKey = factory.generatePrivate(privateSpec); | ||||
|  | ||||
|             return new KeyPair(publicKey, privateKey); | ||||
|         } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { | ||||
|             LogUtils.e(TAG, "密钥文件读取失败:" + e.getMessage()); | ||||
|             throw new Exception("密钥文件损坏或格式错误", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 保存密钥到文件(通用方法) | ||||
|      */ | ||||
|     private void saveKey(String fileName, byte[] keyBytes) throws IOException { | ||||
|         Objects.requireNonNull(keyBytes, "密钥字节数据不可为空"); | ||||
|         File dir = new File(keyPath); | ||||
|         if (!dir.exists() && !dir.mkdirs()) { | ||||
|             throw new IOException("创建密钥目录失败:" + keyPath); | ||||
|         } | ||||
|  | ||||
|         FileOutputStream fos = null; | ||||
|         try { | ||||
|             fos = new FileOutputStream(keyPath + fileName); | ||||
|             fos.write(keyBytes); | ||||
|         } finally { | ||||
|             if (fos != null) { | ||||
|                 try { | ||||
|                     fos.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 读取文件为字节数组(Java 7 兼容) | ||||
|      */ | ||||
|     private byte[] readFileToBytes(String filePath) throws IOException { | ||||
|         File file = new File(filePath); | ||||
|         if (!file.exists() || file.isDirectory()) { | ||||
|             throw new IOException("文件不存在或为目录:" + filePath); | ||||
|         } | ||||
|  | ||||
|         FileInputStream fis = null; | ||||
|         try { | ||||
|             fis = new FileInputStream(file); | ||||
|             byte[] data = new byte[(int) file.length()]; | ||||
|             int bytesRead = fis.read(data); | ||||
|             if (bytesRead != data.length) { | ||||
|                 throw new IOException("文件读取不完整"); | ||||
|             } | ||||
|             return data; | ||||
|         } finally { | ||||
|             if (fis != null) { | ||||
|                 try { | ||||
|                     fis.close(); | ||||
|                 } catch (IOException e) { | ||||
|                     LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 公钥加密(带参数校验) | ||||
|      */ | ||||
|     public byte[] encryptWithPublicKey(String plainText, PublicKey publicKey) throws Exception { | ||||
|         Objects.requireNonNull(plainText, "明文不可为空"); | ||||
|         Objects.requireNonNull(publicKey, "公钥不可为空"); | ||||
|  | ||||
|         Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | ||||
|         cipher.init(Cipher.ENCRYPT_MODE, publicKey); | ||||
|  | ||||
|         // 检查数据长度是否超过 RSA 限制(2048位密钥最大明文为 214字节,PKCS1Padding) | ||||
|         int maxPlainTextSize = cipher.getBlockSize() - 11; // PKCS1Padding 固定填充长度 | ||||
|         if (plainText.getBytes("UTF-8").length > maxPlainTextSize) { | ||||
|             throw new IllegalArgumentException("明文过长,最大支持 " + maxPlainTextSize + " 字节"); | ||||
|         } | ||||
|  | ||||
|         return cipher.doFinal(plainText.getBytes("UTF-8")); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 私钥解密(带参数校验) | ||||
|      */ | ||||
|     public String decryptWithPrivateKey(byte[] encryptedData, PrivateKey privateKey) throws Exception { | ||||
|         Objects.requireNonNull(encryptedData, "密文不可为空"); | ||||
|         Objects.requireNonNull(privateKey, "私钥不可为空"); | ||||
|  | ||||
|         Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | ||||
|         cipher.init(Cipher.DECRYPT_MODE, privateKey); | ||||
|         byte[] decryptedBytes = cipher.doFinal(encryptedData); | ||||
|         return new String(decryptedBytes, "UTF-8"); | ||||
|     } | ||||
|     /** | ||||
|      * 将 HTTP 传输的 Base64 字符串还原为加密字节数组(Java 7 兼容) | ||||
|      * @param httpString Base64 字符串(非 null) | ||||
|      * @return 加密字节数组 | ||||
|      * @throws IllegalArgumentException 解码失败时抛出 | ||||
|      */ | ||||
|     public byte[] httpStringToEncryptBytes(String httpString) { | ||||
|         Objects.requireNonNull(httpString, "HTTP 字符串不可为空"); | ||||
|  | ||||
|         // 计算缺失的填充符数量(Java 7 不支持 repeat(),手动拼接) | ||||
|         int pad = httpString.length() % 4; | ||||
|         StringBuilder paddedString = new StringBuilder(httpString); | ||||
|         if (pad != 0) { | ||||
|             for (int i = 0; i < pad; i++) { | ||||
|                 paddedString.append('='); // 补全 '=' | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 使用 Base64 解码(Android 原生 Base64 类兼容 Java 7) | ||||
|         return Base64.decode(paddedString.toString(), Base64.URL_SAFE); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,281 +0,0 @@ | ||||
| package cc.winboll.studio.libapputils.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @Date 2025/06/04 17:21 | ||||
|  * @Describe 应用登录与接口工具 | ||||
|  */ | ||||
| import android.content.Context; | ||||
| import android.os.Handler; | ||||
| import android.os.Looper; | ||||
| import cc.winboll.studio.libappbase.LogUtils; | ||||
| import cc.winboll.studio.libapputils.models.ResponseData; | ||||
| import cc.winboll.studio.libapputils.models.UserInfoModel; | ||||
| import com.google.gson.Gson; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.UnsupportedEncodingException; | ||||
| import java.net.URLDecoder; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.security.KeyPair; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.PublicKey; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import okhttp3.Call; | ||||
| import okhttp3.Callback; | ||||
| import okhttp3.MediaType; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Request; | ||||
| import okhttp3.RequestBody; | ||||
| import okhttp3.Response; | ||||
|  | ||||
| public class YunUtils { | ||||
|     public static final String TAG = "YunUtils"; | ||||
|     // 私有静态实例,类加载时创建 | ||||
|     private static volatile YunUtils INSTANCE; | ||||
|     Context mContext; | ||||
|     UserInfoModel mUserInfoModel; | ||||
|     String token = ""; | ||||
|     String mDataFolderPath = ""; | ||||
|     String mUserInfoModelPath = ""; | ||||
|  | ||||
|     private static final int CONNECT_TIMEOUT = 15; // 连接超时时间(秒) | ||||
|     private static final int READ_TIMEOUT = 20;    // 读取超时时间(秒) | ||||
|     private static volatile YunUtils instance; | ||||
|     private OkHttpClient okHttpClient; | ||||
|     private Handler mainHandler; // 主线程 Handler | ||||
|  | ||||
|     // 私有构造方法,防止外部实例化 | ||||
|     private YunUtils(Context context) { | ||||
|         LogUtils.d(TAG, "YunUtils"); | ||||
|         mContext = context; | ||||
|         mDataFolderPath = mContext.getExternalFilesDir(TAG).toString(); | ||||
|         File fTest = new File(mDataFolderPath); | ||||
|         if (!fTest.exists()) { | ||||
|             fTest.mkdirs(); | ||||
|         } | ||||
|         mUserInfoModelPath = mDataFolderPath + File.separator + "UserInfoModel.rsajson"; | ||||
|  | ||||
|         okHttpClient = new OkHttpClient.Builder() | ||||
|             .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) | ||||
|             .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) | ||||
|             .build(); | ||||
|         mainHandler = new Handler(Looper.getMainLooper()); // 获取主线程 Looper | ||||
|     } | ||||
|  | ||||
|     // 公共静态方法,返回唯一实例 | ||||
|     public static synchronized YunUtils getInstance(Context context) { | ||||
|         LogUtils.d(TAG, "getInstance"); | ||||
|         if (INSTANCE == null) { | ||||
|             INSTANCE = new YunUtils(context); | ||||
|         } | ||||
|         return INSTANCE; | ||||
|     } | ||||
|  | ||||
|     public void checkLoginStatus() { | ||||
|         String token = getLocalToken(); | ||||
|         LogUtils.d(TAG, String.format("checkLoginStatus token is %s", token)); | ||||
|     } | ||||
|  | ||||
|     String getLocalToken() { | ||||
|         UserInfoModel userInfoModel = loadUserInfoModel(); | ||||
|         return (userInfoModel == null) ?"": userInfoModel.getToken(); | ||||
|     } | ||||
|  | ||||
|     public void login(String host, UserInfoModel userInfoModel) { | ||||
|         LogUtils.d(TAG, "login"); | ||||
|  | ||||
|         // 发送 POST 请求 | ||||
|         String apiUrl = host + "/login/index.php"; | ||||
|         // 序列化对象为JSON | ||||
|         Gson gson = new Gson(); | ||||
|         String jsonData = gson.toJson(userInfoModel); // 自动生成标准JSON | ||||
|         //String jsonData = userInfoModel.toString(); | ||||
|         LogUtils.d(TAG, "要发送的数据 : " + jsonData); | ||||
|  | ||||
|         sendPostRequest(apiUrl, jsonData, new OnResponseListener() { | ||||
|                 // 成功回调(主线程) | ||||
|                 @Override | ||||
|                 public void onSuccess(String responseBody) { | ||||
|                     LogUtils.d(TAG, "onSuccess"); | ||||
|                     LogUtils.d(TAG, String.format("responseBody %s", responseBody)); | ||||
|                     Gson gson = new Gson(); | ||||
|                     ResponseData result = gson.fromJson(responseBody, ResponseData.class); // 转为 Result 实例 | ||||
|                     if(result.getStatus().equals(ResponseData.STATUS_SUCCESS)) { | ||||
|                          | ||||
|                             UserInfoModel userInfoModel = result.getData(); | ||||
|                             if (userInfoModel != null) { | ||||
|                                 LogUtils.d(TAG, "收到网站 UserInfoModel"); | ||||
|                                 String token = userInfoModel.getToken(); | ||||
|                                 saveLocalToken(token); | ||||
|                                 checkLoginStatus(); | ||||
|                             } | ||||
|                         | ||||
|                     } else if(result.getStatus().equals(ResponseData.STATUS_ERROR)) { | ||||
|                         try { | ||||
|                             String decodedMessage = URLDecoder.decode(result.getMessage(), "UTF-8"); | ||||
|                             LogUtils.d(TAG, "服务器返回信息: " + decodedMessage); | ||||
|                         } catch (UnsupportedEncodingException e) { | ||||
|                             LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // 失败回调(主线程) | ||||
|                 @Override | ||||
|                 public void onFailure(String errorMsg) { | ||||
|                     LogUtils.d(TAG, errorMsg); | ||||
|                     // 处理错误 | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     public void saveLocalToken(String token) { | ||||
|         UserInfoModel userInfoModel = new UserInfoModel(); | ||||
|         userInfoModel.setToken(token); | ||||
|         saveUserInfoModel(userInfoModel); | ||||
|     } | ||||
|  | ||||
|     UserInfoModel loadUserInfoModel() { | ||||
|         LogUtils.d(TAG, "loadUserInfoModel"); | ||||
|         if (new File(mUserInfoModelPath).exists()) { | ||||
|             try { | ||||
|                 // 加载加密后的模型数据 | ||||
|                 byte[] encryptedData = FileUtils.readByteArrayFromFile(mUserInfoModelPath); | ||||
|                 // 加载 RSA 工具 | ||||
|                 RSAUtils utils = RSAUtils.getInstance(mContext); | ||||
|                 KeyPair keyPair = utils.getOrGenerateKeys(); | ||||
|                 //PublicKey publicKey = keyPair.getPublic(); | ||||
|                 PrivateKey privateKey = keyPair.getPrivate(); | ||||
|                 // 私钥解密模型数据 | ||||
|                 String szInfo = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate()); | ||||
|                 LogUtils.d(TAG, String.format("szInfo %s", szInfo)); | ||||
|                 mUserInfoModel = UserInfoModel.parseStringToBean(szInfo, UserInfoModel.class); | ||||
|                 if (mUserInfoModel == null) { | ||||
|                     LogUtils.d(TAG, "模型数据解析为空数据。"); | ||||
|                 } | ||||
|                 LogUtils.d(TAG, "UserInfoModel 解密加载结束。"); | ||||
|             } catch (Exception e) { | ||||
|                 LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); | ||||
|             } | ||||
|         } else { | ||||
|             LogUtils.d(TAG, "云服务登录信息不存在。"); | ||||
|             mUserInfoModel = null; | ||||
|         } | ||||
|         return mUserInfoModel; | ||||
|     } | ||||
|  | ||||
|     void saveUserInfoModel(UserInfoModel userInfoModel) { | ||||
|         LogUtils.d(TAG, "saveUserInfoModel"); | ||||
|         try { | ||||
|             String szInfo = userInfoModel.toString(); | ||||
|             LogUtils.d(TAG, "原始数据: " + szInfo); | ||||
|  | ||||
|             RSAUtils utils = RSAUtils.getInstance(mContext); | ||||
|             KeyPair keyPair = utils.getOrGenerateKeys(); | ||||
|             PublicKey publicKey = keyPair.getPublic(); | ||||
|  | ||||
|             // 公钥加密(传入字节数组,避免中间字符串转换) | ||||
|             byte[] encryptedData = utils.encryptWithPublicKey(szInfo, publicKey); | ||||
|  | ||||
|             // 保存加密字节数组到文件(直接操作字节,无需转字符串) | ||||
|             FileUtils.writeByteArrayToFile(encryptedData, mUserInfoModelPath); | ||||
|             LogUtils.d(TAG, "加密数据已保存"); | ||||
|  | ||||
|             // 测试解密(仅调试用) | ||||
|             String szInfo2 = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate()); | ||||
|             LogUtils.d(TAG, "解密结果: " + szInfo2); | ||||
|  | ||||
|             mUserInfoModel = UserInfoModel.parseStringToBean(szInfo2, UserInfoModel.class); | ||||
|             if (mUserInfoModel == null) { | ||||
|                 LogUtils.d(TAG, "模型解析失败"); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             LogUtils.d(TAG, "加密/解密失败: " + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 发送 POST 请求(JSON 数据) | ||||
|     public void sendPostRequest(String url, String data, OnResponseListener listener) { | ||||
|         RequestBody requestBody = RequestBody.create( | ||||
|             MediaType.parse("application/json; charset=utf-8"), // 关键头信息 | ||||
|             data.getBytes(StandardCharsets.UTF_8) | ||||
|         ); | ||||
|  | ||||
|         Request request = new Request.Builder() | ||||
|             .url(url) | ||||
|             .post(requestBody) | ||||
|             .addHeader("Content-Type", "application/json") // 显式添加头 | ||||
|             .build(); | ||||
|  | ||||
|         executeRequest(request, listener); | ||||
|     } | ||||
|  | ||||
|     // 发送 GET 请求 | ||||
|     public void sendGetRequest(String url, OnResponseListener listener) { | ||||
|         Request request = new Request.Builder() | ||||
|             .url(url) | ||||
|             .get() | ||||
|             .build(); | ||||
|         executeRequest(request, listener); | ||||
|     } | ||||
|  | ||||
|     // 执行请求(子线程处理) | ||||
|     private void executeRequest(final Request request, final OnResponseListener listener) { | ||||
|         okHttpClient.newCall(request).enqueue(new Callback() { | ||||
|                 // 响应成功(子线程) | ||||
|                 @Override | ||||
|                 public void onResponse(Call call, Response response) throws IOException { | ||||
|                     try { | ||||
|                         if (!response.isSuccessful()) { | ||||
|                             postFailure(listener, "响应码错误:" + response.code()); | ||||
|                             return; | ||||
|                         } | ||||
|                         String responseBody = response.body().string(); | ||||
|                         postSuccess(listener, responseBody); | ||||
|                     } catch (Exception e) { | ||||
|                         postFailure(listener, "解析失败:" + e.getMessage()); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // 响应失败(子线程) | ||||
|                 @Override | ||||
|                 public void onFailure(Call call, IOException e) { | ||||
|                     postFailure(listener, "网络失败:" + e.getMessage()); | ||||
|                 } | ||||
|  | ||||
|                 // 主线程回调(使用 Handler) | ||||
|                 private void postSuccess(final OnResponseListener listener, final String msg) { | ||||
|                     mainHandler.post(new Runnable() { | ||||
|                             @Override | ||||
|                             public void run() { | ||||
|                                 listener.onSuccess(msg); | ||||
|                             } | ||||
|                         }); | ||||
|                 } | ||||
|  | ||||
|                 private void postFailure(final OnResponseListener listener, final String msg) { | ||||
|                     mainHandler.post(new Runnable() { | ||||
|                             @Override | ||||
|                             public void run() { | ||||
|                                 listener.onFailure(msg); | ||||
|                             } | ||||
|                         }); | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     public interface OnResponseListener { | ||||
|         /** | ||||
|          * 成功响应(主线程回调) | ||||
|          * @param responseBody 响应体字符串 | ||||
|          */ | ||||
|         void onSuccess(String responseBody); | ||||
|  | ||||
|         /** | ||||
|          * 失败回调(包含错误信息) | ||||
|          * @param errorMsg 错误描述 | ||||
|          */ | ||||
|         void onFailure(String errorMsg); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user