重构WinBoLl类库命名空间
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Jan 15 02:32:25 GMT 2026
|
||||
#Thu Jan 15 03:05:40 GMT 2026
|
||||
stageCount=0
|
||||
libraryProject=library
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.0
|
||||
buildCount=47
|
||||
buildCount=50
|
||||
baseBetaVersion=15.0.1
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
|
||||
<activity android:name="cc.winboll.studio.authcenterapp.unittest.PingTestActivity"/>
|
||||
|
||||
<activity android:name="cc.winboll.studio.authcenterapp.activities.AuthTestActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -9,6 +9,7 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.authcenterapp.R;
|
||||
import cc.winboll.studio.authcenterapp.activities.AuthTestActivity;
|
||||
import cc.winboll.studio.authcenterapp.activities.BaseWinBoLLActivity;
|
||||
import cc.winboll.studio.authcenterapp.activities.ConsoleActivity;
|
||||
import cc.winboll.studio.authcenterapp.unittest.PingTestActivity;
|
||||
@@ -103,6 +104,9 @@ public class MainActivity extends BaseWinBoLLActivity {
|
||||
break;
|
||||
case R.id.item_ping_test:
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), PingTestActivity.class);
|
||||
break;
|
||||
case R.id.item_auth_test:
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), AuthTestActivity.class);
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
package cc.winboll.studio.authcenterapp.activities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.LogUtils;
|
||||
import cc.winboll.library.model.AuthDataModel;
|
||||
import cc.winboll.library.utils.AuthUtils;
|
||||
import cc.winboll.library.utils.NFCUtils;
|
||||
import cc.winboll.library.utils.ServerUtils;
|
||||
import cc.winboll.studio.authcenterapp.R;
|
||||
import cc.winboll.studio.authcenterapp.activities.AuthTestActivity;
|
||||
import cc.winboll.studio.authcenterapp.manager.HeartbeatManager;
|
||||
import cc.winboll.studio.authcenterapp.utils.StorageUtils;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 鉴权全流程测试Activity,分步执行+单步验证,适配全流程校验点
|
||||
* 适配Android API30,基于Java7语法开发,支持单步测试与全流程自动化测试
|
||||
* 集成NFC绑定、密钥加解密、服务器交互、心跳保活全链路测试能力
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/15 15:30:00
|
||||
* @LastEditTime 2026/01/15 17:12:00
|
||||
*/
|
||||
public class AuthTestActivity extends BaseWinBoLLActivity implements View.OnClickListener {
|
||||
// 常量定义区
|
||||
private static final String TAG = "AuthTestActivity";
|
||||
private static final String SERVER_PUBLIC_KEY = "此处替换为实际服务器公钥";
|
||||
|
||||
// 控件属性区
|
||||
private EditText etEmail;
|
||||
private EditText etServerUrl;
|
||||
private EditText etVerifyCode;
|
||||
private TextView tvTestStatus;
|
||||
private Button btnStep1;
|
||||
private Button btnStep2;
|
||||
private Button btnStep3;
|
||||
private Button btnStep4;
|
||||
private Button btnStep5;
|
||||
private Button btnStep6;
|
||||
private Button btnStepAll;
|
||||
|
||||
// 核心工具与缓存属性区
|
||||
private Handler mainHandler;
|
||||
private String encryptPrivateKey; // 加密后的应用私钥
|
||||
private String appPublicKeyStr; // 应用公钥字符串
|
||||
|
||||
// ===================== 生命周期方法 =====================
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_auth_test);
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
LogUtils.d(TAG, "onCreate:鉴权测试页面初始化");
|
||||
initView();
|
||||
initBaseConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
LogUtils.d(TAG, "onNewIntent:接收新Intent,检测是否为NFC触发");
|
||||
// 接收NFC标签广播,处理真实NFC写入逻辑
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
||||
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||
LogUtils.d(TAG, "onNewIntent:检测到NFC标签,开始写入加密私钥");
|
||||
if (tag != null && encryptPrivateKey != null) {
|
||||
boolean writeSuccess = NFCUtils.writeDataToNfc(tag, encryptPrivateKey);
|
||||
if (writeSuccess) {
|
||||
updateStatus("NFC写入成功:加密私钥已写入NFC卡");
|
||||
LogUtils.i(TAG, "onNewIntent:NFC卡写入加密私钥成功");
|
||||
} else {
|
||||
LogUtils.w(TAG, "onNewIntent:NFC卡写入加密私钥失败");
|
||||
}
|
||||
} else {
|
||||
LogUtils.w(TAG, "onNewIntent:NFC标签或加密私钥为空,无法写入");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy:页面销毁,停止心跳并清空临时私钥");
|
||||
// 安全兜底:停止心跳+清空临时私钥,防止内存泄露
|
||||
HeartbeatManager.stopHeartbeat();
|
||||
AuthUtils.clearTempPrivateKey();
|
||||
}
|
||||
|
||||
// ===================== 点击事件与初始化方法 =====================
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
LogUtils.d(TAG, "onClick:触发点击事件,控件ID=" + v.getId());
|
||||
if (id == R.id.btn_step1_check_input) {
|
||||
testStep1CheckInput();
|
||||
} else if (id == R.id.btn_step2_send_code) {
|
||||
testStep2SendVerifyCode();
|
||||
} else if (id == R.id.btn_step3_verify_code) {
|
||||
testStep3VerifyCode();
|
||||
} else if (id == R.id.btn_step4_nfc_bind) {
|
||||
testStep4NfcBind();
|
||||
} else if (id == R.id.btn_step5_submit_pubkey) {
|
||||
testStep5SubmitPublicKey();
|
||||
} else if (id == R.id.btn_step6_heartbeat) {
|
||||
testStep6Heartbeat();
|
||||
} else if (id == R.id.btn_step_all_test) {
|
||||
testAllStep();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件与点击事件绑定
|
||||
*/
|
||||
private void initView() {
|
||||
// 绑定控件
|
||||
etEmail = (EditText) findViewById(R.id.et_test_email);
|
||||
etServerUrl = (EditText) findViewById(R.id.et_test_server_url);
|
||||
etVerifyCode = (EditText) findViewById(R.id.et_test_verify_code);
|
||||
tvTestStatus = (TextView) findViewById(R.id.tv_test_status);
|
||||
btnStep1 = (Button) findViewById(R.id.btn_step1_check_input);
|
||||
btnStep2 = (Button) findViewById(R.id.btn_step2_send_code);
|
||||
btnStep3 = (Button) findViewById(R.id.btn_step3_verify_code);
|
||||
btnStep4 = (Button) findViewById(R.id.btn_step4_nfc_bind);
|
||||
btnStep5 = (Button) findViewById(R.id.btn_step5_submit_pubkey);
|
||||
btnStep6 = (Button) findViewById(R.id.btn_step6_heartbeat);
|
||||
btnStepAll = (Button) findViewById(R.id.btn_step_all_test);
|
||||
|
||||
// 绑定点击事件
|
||||
btnStep1.setOnClickListener(this);
|
||||
btnStep2.setOnClickListener(this);
|
||||
btnStep3.setOnClickListener(this);
|
||||
btnStep4.setOnClickListener(this);
|
||||
btnStep5.setOnClickListener(this);
|
||||
btnStep6.setOnClickListener(this);
|
||||
btnStepAll.setOnClickListener(this);
|
||||
LogUtils.d(TAG, "initView:控件初始化与点击事件绑定完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化基础配置(存储、服务器工具)
|
||||
*/
|
||||
private void initBaseConfig() {
|
||||
LogUtils.d(TAG, "initBaseConfig:开始初始化基础工具类");
|
||||
StorageUtils.init(this);
|
||||
updateStatus("基础工具初始化完成,等待测试");
|
||||
LogUtils.i(TAG, "initBaseConfig:基础工具初始化完成");
|
||||
}
|
||||
|
||||
// ===================== 分步测试核心方法 =====================
|
||||
/**
|
||||
* 步骤1:测试邮箱+服务地址输入校验
|
||||
*/
|
||||
private void testStep1CheckInput() {
|
||||
LogUtils.d(TAG, "testStep1CheckInput:开始执行步骤1-邮箱与服务地址校验");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String email = etEmail.getText().toString().trim();
|
||||
String serverUrl = etServerUrl.getText().toString().trim();
|
||||
LogUtils.d(TAG, "testStep1CheckInput:入参-email=" + email + ",serverUrl=" + serverUrl);
|
||||
|
||||
// 基础格式校验
|
||||
if (email.isEmpty() || !email.contains("@")) {
|
||||
updateStatus("步骤1失败:邮箱格式无效");
|
||||
LogUtils.w(TAG, "testStep1CheckInput:邮箱为空或格式无效");
|
||||
return;
|
||||
}
|
||||
if (serverUrl.isEmpty() || (!serverUrl.startsWith("http://") && !serverUrl.startsWith("https://"))) {
|
||||
updateStatus("步骤1失败:服务地址需带http/https");
|
||||
LogUtils.w(TAG, "testStep1CheckInput:服务地址为空或缺少http/https协议头");
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化服务器地址
|
||||
ServerUtils.initServerUrl(serverUrl);
|
||||
updateStatus("步骤1成功:邮箱+服务地址校验通过");
|
||||
LogUtils.i(TAG, "testStep1CheckInput:步骤1执行成功,服务器地址已初始化");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤2:测试发送验证码
|
||||
*/
|
||||
private void testStep2SendVerifyCode() {
|
||||
LogUtils.d(TAG, "testStep2SendVerifyCode:开始执行步骤2-发送验证码");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String email = etEmail.getText().toString().trim();
|
||||
LogUtils.d(TAG, "testStep2SendVerifyCode:入参-email=" + email);
|
||||
|
||||
// 前置校验
|
||||
if (email.isEmpty() || !email.contains("@")) {
|
||||
updateStatus("步骤2失败:请先完成步骤1校验邮箱");
|
||||
LogUtils.w(TAG, "testStep2SendVerifyCode:前置校验失败,邮箱未通过步骤1验证");
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用服务器接口发送验证码
|
||||
String result = ServerUtils.sendVerifyCode(email);
|
||||
if (result != null && result.contains("success")) {
|
||||
updateStatus("步骤2成功:验证码已发送,请注意查收");
|
||||
LogUtils.i(TAG, "testStep2SendVerifyCode:步骤2执行成功,验证码发送成功");
|
||||
} else {
|
||||
updateStatus("步骤2失败:验证码发送失败,服务异常");
|
||||
LogUtils.w(TAG, "testStep2SendVerifyCode:步骤2执行失败,服务返回结果=" + result);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤3:测试验证码校验
|
||||
*/
|
||||
private void testStep3VerifyCode() {
|
||||
LogUtils.d(TAG, "testStep3VerifyCode:开始执行步骤3-校验验证码");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String email = etEmail.getText().toString().trim();
|
||||
String code = etVerifyCode.getText().toString().trim();
|
||||
LogUtils.d(TAG, "testStep3VerifyCode:入参-email=" + email + ",code=" + code);
|
||||
|
||||
// 验证码格式校验
|
||||
if (code.isEmpty() || code.length() != 6) {
|
||||
updateStatus("步骤3失败:请输入6位验证码");
|
||||
LogUtils.w(TAG, "testStep3VerifyCode:验证码为空或长度非6位");
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用服务器接口校验验证码
|
||||
String result = ServerUtils.verifyCode(email, code);
|
||||
if (result != null && result.contains("success")) {
|
||||
updateStatus("步骤3成功:验证码校验通过");
|
||||
LogUtils.i(TAG, "testStep3VerifyCode:步骤3执行成功,验证码校验通过");
|
||||
} else {
|
||||
updateStatus("步骤3失败:验证码错误或已过期");
|
||||
LogUtils.w(TAG, "testStep3VerifyCode:步骤3执行失败,服务返回结果=" + result);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤4:测试NFC绑定(生成密钥+写入NFC+本地存储)
|
||||
*/
|
||||
private void testStep4NfcBind() {
|
||||
LogUtils.d(TAG, "testStep4NfcBind:开始执行步骤4-NFC绑定与密钥处理");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 1. NFC功能可用性检测
|
||||
if (!NFCUtils.isNfcEnabled(AuthTestActivity.this)) {
|
||||
updateStatus("步骤4失败:请先开启手机NFC功能");
|
||||
LogUtils.w(TAG, "testStep4NfcBind:手机NFC功能未开启");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 生成RSA密钥对
|
||||
KeyPair keyPair = AuthUtils.generateRsaKeyPair();
|
||||
if (keyPair == null) {
|
||||
updateStatus("步骤4失败:RSA密钥对生成失败");
|
||||
LogUtils.e(TAG, "testStep4NfcBind:RSA密钥对生成失败");
|
||||
return;
|
||||
}
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
appPublicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
|
||||
LogUtils.d(TAG, "testStep4NfcBind:RSA密钥对生成成功,应用公钥已Base64编码");
|
||||
|
||||
// 3. 服务器公钥加密应用私钥
|
||||
String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKey.getEncoded());
|
||||
encryptPrivateKey = AuthUtils.encryptByPublicKey(privateKeyBase64, SERVER_PUBLIC_KEY);
|
||||
if (encryptPrivateKey == null) {
|
||||
updateStatus("步骤4失败:应用私钥加密失败");
|
||||
LogUtils.e(TAG, "testStep4NfcBind:应用私钥通过服务器公钥加密失败");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "testStep4NfcBind:应用私钥加密成功,准备写入NFC与本地存储");
|
||||
|
||||
// 4. 提示用户贴近NFC卡,简化逻辑预留广播接收入口
|
||||
updateStatus("步骤4中:请将空白NFC卡贴近手机感应区...");
|
||||
boolean writeSuccess = false; // 真实场景由NFC广播回调更新
|
||||
|
||||
// 5. 本地存储加密私钥副本
|
||||
boolean saveSuccess = StorageUtils.saveEncryptPrivateKey(encryptPrivateKey);
|
||||
if (saveSuccess && writeSuccess) {
|
||||
updateStatus("步骤4成功:NFC写入+本地存储加密私钥完成");
|
||||
LogUtils.i(TAG, "testStep4NfcBind:步骤4执行成功,NFC写入与本地存储均完成");
|
||||
} else if (saveSuccess) {
|
||||
updateStatus("步骤4成功(简化):本地存储完成,NFC需配合广播优化");
|
||||
LogUtils.i(TAG, "testStep4NfcBind:步骤4执行成功(简化),本地存储完成,NFC需依赖广播触发");
|
||||
} else {
|
||||
updateStatus("步骤4失败:本地存储加密私钥失败");
|
||||
LogUtils.e(TAG, "testStep4NfcBind:步骤4执行失败,加密私钥本地存储失败");
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤5:测试提交应用公钥+解析PONG数据完成鉴权
|
||||
*/
|
||||
private void testStep5SubmitPublicKey() {
|
||||
LogUtils.d(TAG, "testStep5SubmitPublicKey:开始执行步骤5-提交公钥与鉴权登录");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 前置校验:应用公钥是否已生成
|
||||
if (appPublicKeyStr == null || appPublicKeyStr.isEmpty()) {
|
||||
updateStatus("步骤5失败:请先完成步骤4生成应用公钥");
|
||||
LogUtils.w(TAG, "testStep5SubmitPublicKey:前置校验失败,应用公钥未生成(未执行步骤4)");
|
||||
return;
|
||||
}
|
||||
String email = etEmail.getText().toString().trim();
|
||||
LogUtils.d(TAG, "testStep5SubmitPublicKey:入参-email=" + email + ",应用公钥已准备");
|
||||
|
||||
// 1. 提交应用公钥到服务器,获取加密PONG数据
|
||||
String encryptPong = ServerUtils.submitAppPublicKey(email, appPublicKeyStr);
|
||||
if (encryptPong == null) {
|
||||
updateStatus("步骤5失败:提交公钥失败,服务无响应");
|
||||
LogUtils.e(TAG, "testStep5SubmitPublicKey:提交应用公钥到服务器失败,服务无响应");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "testStep5SubmitPublicKey:应用公钥提交成功,获取到加密PONG数据");
|
||||
|
||||
// 2. 解密应用私钥(服务器公钥解密)
|
||||
String encryptPriKey = StorageUtils.getEncryptPrivateKey();
|
||||
String privateKeyStr = AuthUtils.decryptByPrivateKey(encryptPriKey, SERVER_PUBLIC_KEY);
|
||||
if (privateKeyStr == null) {
|
||||
updateStatus("步骤5失败:解密应用私钥失败");
|
||||
LogUtils.e(TAG, "testStep5SubmitPublicKey:通过服务器公钥解密应用私钥失败");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "testStep5SubmitPublicKey:应用私钥解密成功,准备解密PONG数据");
|
||||
|
||||
// 3. 应用私钥解密PONG数据并解析
|
||||
String pongJson = AuthUtils.decryptByPrivateKey(encryptPong, privateKeyStr);
|
||||
AuthDataModel pongModel = AuthDataModel.parsePongJson(pongJson);
|
||||
if (pongModel != null && pongModel.isLoginFlag()) {
|
||||
// 保存登录态核心参数
|
||||
AuthUtils.setAppId(pongModel.getAppId());
|
||||
AuthUtils.setShortToken(pongModel.getShortToken());
|
||||
AuthUtils.setLoginStatus(true);
|
||||
LogUtils.d(TAG, "testStep5SubmitPublicKey:PONG数据解析成功,登录态参数已保存-appId=" + pongModel.getAppId());
|
||||
|
||||
// 临时存储明文私钥,用于后续心跳解密
|
||||
byte[] priBytes = Base64.getDecoder().decode(privateKeyStr);
|
||||
try {
|
||||
PrivateKey privateKey = java.security.KeyFactory.getInstance("RSA")
|
||||
.generatePrivate(new PKCS8EncodedKeySpec(priBytes));
|
||||
AuthUtils.setTempPrivateKey(privateKey);
|
||||
LogUtils.d(TAG, "testStep5SubmitPublicKey:明文私钥已临时存储,用于后续心跳流程");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "testStep5SubmitPublicKey:临时私钥存储失败", e);
|
||||
}
|
||||
|
||||
updateStatus("步骤5成功:公钥提交+登录鉴权完成,获取AppId:" + pongModel.getAppId());
|
||||
LogUtils.i(TAG, "testStep5SubmitPublicKey:步骤5执行成功,公钥提交与鉴权登录均完成");
|
||||
} else {
|
||||
updateStatus("步骤5失败:PONG数据解析失败,鉴权未通过");
|
||||
LogUtils.w(TAG, "testStep5SubmitPublicKey:步骤5执行失败,PONG数据解析失败或鉴权未通过");
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤6:测试心跳保活启动与运行
|
||||
*/
|
||||
private void testStep6Heartbeat() {
|
||||
LogUtils.d(TAG, "testStep6Heartbeat:开始执行步骤6-心跳保活测试");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 前置校验:是否已完成鉴权登录
|
||||
if (!AuthUtils.isLogin()) {
|
||||
updateStatus("步骤6失败:请先完成步骤5登录鉴权");
|
||||
LogUtils.w(TAG, "testStep6Heartbeat:前置校验失败,未完成登录鉴权(未执行步骤5)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化并启动心跳管理器
|
||||
HeartbeatManager.init(SERVER_PUBLIC_KEY);
|
||||
HeartbeatManager.startHeartbeat();
|
||||
updateStatus("步骤6成功:心跳保活已启动,每分钟发送1次PING");
|
||||
LogUtils.i(TAG, "testStep6Heartbeat:步骤6执行成功,心跳保活任务已启动,间隔1分钟");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// ===================== 全流程测试方法 =====================
|
||||
/**
|
||||
* 全流程自动化测试(按顺序执行所有步骤)
|
||||
*/
|
||||
private void testAllStep() {
|
||||
LogUtils.d(TAG, "testAllStep:全流程自动化测试启动,按步骤顺序执行");
|
||||
updateStatus("全流程测试开始,按步骤执行...");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 步骤1:邮箱与服务地址校验
|
||||
String email = etEmail.getText().toString().trim();
|
||||
String serverUrl = etServerUrl.getText().toString().trim();
|
||||
LogUtils.d(TAG, "testAllStep-步骤1:入参-email=" + email + ",serverUrl=" + serverUrl);
|
||||
if (email.isEmpty() || !email.contains("@") || serverUrl.isEmpty() || (!serverUrl.startsWith("http://") && !serverUrl.startsWith("https://"))) {
|
||||
updateStatus("全流程失败:步骤1邮箱/服务地址无效");
|
||||
LogUtils.w(TAG, "testAllStep-步骤1:执行失败,邮箱或服务地址格式无效");
|
||||
return;
|
||||
}
|
||||
ServerUtils.initServerUrl(serverUrl);
|
||||
updateStatus("全流程-步骤1:校验通过");
|
||||
LogUtils.d(TAG, "testAllStep-步骤1:执行成功");
|
||||
sleep(1000);
|
||||
|
||||
// 步骤2:发送验证码
|
||||
String sendResult = ServerUtils.sendVerifyCode(email);
|
||||
if (sendResult == null || !sendResult.contains("success")) {
|
||||
updateStatus("全流程失败:步骤2验证码发送失败");
|
||||
LogUtils.w(TAG, "testAllStep-步骤2:执行失败,验证码发送失败,服务返回=" + sendResult);
|
||||
return;
|
||||
}
|
||||
updateStatus("全流程-步骤2:验证码发送成功,请手动输入后继续");
|
||||
LogUtils.d(TAG, "testAllStep-步骤2:执行成功,等待用户输入验证码");
|
||||
sleep(3000);
|
||||
|
||||
// 步骤3:验证码校验(需用户提前输入)
|
||||
String code = etVerifyCode.getText().toString().trim();
|
||||
String verifyResult = ServerUtils.verifyCode(email, code);
|
||||
LogUtils.d(TAG, "testAllStep-步骤3:入参-code=" + code);
|
||||
if (verifyResult == null || !verifyResult.contains("success")) {
|
||||
updateStatus("全流程失败:步骤3验证码校验失败");
|
||||
LogUtils.w(TAG, "testAllStep-步骤3:执行失败,验证码校验失败,服务返回=" + verifyResult);
|
||||
return;
|
||||
}
|
||||
updateStatus("全流程-步骤3:验证码校验通过");
|
||||
LogUtils.d(TAG, "testAllStep-步骤3:执行成功");
|
||||
sleep(1000);
|
||||
|
||||
// 步骤4:NFC绑定与密钥存储
|
||||
if (!NFCUtils.isNfcEnabled(AuthTestActivity.this)) {
|
||||
updateStatus("全流程失败:步骤4请开启NFC功能");
|
||||
LogUtils.w(TAG, "testAllStep-步骤4:执行失败,NFC功能未开启");
|
||||
return;
|
||||
}
|
||||
KeyPair keyPair = AuthUtils.generateRsaKeyPair();
|
||||
if (keyPair == null) {
|
||||
updateStatus("全流程失败:步骤4密钥生成失败");
|
||||
LogUtils.w(TAG, "testAllStep-步骤4:执行失败,RSA密钥对生成失败");
|
||||
return;
|
||||
}
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
appPublicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
|
||||
String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKey.getEncoded());
|
||||
encryptPrivateKey = AuthUtils.encryptByPublicKey(privateKeyBase64, SERVER_PUBLIC_KEY);
|
||||
boolean saveSuccess = StorageUtils.saveEncryptPrivateKey(encryptPrivateKey);
|
||||
if (!saveSuccess) {
|
||||
updateStatus("全流程失败:步骤4本地存储失败");
|
||||
LogUtils.w(TAG, "testAllStep-步骤4:执行失败,加密私钥本地存储失败");
|
||||
return;
|
||||
}
|
||||
updateStatus("全流程-步骤4:NFC绑定(简化)+本地存储完成");
|
||||
LogUtils.d(TAG, "testAllStep-步骤4:执行成功(简化)");
|
||||
sleep(1000);
|
||||
|
||||
// 步骤5:提交公钥与鉴权登录
|
||||
String encryptPong = ServerUtils.submitAppPublicKey(email, appPublicKeyStr);
|
||||
String privateKeyStr = AuthUtils.decryptByPrivateKey(encryptPrivateKey, SERVER_PUBLIC_KEY);
|
||||
String pongJson = AuthUtils.decryptByPrivateKey(encryptPong, privateKeyStr);
|
||||
AuthDataModel pongModel = AuthDataModel.parsePongJson(pongJson);
|
||||
if (pongModel == null || !pongModel.isLoginFlag()) {
|
||||
updateStatus("全流程失败:步骤5鉴权失败");
|
||||
LogUtils.w(TAG, "testAllStep-步骤5:执行失败,鉴权未通过");
|
||||
return;
|
||||
}
|
||||
AuthUtils.setAppId(pongModel.getAppId());
|
||||
AuthUtils.setShortToken(pongModel.getShortToken());
|
||||
AuthUtils.setLoginStatus(true);
|
||||
// 临时存储私钥
|
||||
byte[] priBytes = Base64.getDecoder().decode(privateKeyStr);
|
||||
try {
|
||||
PrivateKey privateKeyTemp = java.security.KeyFactory.getInstance("RSA")
|
||||
.generatePrivate(new PKCS8EncodedKeySpec(priBytes));
|
||||
AuthUtils.setTempPrivateKey(privateKeyTemp);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "testAllStep-步骤5:临时私钥存储失败", e);
|
||||
}
|
||||
updateStatus("全流程-步骤5:鉴权成功,获取AppId:" + pongModel.getAppId());
|
||||
LogUtils.d(TAG, "testAllStep-步骤5:执行成功,鉴权登录完成");
|
||||
sleep(1000);
|
||||
|
||||
// 步骤6:启动心跳保活
|
||||
HeartbeatManager.init(SERVER_PUBLIC_KEY);
|
||||
HeartbeatManager.startHeartbeat();
|
||||
updateStatus("全流程测试完成:所有步骤执行成功,心跳已启动");
|
||||
LogUtils.i(TAG, "testAllStep:全流程自动化测试执行成功,所有步骤完成,心跳已启动");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// ===================== 辅助工具方法 =====================
|
||||
/**
|
||||
* 主线程更新测试状态(UI操作需主线程)
|
||||
*/
|
||||
private void updateStatus(final String status) {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tvTestStatus.append("[" + System.currentTimeMillis() + "] " + status + "\n");
|
||||
LogUtils.d(TAG, "测试状态更新:" + status);
|
||||
Toast.makeText(AuthTestActivity.this, status, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 线程休眠(简化流程等待,避免步骤执行过快)
|
||||
*/
|
||||
private void sleep(long millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
LogUtils.d(TAG, "线程休眠完成,休眠时长=" + millis + "ms");
|
||||
} catch (InterruptedException e) {
|
||||
LogUtils.w(TAG, "线程休眠被中断", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部启动当前测试Activity的静态入口方法
|
||||
*/
|
||||
public static void start(Context context) {
|
||||
LogUtils.d(TAG, "start:外部调用启动鉴权测试页面");
|
||||
Intent intent = new Intent(context, AuthTestActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package cc.winboll.manager;
|
||||
|
||||
package cc.winboll.studio.authcenterapp.manager;
|
||||
import cc.winboll.LogUtils;
|
||||
import cc.winboll.model.AuthDataModel;
|
||||
import cc.winboll.utils.AuthUtils;
|
||||
import cc.winboll.utils.ServerUtils;
|
||||
import cc.winboll.library.model.AuthDataModel;
|
||||
import cc.winboll.library.utils.AuthUtils;
|
||||
import cc.winboll.library.utils.ServerUtils;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll;
|
||||
package cc.winboll.studio.authcenterapp.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
92
authcenterapp/src/main/res/layout/activity_auth_test.xml
Normal file
92
authcenterapp/src/main/res/layout/activity_auth_test.xml
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<EditText
|
||||
android:id="@+id/et_test_email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="请输入登录邮箱"
|
||||
android:inputType="textEmailAddress"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_test_server_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="请输入服务地址(带http/https)"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_test_verify_code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="请输入6位验证码"
|
||||
android:inputType="number"
|
||||
android:maxLength="6"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- 分步测试按钮 -->
|
||||
<Button
|
||||
android:id="@+id/btn_step1_check_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="步骤1:校验邮箱+服务地址"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_step2_send_code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="步骤2:发送验证码"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_step3_verify_code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="步骤3:校验验证码"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_step4_nfc_bind"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="步骤4:NFC绑定+密钥存储"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_step5_submit_pubkey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="步骤5:提交公钥+鉴权登录"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_step6_heartbeat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="步骤6:启动心跳保活"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_step_all_test"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="全流程测试(自动执行所有步骤)"
|
||||
android:background="@android:color/holo_blue_dark"
|
||||
android:textColor="@android:color/white"
|
||||
android:layout_marginTop="8dp"/>
|
||||
|
||||
<!-- 状态显示区域 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_test_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textSize="14sp"
|
||||
android:scrollbars="vertical"
|
||||
android:maxLines="20"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<item
|
||||
android:id="@+id/item_ping_test"
|
||||
android:title="Ping Test"/>
|
||||
<item
|
||||
android:id="@+id/item_auth_test"
|
||||
android:title="Auth Test"/>
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
|
||||
@@ -21,5 +21,13 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// WinBoLL库 nexus.winboll.cc 地址
|
||||
api 'cc.winboll.studio:libaes:15.15.7'
|
||||
api 'cc.winboll.studio:libappbase:15.15.4'
|
||||
|
||||
// WinBoLL备用库 jitpack.io 地址
|
||||
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
|
||||
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.4'
|
||||
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Jan 15 02:32:25 GMT 2026
|
||||
#Thu Jan 15 03:05:40 GMT 2026
|
||||
stageCount=0
|
||||
libraryProject=library
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.0
|
||||
buildCount=47
|
||||
buildCount=50
|
||||
baseBetaVersion=15.0.1
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
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,简化分级调用+统一格式化输出
|
||||
@@ -14,78 +9,6 @@ import java.util.logging.Logger;
|
||||
* @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));
|
||||
}
|
||||
}
|
||||
public class LogUtils extends cc.winboll.studio.libappbase.LogUtils {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.model;
|
||||
package cc.winboll.library.model;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import cc.winboll.LogUtils;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.utils;
|
||||
package cc.winboll.library.utils;
|
||||
|
||||
import cc.winboll.LogUtils;
|
||||
import java.security.KeyPair;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.utils;
|
||||
package cc.winboll.library.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.nfc.FormatException;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.utils;
|
||||
package cc.winboll.library.utils;
|
||||
|
||||
import cc.winboll.LogUtils;
|
||||
import java.io.BufferedReader;
|
||||
Reference in New Issue
Block a user