From b66d53da1bd6e2eefe7e8056ff142388b0cdf51e Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 22 Sep 2025 06:31:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5APPBase=E9=80=92=E4=BA=A4?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apputils/build.gradle | 2 +- apputils/build.properties | 12 +- libapputils/build.gradle | 2 + libapputils/build.properties | 12 +- .../studio/libapputils/utils/AppUtils.java | 29 ++ .../studio/libapputils/utils/FileUtils.java | 146 +++++++-- .../studio/libapputils/utils/RSAUtils.java | 222 ++++++++++++++ .../studio/libapputils/utils/YunUtils.java | 281 ++++++++++++++++++ 8 files changed, 662 insertions(+), 44 deletions(-) create mode 100644 libapputils/src/main/java/cc/winboll/studio/libapputils/utils/AppUtils.java create mode 100644 libapputils/src/main/java/cc/winboll/studio/libapputils/utils/RSAUtils.java create mode 100644 libapputils/src/main/java/cc/winboll/studio/libapputils/utils/YunUtils.java diff --git a/apputils/build.gradle b/apputils/build.gradle index e0e7c88..09513c2 100644 --- a/apputils/build.gradle +++ b/apputils/build.gradle @@ -29,7 +29,7 @@ android { // versionName 更新后需要手动设置 // 项目模块目录的 build.gradle 文件的 stageCount=0 // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" - versionName "15.8" + versionName "15.9" if(true) { versionName = genVersionName("${versionName}") } diff --git a/apputils/build.properties b/apputils/build.properties index 2be9ccd..83cf439 100644 --- a/apputils/build.properties +++ b/apputils/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Sep 01 07:56:33 HKT 2025 -stageCount=7 +#Sun Sep 21 22:28:28 GMT 2025 +stageCount=0 libraryProject=libapputils -baseVersion=15.8 -publishVersion=15.8.6 -buildCount=0 -baseBetaVersion=15.8.7 +baseVersion=15.9 +publishVersion=15.9.0 +buildCount=2 +baseBetaVersion=15.9.1 diff --git a/libapputils/build.gradle b/libapputils/build.gradle index 3e2a2bc..56cda42 100644 --- a/libapputils/build.gradle +++ b/libapputils/build.gradle @@ -32,6 +32,8 @@ dependencies { // Html 解析 api 'org.jsoup:jsoup:1.13.1' + + api 'com.google.code.gson:gson:2.10.1' // SSH //api 'com.jcraft:jsch:0.1.55' diff --git a/libapputils/build.properties b/libapputils/build.properties index d28973e..83cf439 100644 --- a/libapputils/build.properties +++ b/libapputils/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Sep 01 07:56:11 HKT 2025 -stageCount=7 +#Sun Sep 21 22:28:28 GMT 2025 +stageCount=0 libraryProject=libapputils -baseVersion=15.8 -publishVersion=15.8.6 -buildCount=0 -baseBetaVersion=15.8.7 +baseVersion=15.9 +publishVersion=15.9.0 +buildCount=2 +baseBetaVersion=15.9.1 diff --git a/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/AppUtils.java b/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/AppUtils.java new file mode 100644 index 0000000..749a411 --- /dev/null +++ b/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/AppUtils.java @@ -0,0 +1,29 @@ +package cc.winboll.studio.libapputils.utils; + +/** + * @Author ZhanGSKen + * @Date 2025/02/15 20:05:03 + * @Describe AppUtils + */ +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import cc.winboll.studio.libappbase.LogUtils; + +public class AppUtils { + + public static final String TAG = "AppUtils"; + + public static String getAppNameByPackageName(Context context, String packageName) { + PackageManager packageManager = context.getPackageManager(); + try { + ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0); + return (String) packageManager.getApplicationLabel(applicationInfo); + } catch (NameNotFoundException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + return ""; + } + } +} + diff --git a/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/FileUtils.java b/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/FileUtils.java index 1da64f9..f42adf8 100644 --- a/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/FileUtils.java +++ b/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/FileUtils.java @@ -9,10 +9,16 @@ import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.net.Uri; +import android.support.v4.content.FileProvider; import cc.winboll.studio.libappbase.LogUtils; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -22,7 +28,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import android.support.v4.content.FileProvider; public class FileUtils { @@ -97,36 +102,6 @@ public class FileUtils { } } - // - // 把字符串写入文件,指定 UTF-8 编码 - // - public static void writeStringToFile(String szFilePath, String szContent) throws IOException { - File file = new File(szFilePath); - if (!file.getParentFile().exists()) { - file.getParentFile().mkdirs(); - } - FileOutputStream outputStream = new FileOutputStream(file); - OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - writer.write(szContent); - writer.close(); - } - - // - // 读取文件到字符串,指定 UTF-8 编码 - // - public static String readStringFromFile(String szFilePath) throws IOException { - File file = new File(szFilePath); - FileInputStream inputStream = new FileInputStream(file); - InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); - StringBuilder content = new StringBuilder(); - int character; - while ((character = reader.read()) != -1) { - content.append((char) character); - } - reader.close(); - return content.toString(); - } - public static boolean copyFile(File srcFile, File dstFile) { if (!srcFile.exists()) { LogUtils.d(TAG, "The original file does not exist."); @@ -154,4 +129,113 @@ public class FileUtils { } return false; } + + + /** + * 读取文件为字节数组(Java 7 语法) + */ + public static byte[] readByteArrayFromFile(String filePath) { + FileInputStream fis = null; + ByteArrayOutputStream bos = null; + try { + fis = new FileInputStream(filePath); + bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = fis.read(buffer)) != -1) { + bos.write(buffer, 0, bytesRead); + } + return bos.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + // 手动关闭流(Java 7 不支持 try-with-resources) + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (bos != null) { + try { + bos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 写入字节数组到文件(Java 7 语法) + */ + public static boolean writeByteArrayToFile(byte[] data, String filePath) { + FileOutputStream fos = null; + try { + fos = new FileOutputStream(filePath); + fos.write(data); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static String readStringFromFile(String filePath) { + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(filePath)); + StringBuilder content = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append(System.getProperty("line.separator")); + } + // 去除最后一个换行符(可选) + if (content.length() > 0) { + content.deleteCharAt(content.length() - 1); + } + return content.toString(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static boolean writeStringToFile(String content, String filePath, boolean append) { + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new FileWriter(filePath, append)); + writer.write(content); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } } diff --git a/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/RSAUtils.java b/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/RSAUtils.java new file mode 100644 index 0000000..1ef63ec --- /dev/null +++ b/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/RSAUtils.java @@ -0,0 +1,222 @@ +package cc.winboll.studio.libapputils.utils; + +/** + * @Author ZhanGSKen + * @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); + } +} + diff --git a/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/YunUtils.java b/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/YunUtils.java new file mode 100644 index 0000000..002bb9c --- /dev/null +++ b/libapputils/src/main/java/cc/winboll/studio/libapputils/utils/YunUtils.java @@ -0,0 +1,281 @@ +package cc.winboll.studio.libapputils.utils; + +/** + * @Author ZhanGSKen + * @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.libappbase.models.ResponseData; +import cc.winboll.studio.libappbase.models.UserInfoModel; +import com.google.gson.Gson; +import java.io.File; +import java.io.IOException; +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; +import java.io.UnsupportedEncodingException; + +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); + } +}