加入APPBase递交模块
This commit is contained in:
		| @@ -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}") | ||||
|         } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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' | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -0,0 +1,29 @@ | ||||
| package cc.winboll.studio.libapputils.utils; | ||||
|  | ||||
| /** | ||||
|  * @Author ZhanGSKen<zhangsken@qq.com> | ||||
|  * @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 ""; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -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(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,222 @@ | ||||
| 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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,281 @@ | ||||
| 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.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); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen