添加应用认证实现类源码

This commit is contained in:
2026-01-15 10:33:42 +08:00
parent ecdaf52939
commit 2d0a4b4ebd
19 changed files with 790 additions and 5 deletions

View File

@@ -43,6 +43,7 @@ android {
}
dependencies {
api project(':library')
// 网络连接类库
api 'com.squareup.okhttp3:okhttp:4.4.1'
// Html 解析

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Jan 14 12:40:12 GMT 2026
#Thu Jan 15 02:32:25 GMT 2026
stageCount=0
libraryProject=
libraryProject=library
baseVersion=15.0
publishVersion=15.0.0
buildCount=44
buildCount=47
baseBetaVersion=15.0.1

View File

@@ -0,0 +1,74 @@
package cc.winboll;
import android.content.Context;
import android.content.SharedPreferences;
import cc.winboll.LogUtils;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/15 10:10
* 本地存储工具类,负责加密私钥等核心数据存储
* 存储于应用私有SharedPreferences仅本应用可访问适配Android API30
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
*/
public class StorageUtils {
private static final String TAG = "StorageUtils";
private static final String SP_NAME = "AuthCenter_Storage";
private static final String KEY_ENCRYPT_PRIVATE_KEY = "encrypt_private_key";
private static SharedPreferences sp;
/**
* 初始化存储工具
*/
public static void init(Context context) {
if (sp == null && context != null) {
sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
}
/**
* 存储加密后的应用私钥
*/
public static boolean saveEncryptPrivateKey(String encryptKey) {
if (sp == null || encryptKey == null) {
LogUtils.w(TAG, "存储加密私钥参数无效");
return false;
}
try {
sp.edit().putString(KEY_ENCRYPT_PRIVATE_KEY, encryptKey).commit();
return true;
} catch (Exception e) {
LogUtils.e(TAG, "存储加密私钥失败", e);
return false;
}
}
/**
* 获取存储的加密应用私钥
*/
public static String getEncryptPrivateKey() {
if (sp == null) {
LogUtils.w(TAG, "存储工具未初始化");
return null;
}
return sp.getString(KEY_ENCRYPT_PRIVATE_KEY, null);
}
/**
* 校验存储的加密私钥与目标数据一致
*/
public static boolean checkEncryptKeyConsistent(String targetKey) {
String saveKey = getEncryptPrivateKey();
return targetKey != null && targetKey.equals(saveKey);
}
/**
* 清除所有存储数据
*/
public static void clearAllData() {
if (sp != null) {
sp.edit().clear().commit();
}
}
}

View File

@@ -0,0 +1,112 @@
package cc.winboll.manager;
import cc.winboll.LogUtils;
import cc.winboll.model.AuthDataModel;
import cc.winboll.utils.AuthUtils;
import cc.winboll.utils.ServerUtils;
import java.util.Timer;
import java.util.TimerTask;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/15 10:14
* @Describe 心跳保活管理类负责定时发送PING请求处理PONG响应
* 适配Java8每分钟发送1次心跳支持异常重试
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
*/
public class HeartbeatManager {
private static final String TAG = "HeartbeatManager";
private static final long HEARTBEAT_INTERVAL = 60 * 1000; // 1分钟
private static Timer heartbeatTimer;
private static String serverPublicKey; // 服务器公钥用于加密PING数据
/**
* 初始化心跳管理器
*/
public static void init(String serverPubKey) {
serverPublicKey = serverPubKey;
LogUtils.d(TAG, "心跳管理器初始化完成,间隔:" + HEARTBEAT_INTERVAL + "ms");
}
/**
* 启动心跳任务
*/
public static void startHeartbeat() {
stopHeartbeat(); // 先停止已有任务,避免重复
heartbeatTimer = new Timer();
heartbeatTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
sendHeartbeatPing();
}
}, 0, HEARTBEAT_INTERVAL);
LogUtils.i(TAG, "心跳保活任务已启动");
}
/**
* 停止心跳任务
*/
public static void stopHeartbeat() {
if (heartbeatTimer != null) {
heartbeatTimer.cancel();
heartbeatTimer = null;
LogUtils.i(TAG, "心跳保活任务已停止");
}
}
/**
* 发送心跳PING请求
*/
private static void sendHeartbeatPing() {
if (!AuthUtils.isLogin() || serverPublicKey == null) {
LogUtils.w(TAG, "未登录或服务器公钥未初始化,跳过心跳");
return;
}
// 组装PING数据
AuthDataModel pingModel = new AuthDataModel();
pingModel.setAppId(AuthUtils.getAppId());
pingModel.setShortToken(AuthUtils.getShortToken());
pingModel.setLoginFlag(true);
String pingJson = pingModel.buildPingJson();
if (pingJson == null) {
LogUtils.w(TAG, "PING数据组装失败");
return;
}
// 服务器公钥加密PING数据
String encryptPing = AuthUtils.encryptByPublicKey(pingJson, serverPublicKey);
if (encryptPing == null) {
LogUtils.w(TAG, "PING数据加密失败");
return;
}
// 发送请求
String response = ServerUtils.sendPingRequest(encryptPing);
if (response != null) {
handlePongResponse(response);
} else {
LogUtils.w(TAG, "心跳PING请求失败下次重试");
}
}
/**
* 处理PONG响应
*/
private static void handlePongResponse(String encryptPongData) {
// 临时私钥解密PONG数据需确保临时私钥已存在
String pongJson = AuthUtils.decryptByPrivateKey(encryptPongData, AuthUtils.getTempPrivateKey().toString());
AuthDataModel pongModel = AuthDataModel.parsePongJson(pongJson);
if (pongModel == null) {
LogUtils.w(TAG, "PONG数据解析失败");
return;
}
// 更新Token
if (pongModel.isLoginFlag() && pongModel.getShortToken() != null) {
AuthUtils.setShortToken(pongModel.getShortToken());
LogUtils.d(TAG, "心跳保活成功Token已更新");
} else {
LogUtils.w(TAG, "登录态失效,停止心跳");
AuthUtils.setLoginStatus(false);
stopHeartbeat();
}
}
}

1
library/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

25
library/build.gradle Normal file
View File

@@ -0,0 +1,25 @@
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply from: '../.winboll/winboll_lib_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
android {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
}

8
library/build.properties Normal file
View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Jan 15 02:32:25 GMT 2026
stageCount=0
libraryProject=library
baseVersion=15.0
publishVersion=15.0.0
buildCount=47
baseBetaVersion=15.0.1

17
library/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:/tools/adt-bundle-windows-x86_64-20131030/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.library" >
<application>
</application>
</manifest>

View File

@@ -0,0 +1,91 @@
package cc.winboll;
import java.util.Date;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
/**
* 日志工具类对接Java原生java.util.logging简化分级调用+统一格式化输出
* 适配Java8语法兼容Android API30支持异常堆栈打印
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/14 00:00:00
* @LastEditTime 2026/01/15 02:42:35
*/
public class LogUtils {
// 全局日志实例,绑定当前工具类名
private static final Logger LOGGER = Logger.getLogger(LogUtils.class.getName());
// 静态代码块初始化日志配置(仅执行一次,全局生效)
static {
// 关闭父处理器,避免日志重复输出
LOGGER.setUseParentHandlers(false);
// 自定义控制台输出处理器,绑定格式化规则
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new CustomLogFormatter());
LOGGER.addHandler(consoleHandler);
// 默认日志级别(全量输出,可按需调整屏蔽低级别日志)
LOGGER.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
}
// 私有化构造方法,禁止外部实例化
private LogUtils() {}
// 调试日志(细粒度开发调试信息,上线可屏蔽)
public static void d(String tag, String msg) {
LOGGER.fine(String.format("[%s] %s", tag, msg));
}
// 调试日志-重载版(带异常堆栈,便于调试阶段排查问题)
public static void d(String tag, String msg, Throwable throwable) {
LOGGER.log(Level.FINE, String.format("[%s] %s", tag, msg), throwable);
}
// 信息日志(常规运行状态、流程节点信息)
public static void i(String tag, String msg) {
LOGGER.info(String.format("[%s] %s", tag, msg));
}
// 信息日志-重载版(带异常堆栈,关联信息类场景的异常链路)
public static void i(String tag, String msg, Throwable throwable) {
LOGGER.log(Level.INFO, String.format("[%s] %s", tag, msg), throwable);
}
// 警告日志-基础版(非致命异常、潜在风险提示)
public static void w(String tag, String msg) {
LOGGER.warning(String.format("[%s] %s", tag, msg));
}
// 警告日志-重载版(带异常堆栈,便于排查警告根源)
public static void w(String tag, String msg, Throwable throwable) {
LOGGER.log(Level.WARNING, String.format("[%s] %s", tag, msg), throwable);
}
// 错误日志-基础版(致命异常、核心流程错误提示)
public static void e(String tag, String msg) {
LOGGER.severe(String.format("[%s] %s", tag, msg));
}
// 错误日志-重载版(带异常堆栈,定位错误完整链路)
public static void e(String tag, String msg, Throwable throwable) {
LOGGER.log(Level.SEVERE, String.format("[%s] %s", tag, msg), throwable);
}
// 自定义日志格式化器,统一输出格式,提升可读性
static class CustomLogFormatter extends Formatter {
@Override
public String format(LogRecord record) {
// 输出格式:[时间戳] [日志级别] [线程名] [日志源] - 日志内容
return String.format("[%1$tF %1$tT] [%2$s] [%3$s] %4$s - %5$s%n",
new Date(record.getMillis()),
record.getLevel().getName(),
Thread.currentThread().getName(),
record.getLoggerName(),
formatMessage(record));
}
}
}

View File

@@ -0,0 +1,64 @@
package cc.winboll.model;
import org.json.JSONObject;
import cc.winboll.LogUtils;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/15 10:11
* @Describe 鉴权核心数据模型封装PING/PONG JSON数据负责序列化与反序列化
* 适配Java8支持核心字段解析与组装
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
*/
public class AuthDataModel {
private static final String TAG = "AuthDataModel";
// PING/PONG 核心字段
private String appId;
private String shortToken;
private boolean loginFlag;
public String getAppId() { return appId; }
public void setAppId(String appId) { this.appId = appId; }
public String getShortToken() { return shortToken; }
public void setShortToken(String shortToken) { this.shortToken = shortToken; }
public boolean isLoginFlag() { return loginFlag; }
public void setLoginFlag(boolean loginFlag) { this.loginFlag = loginFlag; }
/**
* 组装PING JSON字符串
*/
public String buildPingJson() {
try {
JSONObject json = new JSONObject();
json.put("appId", appId);
json.put("shortToken", shortToken);
json.put("loginFlag", loginFlag);
return json.toString();
} catch (Exception e) {
LogUtils.e(TAG, "组装PING JSON失败", e);
return null;
}
}
/**
* 解析PONG JSON字符串
*/
public static AuthDataModel parsePongJson(String jsonStr) {
if (jsonStr == null || jsonStr.isEmpty()) {
LogUtils.w(TAG, "PONG JSON字符串为空");
return null;
}
try {
JSONObject json = new JSONObject(jsonStr);
AuthDataModel model = new AuthDataModel();
model.setAppId(json.optString("appId"));
model.setShortToken(json.optString("shortToken"));
model.setLoginFlag(json.optBoolean("loginFlag"));
return model;
} catch (Exception e) {
LogUtils.e(TAG, "解析PONG JSON失败", e);
return null;
}
}
}

View File

