Compare commits
	
		
			3 Commits
		
	
	
		
			contacts-v
			...
			471ca23585
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					471ca23585 | ||
| 
						 | 
					56be1767bb | ||
| 
						 | 
					b66d53da1b | 
@@ -29,7 +29,7 @@ android {
 | 
				
			|||||||
        // versionName 更新后需要手动设置 
 | 
					        // versionName 更新后需要手动设置 
 | 
				
			||||||
        // 项目模块目录的 build.gradle 文件的 stageCount=0
 | 
					        // 项目模块目录的 build.gradle 文件的 stageCount=0
 | 
				
			||||||
        // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
 | 
					        // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
 | 
				
			||||||
        versionName "15.8" 
 | 
					        versionName "15.9" 
 | 
				
			||||||
        if(true) {
 | 
					        if(true) {
 | 
				
			||||||
            versionName = genVersionName("${versionName}")
 | 
					            versionName = genVersionName("${versionName}")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
#Created by .winboll/winboll_app_build.gradle
 | 
					#Created by .winboll/winboll_app_build.gradle
 | 
				
			||||||
#Mon Sep 01 07:56:33 HKT 2025
 | 
					#Sun Sep 21 22:28:28 GMT 2025
 | 
				
			||||||
stageCount=7
 | 
					stageCount=0
 | 
				
			||||||
libraryProject=libapputils
 | 
					libraryProject=libapputils
 | 
				
			||||||
baseVersion=15.8
 | 
					baseVersion=15.9
 | 
				
			||||||
publishVersion=15.8.6
 | 
					publishVersion=15.9.0
 | 
				
			||||||
buildCount=0
 | 
					buildCount=2
 | 
				
			||||||
baseBetaVersion=15.8.7
 | 
					baseBetaVersion=15.9.1
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,8 @@ dependencies {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    // Html 解析
 | 
					    // Html 解析
 | 
				
			||||||
    api 'org.jsoup:jsoup:1.13.1'
 | 
					    api 'org.jsoup:jsoup:1.13.1'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						api 'com.google.code.gson:gson:2.10.1'
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // SSH
 | 
					    // SSH
 | 
				
			||||||
    //api 'com.jcraft:jsch:0.1.55'
 | 
					    //api 'com.jcraft:jsch:0.1.55'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
#Created by .winboll/winboll_app_build.gradle
 | 
					#Created by .winboll/winboll_app_build.gradle
 | 
				
			||||||
#Mon Sep 01 07:56:11 HKT 2025
 | 
					#Sun Sep 21 22:28:28 GMT 2025
 | 
				
			||||||
stageCount=7
 | 
					stageCount=0
 | 
				
			||||||
libraryProject=libapputils
 | 
					libraryProject=libapputils
 | 
				
			||||||
baseVersion=15.8
 | 
					baseVersion=15.9
 | 
				
			||||||
publishVersion=15.8.6
 | 
					publishVersion=15.9.0
 | 
				
			||||||
buildCount=0
 | 
					buildCount=2
 | 
				
			||||||
baseBetaVersion=15.8.7
 | 
					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.Intent;
 | 
				
			||||||
import android.content.res.AssetManager;
 | 
					import android.content.res.AssetManager;
 | 
				
			||||||
import android.net.Uri;
 | 
					import android.net.Uri;
 | 
				
			||||||
 | 
					import android.support.v4.content.FileProvider;
 | 
				
			||||||
import cc.winboll.studio.libappbase.LogUtils;
 | 
					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.File;
 | 
				
			||||||
import java.io.FileInputStream;
 | 
					import java.io.FileInputStream;
 | 
				
			||||||
import java.io.FileOutputStream;
 | 
					import java.io.FileOutputStream;
 | 
				
			||||||
 | 
					import java.io.FileReader;
 | 
				
			||||||
 | 
					import java.io.FileWriter;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.io.InputStreamReader;
 | 
					import java.io.InputStreamReader;
 | 
				
			||||||
@@ -22,7 +28,6 @@ import java.nio.charset.StandardCharsets;
 | 
				
			|||||||
import java.nio.file.Files;
 | 
					import java.nio.file.Files;
 | 
				
			||||||
import java.nio.file.Path;
 | 
					import java.nio.file.Path;
 | 
				
			||||||
import java.nio.file.Paths;
 | 
					import java.nio.file.Paths;
 | 
				
			||||||
import android.support.v4.content.FileProvider;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class FileUtils {
 | 
					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) {
 | 
					    public static boolean copyFile(File srcFile, File dstFile) {
 | 
				
			||||||
        if (!srcFile.exists()) {
 | 
					        if (!srcFile.exists()) {
 | 
				
			||||||
            LogUtils.d(TAG, "The original file does not exist.");
 | 
					            LogUtils.d(TAG, "The original file does not exist.");
 | 
				
			||||||
@@ -154,4 +129,113 @@ public class FileUtils {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return false;
 | 
					        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