@@ -0,0 +1,111 @@
package cc.winboll.utils;
import cc.winboll.LogUtils;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
/**
* 鉴权核心工具类,负责密钥生成、加解密、登录态管理
* 适配Java8非对称加密采用RSA 2048位
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/15 09:56:00
*/
public class AuthUtils {
private static final String TAG = "AuthUtils";
private static final String ALGORITHM = "RSA";
private static final int KEY_SIZE = 2048;
// 临时存储明文私钥,使用后立即置空
private static PrivateKey tempPrivateKey;
// 登录态核心参数
private static String appId;
private static String shortToken;
private static boolean loginStatus = false;
/**
* 生成RSA密钥对公钥+私钥)
*/
public static KeyPair generateRsaKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
keyGen.initialize(KEY_SIZE);
return keyGen.generateKeyPair();
} catch (Exception e) {
LogUtils.e(TAG, "生成RSA密钥对失败", e);
return null;
}
}
/**
* 公钥加密数据
*/
public static String encryptByPublicKey(String data, String publicKeyStr) {
try {
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
PublicKey publicKey = java.security.KeyFactory.getInstance(ALGORITHM).generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptBytes = cipher.doFinal(data.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encryptBytes);
} catch (Exception e) {
LogUtils.e(TAG, "公钥加密失败", e);
return null;
}
}
/**
* 私钥解密数据
*/
public static String decryptByPrivateKey(String data, String privateKeyStr) {
try {
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = java.security.KeyFactory.getInstance(ALGORITHM).generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptBytes = cipher.doFinal(Base64.getDecoder().decode(data));
return new String(decryptBytes, "UTF-8");
} catch (Exception e) {
LogUtils.e(TAG, "私钥解密失败", e);
return null;
}
}
/**
* 获取临时明文私钥
*/
public static PrivateKey getTempPrivateKey() {
return tempPrivateKey;
}
/**
* 临时存储明文私钥,用完需手动置空
*/
public static void setTempPrivateKey(PrivateKey privateKey) {
tempPrivateKey = privateKey;
}
/**
* 清空临时私钥,保障安全
*/
public static void clearTempPrivateKey() {
tempPrivateKey = null;
}
// 登录态相关get/set方法
public static String getAppId() { return appId; }
public static void setAppId(String appId) { AuthUtils.appId = appId; }
public static String getShortToken() { return shortToken; }
public static void setShortToken(String shortToken) { AuthUtils.shortToken = shortToken; }
public static boolean isLogin() { return loginStatus; }
public static void setLoginStatus(boolean status) { loginStatus = status; }
}

View File

@@ -0,0 +1,124 @@
package cc.winboll.utils;
import android.content.Context;
import android.nfc.FormatException;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.nfc.tech.NdefFormatable;
import cc.winboll.LogUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/15 10:06
* NFC操作工具类负责NFC功能检测、卡连接、数据读写
* 适配Android API30仅支持空白NFC卡操作
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
*/
public class NFCUtils {
private static final String TAG = "NFCUtils";
/**
* 检测手机NFC功能是否开启
*/
public static boolean isNfcEnabled(Context context) {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);
return nfcAdapter != null && nfcAdapter.isEnabled();
}
/**
* 检测是否为空白NFC卡无NDEF数据
*/
public static boolean isBlankNfcCard(Tag tag) {
try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
return ndef.getCachedNdefMessage() == null || ndef.getCachedNdefMessage().getRecords().length == 0;
}
NdefFormatable ndefFormatable = NdefFormatable.get(tag);
return ndefFormatable != null;
} catch (Exception e) {
LogUtils.w(TAG, "检测NFC卡是否空白失败", e);
return false;
}
}
/**
* 向NFC卡写入加密数据仅支持NDEF格式
*/
public static boolean writeDataToNfc(Tag tag, String encryptData) {
if (tag == null || encryptData == null || encryptData.isEmpty()) {
LogUtils.w(TAG, "NFC写入参数无效");
return false;
}
try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
if (!ndef.isWritable()) {
LogUtils.w(TAG, "NFC卡不可写入");
return false;
}
android.nfc.NdefRecord record = new android.nfc.NdefRecord(
android.nfc.NdefRecord.TNF_WELL_KNOWN,
android.nfc.NdefRecord.RTD_TEXT,
new byte[0],
encryptData.getBytes(StandardCharsets.UTF_8)
);
android.nfc.NdefMessage message = new android.nfc.NdefMessage(new android.nfc.NdefRecord[]{record});
int size = message.toByteArray().length;
if (ndef.getMaxSize() < size) {
LogUtils.w(TAG, "NFC卡存储空间不足");
return false;
}
ndef.writeNdefMessage(message);
ndef.close();
return true;
} else {
NdefFormatable ndefFormatable = NdefFormatable.get(tag);
if (ndefFormatable != null) {
ndefFormatable.connect();
android.nfc.NdefRecord record = new android.nfc.NdefRecord(
android.nfc.NdefRecord.TNF_WELL_KNOWN,
android.nfc.NdefRecord.RTD_TEXT,
new byte[0],
encryptData.getBytes(StandardCharsets.UTF_8)
);
android.nfc.NdefMessage message = new android.nfc.NdefMessage(new android.nfc.NdefRecord[]{record});
ndefFormatable.format(message);
ndefFormatable.close();
return true;
}
}
} catch (FormatException|IOException e) {
LogUtils.e(TAG, "NFC卡写入失败", e);
}
return false;
}
/**
* 从NFC卡读取数据
*/
public static String readDataFromNfc(Tag tag) {
if (tag == null) {
return null;
}
try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
android.nfc.NdefMessage message = ndef.getCachedNdefMessage();
ndef.close();
if (message != null && message.getRecords().length > 0) {
return new String(message.getRecords()[0].getPayload(), StandardCharsets.UTF_8);
}
}
} catch (IOException e) {
LogUtils.e(TAG, "NFC卡读取失败", e);
}
return null;
}
}

View File

@@ -0,0 +1,129 @@
package cc.winboll.utils;
import cc.winboll.LogUtils;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/15 10:08
* 服务器交互工具类,负责请求封装、接口调用、响应处理
* 适配Java8支持GET/POST请求兼容Android与标准JVM
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
*/
public class ServerUtils {
private static final String TAG = "ServerUtils";
private static final int CONNECT_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 5000;
private static String serverBaseUrl; // 服务器基础地址
/**
* 初始化服务器基础地址
*/
public static void initServerUrl(String url) {
serverBaseUrl = url;
LogUtils.d(TAG, "服务器地址初始化完成:" + serverBaseUrl);
}
/**
* 发送验证码请求POST
*/
public static String sendVerifyCode(String email) {
String url = serverBaseUrl + "/api/sendVerifyCode";
Map<String, String> params = new HashMap<>();
params.put("email", email);
return sendPostRequest(url, params);
}
/**
* 校验验证码请求POST
*/
public static String verifyCode(String email, String code) {
String url = serverBaseUrl + "/api/verifyCode";
Map<String, String> params = new HashMap<>();
params.put("email", email);
params.put("code", code);
return sendPostRequest(url, params);
}
/**
* 提交应用公钥请求POST
*/
public static String submitAppPublicKey(String email, String publicKey) {
String url = serverBaseUrl + "/api/submitPublicKey";
Map<String, String> params = new HashMap<>();
params.put("email", email);
params.put("appPublicKey", publicKey);
return sendPostRequest(url, params);
}
/**
* 发送心跳PING请求POST加密数据
*/
public static String sendPingRequest(String encryptPingData) {
String url = serverBaseUrl + "/api/heartbeat/ping";
Map<String, String> params = new HashMap<>();
params.put("encryptData", encryptPingData);
return sendPostRequest(url, params);
}
/**
* 通用POST请求封装
*/
private static String sendPostRequest(String urlStr, Map<String, String> params) {
if (urlStr == null || params == null) {
LogUtils.w(TAG, "POST请求参数为空");
return null;
}
HttpURLConnection conn = null;
try {
URL url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
// 拼接参数
StringBuilder paramStr = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
paramStr.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
if (paramStr.length() > 0) {
paramStr.deleteCharAt(paramStr.length() - 1);
}
OutputStream os = conn.getOutputStream();
os.write(paramStr.toString().getBytes("UTF-8"));
os.flush();
os.close();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
br.close();
return response.toString();
} else {
LogUtils.w(TAG, "POST请求失败响应码" + conn.getResponseCode());
}
} catch (Exception e) {
LogUtils.e(TAG, "POST请求异常", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="lib_name">library</string>
<string name="hello_world">Hello world!</string>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.Holo.Light">
</style>
</resources>

View File

@@ -32,6 +32,7 @@
// AuthCenterAPP 项目编译设置
//include ':authcenterapp'
//include ':library'
//rootProject.name = "authcenterapp"
// Contacts 项目编译设置

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Jan 13 16:53:24 HKT 2026
#Tue Jan 13 18:37:44 GMT 2026
stageCount=11
libraryProject=
baseVersion=15.11
publishVersion=15.11.10
buildCount=0
buildCount=1
baseBetaVersion=15.11.11

View File

@@ -11,5 +11,11 @@
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- 允许特定域名的明文请求仅本地127.0.0.1 -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>