附录A:完整代码清单
声明:本文中的公司名称、包名、API地址、密钥等均已脱敏处理,使用虚构名称替代。本文仅用于安全研究和技术教学目的。
脱敏说明:
- 公司名称:梦想世界(DreamWorld)
- 包名前缀:com.dreamworld.*
- API域名:api.dreamworld.com
- 请求头前缀:X-DW-*
- SO库名称:libSecurityCore.so
目录
1. 核心签名生成器
1.1 SecurityChainGenerator.java
这是整个项目的核心类,负责调用Native层生成签名。
package com.dreamworld.security;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 安全链签名生成器
*
* 核心功能:
* 1. 初始化Unidbg模拟器环境
* 2. 加载libSecurityCore.so
* 3. 调用Native方法生成签名
* 4. 管理模拟器生命周期
*
* @author Security Research Team
* @version 1.0.0
*/
public class SecurityChainGenerator extends AbstractJni implements Closeable {
// ==================== 常量定义 ====================
/** APK文件路径 */
private static final String APK_PATH = "data/dreamworld-app.apk";
/** SO库路径 */
private static final String SO_PATH = "data/libSecurityCore.so";
/** 目标SDK版本 */
private static final int TARGET_SDK = 23;
/** 进程名 */
private static final String PROCESS_NAME = "com.dreamworld.app";
// ==================== 实例变量 ====================
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass securityClass;
/** 缓存的签名结果 */
private final Map<String, CachedSignature> signatureCache;
/** 缓存过期时间(毫秒) */
private static final long CACHE_TTL = 60000;
// ==================== 构造函数 ====================
/**
* 创建签名生成器实例
*
* @throws IOException 如果初始化失败
*/
public SecurityChainGenerator() throws IOException {
this(false);
}
/**
* 创建签名生成器实例
*
* @param enableDebug 是否启用调试模式
* @throws IOException 如果初始化失败
*/
public SecurityChainGenerator(boolean enableDebug) throws IOException {
// 初始化缓存
this.signatureCache = new ConcurrentHashMap<>();
// 创建模拟器
this.emulator = createEmulator(enableDebug);
// 创建虚拟机
this.vm = createVM();
// 加载SO库
this.module = loadNativeLibrary();
// 获取安全类
this.securityClass = vm.resolveClass("com/dreamworld/security/SecurityCore");
// 调用JNI_OnLoad
callJniOnLoad();
}
// ==================== 初始化方法 ====================
/**
* 创建Android模拟器
*/
private AndroidEmulator createEmulator(boolean enableDebug) {
AndroidEmulatorBuilder builder = AndroidEmulatorBuilder
.for64Bit()
.setProcessName(PROCESS_NAME);
if (enableDebug) {
builder.addBackendFactory(new DynarmicFactory(true));
}
AndroidEmulator emu = builder.build();
// 配置内存
Memory memory = emu.getMemory();
memory.setLibraryResolver(new AndroidResolver(TARGET_SDK));
return emu;
}
/**
* 创建Dalvik虚拟机
*/
private VM createVM() {
VM dalvikVM = emulator.createDalvikVM(new File(APK_PATH));
dalvikVM.setJni(this);
dalvikVM.setVerbose(false);
return dalvikVM;
}
/**
* 加载Native库
*/
private Module loadNativeLibrary() {
DalvikModule dm = vm.loadLibrary(new File(SO_PATH), false);
return dm.getModule();
}
/**
* 调用JNI_OnLoad初始化
*/
private void callJniOnLoad() {
vm.callJNI_OnLoad(emulator, module);
}
// ==================== 核心签名方法 ====================
/**
* 生成安全链签名
*
* @param url 请求URL
* @param timestamp 时间戳
* @param nonce 随机数
* @param body 请求体(可为null)
* @return 签名结果
*/
public SignatureResult generateSignature(String url, long timestamp,
String nonce, String body) {
// 检查缓存
String cacheKey = buildCacheKey(url, timestamp, nonce, body);
CachedSignature cached = signatureCache.get(cacheKey);
if (cached != null && !cached.isExpired()) {
return cached.getSignature();
}
// 生成新签名
SignatureResult result = doGenerateSignature(url, timestamp, nonce, body);
// 存入缓存
signatureCache.put(cacheKey, new CachedSignature(result));
return result;
}
/**
* 实际执行签名生成
*/
private SignatureResult doGenerateSignature(String url, long timestamp,
String nonce, String body) {
try {
// 准备参数
StringObject urlObj = new StringObject(vm, url);
StringObject nonceObj = new StringObject(vm, nonce);
StringObject bodyObj = body != null ? new StringObject(vm, body) : null;
// 调用Native方法
DvmObject<?> result = securityClass.callStaticJniMethodObject(
emulator,
"generateSecurityChain(Ljava/lang/String;JLjava/lang/String;" +
"Ljava/lang/String;)Lcom/dreamworld/security/SecurityResult;",
urlObj, timestamp, nonceObj, bodyObj
);
// 解析结果
return parseResult(result);
} catch (Exception e) {
throw new SignatureGenerationException("签名生成失败", e);
}
}
/**
* 解析Native返回的结果对象
*/
private SignatureResult parseResult(DvmObject<?> result) {
if (result == null) {
throw new SignatureGenerationException("Native返回null");
}
// 获取各个字段
String signature = getStringField(result, "signature");
String deviceId = getStringField(result, "deviceId");
String sessionToken = getStringField(result, "sessionToken");
long expireTime = getLongField(result, "expireTime");
return new SignatureResult(signature, deviceId, sessionToken, expireTime);
}
/**
* 获取对象的字符串字段
*/
private String getStringField(DvmObject<?> obj, String fieldName) {
DvmObject<?> field = obj.getObjectValue(fieldName);
return field != null ? field.getValue().toString() : null;
}
/**
* 获取对象的长整型字段
*/
private long getLongField(DvmObject<?> obj, String fieldName) {
Number value = obj.getIntValue(fieldName);
return value != null ? value.longValue() : 0L;
}
// ==================== JNI回调处理 ====================
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass,
String signature, VaList vaList) {
switch (signature) {
case "android/os/Build->BRAND:Ljava/lang/String;":
return new StringObject(vm, "DreamWorld");
case "android/os/Build->MODEL:Ljava/lang/String;":
return new StringObject(vm, "DW-X1");
case "android/os/Build->DEVICE:Ljava/lang/String;":
return new StringObject(vm, "dreamworld_x1");
case "android/os/Build->FINGERPRINT:Ljava/lang/String;":
return new StringObject(vm,
"DreamWorld/DW-X1/dreamworld_x1:12/SKQ1.211006.001/" +
"V14.0.1.0.SKBCNXM:user/release-keys");
case "android/provider/Settings$Secure->getString" +
"(Landroid/content/ContentResolver;Ljava/lang/String;)" +
"Ljava/lang/String;":
String key = vaList.getObjectArg(1).getValue().toString();
return handleSecureSettings(key);
default:
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
}
/**
* 处理Settings.Secure查询
*/
private DvmObject<?> handleSecureSettings(String key) {
switch (key) {
case "android_id":
return new StringObject(vm, generateAndroidId());
default:
return null;
}
}
/**
* 生成稳定的Android ID
*/
private String generateAndroidId() {
try {
String seed = "dreamworld_device_" + System.getProperty("user.name");
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(seed.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash).substring(0, 16);
} catch (Exception e) {
return "a1b2c3d4e5f67890";
}
}
@Override
public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass,
String signature, VaList vaList) {
switch (signature) {
case "android/os/Build$VERSION->SDK_INT:I":
return TARGET_SDK;
case "android/os/Process->myPid()I":
return 12345;
case "android/os/Process->myUid()I":
return 10086;
default:
return super.callStaticIntMethodV(vm, dvmClass, signature, vaList);
}
}
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject,
String signature, VaList vaList) {
switch (signature) {
case "android/content/Context->getPackageName()Ljava/lang/String;":
return new StringObject(vm, PROCESS_NAME);
case "android/content/Context->getFilesDir()Ljava/io/File;":
return vm.resolveClass("java/io/File")
.newObject(new File("/data/data/" + PROCESS_NAME + "/files"));
case "android/content/pm/PackageManager->getPackageInfo" +
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;":
return createPackageInfo();
case "android/content/pm/Signature->toByteArray()[B":
return createFakeSignature();
default:
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}
}
/**
* 创建PackageInfo对象
*/
private DvmObject<?> createPackageInfo() {
DvmClass packageInfoClass = vm.resolveClass("android/content/pm/PackageInfo");
DvmObject<?> packageInfo = packageInfoClass.newObject(null);
// 设置版本信息
packageInfo.setIntValue("versionCode", 8170);
packageInfo.setObjectValue("versionName", new StringObject(vm, "8.x.x"));
// 设置签名数组
DvmClass signatureClass = vm.resolveClass("android/content/pm/Signature");
ArrayObject signatures = new ArrayObject(signatureClass.newObject(null));
packageInfo.setObjectValue("signatures", signatures);
return packageInfo;
}
/**
* 创建伪造的APK签名
*/
private DvmObject<?> createFakeSignature() {
// 返回一个固定的签名字节数组
byte[] fakeSignature = new byte[]{
0x30, (byte)0x82, 0x02, (byte)0xA1, 0x30, (byte)0x82, 0x01,
(byte)0x89, (byte)0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x04
// ... 省略其余字节
};
return new ByteArray(vm, fakeSignature);
}
// ==================== 缓存管理 ====================
/**
* 构建缓存键
*/
private String buildCacheKey(String url, long timestamp, String nonce, String body) {
StringBuilder sb = new StringBuilder();
sb.append(url).append("|");
sb.append(timestamp).append("|");
sb.append(nonce).append("|");
sb.append(body != null ? body.hashCode() : "null");
return sb.toString();
}
/**
* 清理过期缓存
*/
public void cleanExpiredCache() {
signatureCache.entrySet().removeIf(entry -> entry.getValue().isExpired());
}
/**
* 清空所有缓存
*/
public void clearCache() {
signatureCache.clear();
}
// ==================== 工具方法 ====================
/**
* 字节数组转十六进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
// ==================== 资源管理 ====================
@Override
public void close() {
if (emulator != null) {
emulator.close();
}
signatureCache.clear();
}
// ==================== 内部类 ====================
/**
* 缓存的签名
*/
private static class CachedSignature {
private final SignatureResult signature;
private final long createTime;
public CachedSignature(SignatureResult signature) {
this.signature = signature;
this.createTime = System.currentTimeMillis();
}
public boolean isExpired() {
return System.currentTimeMillis() - createTime > CACHE_TTL;
}
public SignatureResult getSignature() {
return signature;
}
}
}
1.2 SignatureResult.java
签名结果数据类。
package com.dreamworld.security;
import java.io.Serializable;
/**
* 签名结果
*/
public class SignatureResult implements Serializable {
private static final long serialVersionUID = 1L;
private final String signature;
private final String deviceId;
private final String sessionToken;
private final long expireTime;
private final long createTime;
public SignatureResult(String signature, String deviceId,
String sessionToken, long expireTime) {
this.signature = signature;
this.deviceId = deviceId;
this.sessionToken = sessionToken;
this.expireTime = expireTime;
this.createTime = System.currentTimeMillis();
}
public String getSignature() {
return signature;
}
public String getDeviceId() {
return deviceId;
}
public String getSessionToken() {
return sessionToken;
}
public long getExpireTime() {
return expireTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
public long getAge() {
return System.currentTimeMillis() - createTime;
}
@Override
public String toString() {
return String.format(
"SignatureResult{signature='%s...', deviceId='%s', expired=%s}",
signature.substring(0, Math.min(16, signature.length())),
deviceId,
isExpired()
);
}
}
2. Unidbg模拟器封装
2.1 EmulatorPool.java
模拟器对象池,用于高并发场景。
package com.dreamworld.security.pool;
import com.dreamworld.security.SecurityChainGenerator;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import java.time.Duration;
/**
* 模拟器对象池
*
* 使用Apache Commons Pool2实现对象池化,
* 避免频繁创建/销毁模拟器实例带来的性能开销。
*/
public class EmulatorPool implements AutoCloseable {
private final GenericObjectPool<SecurityChainGenerator> pool;
/**
* 创建默认配置的对象池
*/
public EmulatorPool() {
this(createDefaultConfig());
}
/**
* 创建自定义配置的对象池
*/
public EmulatorPool(GenericObjectPoolConfig<SecurityChainGenerator> config) {
this.pool = new GenericObjectPool<>(new EmulatorFactory(), config);
}
/**
* 创建默认配置
*/
private static GenericObjectPoolConfig<SecurityChainGenerator> createDefaultConfig() {
GenericObjectPoolConfig<SecurityChainGenerator> config =
new GenericObjectPoolConfig<>();
// 池大小配置
config.setMinIdle(2);
config.setMaxIdle(8);
config.setMaxTotal(16);
// 等待配置
config.setMaxWait(Duration.ofSeconds(30));
config.setBlockWhenExhausted(true);
// 驱逐配置
config.setTimeBetweenEvictionRuns(Duration.ofMinutes(5));
config.setMinEvictableIdleTime(Duration.ofMinutes(30));
// 验证配置
config.setTestOnBorrow(true);
config.setTestOnReturn(false);
config.setTestWhileIdle(true);
return config;
}
/**
* 借用模拟器实例
*/
public SecurityChainGenerator borrow() throws Exception {
return pool.borrowObject();
}
/**
* 归还模拟器实例
*/
public void returnObject(SecurityChainGenerator generator) {
if (generator != null) {
pool.returnObject(generator);
}
}
/**
* 使模拟器实例失效
*/
public void invalidate(SecurityChainGenerator generator) throws Exception {
if (generator != null) {
pool.invalidateObject(generator);
}
}
/**
* 获取池状态
*/
public PoolStats getStats() {
return new PoolStats(
pool.getNumActive(),
pool.getNumIdle(),
pool.getNumWaiters(),
pool.getBorrowedCount(),
pool.getReturnedCount(),
pool.getCreatedCount(),
pool.getDestroyedCount()
);
}
@Override
public void close() {
pool.close();
}
/**
* 模拟器工厂
*/
private static class EmulatorFactory
extends BasePooledObjectFactory<SecurityChainGenerator> {
@Override
public SecurityChainGenerator create() throws Exception {
return new SecurityChainGenerator();
}
@Override
public PooledObject<SecurityChainGenerator> wrap(SecurityChainGenerator obj) {
return new DefaultPooledObject<>(obj);
}
@Override
public void destroyObject(PooledObject<SecurityChainGenerator> p) {
p.getObject().close();
}
@Override
public boolean validateObject(PooledObject<SecurityChainGenerator> p) {
try {
// 简单验证:尝试生成一个签名
SecurityChainGenerator gen = p.getObject();
gen.generateSignature("/test", System.currentTimeMillis(),
"test", null);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public void passivateObject(PooledObject<SecurityChainGenerator> p) {
// 归还时清理缓存
p.getObject().cleanExpiredCache();
}
}
/**
* 池状态统计
*/
public static class PoolStats {
public final int active;
public final int idle;
public final int waiters;
public final long borrowed;
public final long returned;
public final long created;
public final long destroyed;
public PoolStats(int active, int idle, int waiters, long borrowed,
long returned, long created, long destroyed) {
this.active = active;
this.idle = idle;
this.waiters = waiters;
this.borrowed = borrowed;
this.returned = returned;
this.created = created;
this.destroyed = destroyed;
}
@Override
public String toString() {
return String.format(
"PoolStats{active=%d, idle=%d, waiters=%d, " +
"borrowed=%d, returned=%d, created=%d, destroyed=%d}",
active, idle, waiters, borrowed, returned, created, destroyed
);
}
}
}
2.2 SignatureService.java
签名服务,封装对象池的使用。
package com.dreamworld.security.service;
import com.dreamworld.security.SecurityChainGenerator;
import com.dreamworld.security.SignatureResult;
import com.dreamworld.security.pool.EmulatorPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
/**
* 签名服务
*
* 提供线程安全的签名生成服务,
* 内部使用对象池管理模拟器实例。
*/
@Service
public class SignatureService {
private static final Logger logger = LoggerFactory.getLogger(SignatureService.class);
private EmulatorPool pool;
private final AtomicLong requestCount = new AtomicLong(0);
private final AtomicLong successCount = new AtomicLong(0);
private final AtomicLong failureCount = new AtomicLong(0);
private final AtomicLong totalTime = new AtomicLong(0);
@PostConstruct
public void init() {
logger.info("初始化签名服务...");
this.pool = new EmulatorPool();
logger.info("签名服务初始化完成");
}
@PreDestroy
public void destroy() {
logger.info("关闭签名服务...");
if (pool != null) {
pool.close();
}
logger.info("签名服务已关闭");
}
/**
* 生成签名
*
* @param url 请求URL
* @param body 请求体(可为null)
* @return 签名结果
*/
public SignatureResult generateSignature(String url, String body) {
long startTime = System.currentTimeMillis();
requestCount.incrementAndGet();
SecurityChainGenerator generator = null;
boolean success = false;
try {
// 从池中借用
generator = pool.borrow();
// 生成签名参数
long timestamp = System.currentTimeMillis();
String nonce = generateNonce();
// 调用签名生成
SignatureResult result = generator.generateSignature(
url, timestamp, nonce, body
);
success = true;
successCount.incrementAndGet();
return result;
} catch (Exception e) {
failureCount.incrementAndGet();
logger.error("签名生成失败: url={}", url, e);
// 使实例失效
if (generator != null) {
try {
pool.invalidate(generator);
generator = null;
} catch (Exception ex) {
logger.warn("使实例失效时出错", ex);
}
}
throw new SignatureServiceException("签名生成失败", e);
} finally {
// 归还实例
if (generator != null) {
pool.returnObject(generator);
}
// 记录耗时
long elapsed = System.currentTimeMillis() - startTime;
totalTime.addAndGet(elapsed);
if (elapsed > 1000) {
logger.warn("签名生成耗时过长: {}ms, url={}", elapsed, url);
}
}
}
/**
* 生成随机数
*/
private String generateNonce() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 获取服务统计信息
*/
public ServiceStats getStats() {
long requests = requestCount.get();
long successes = successCount.get();
long failures = failureCount.get();
long time = totalTime.get();
return new ServiceStats(
requests,
successes,
failures,
requests > 0 ? (double) time / requests : 0,
requests > 0 ? (double) successes / requests * 100 : 0,
pool.getStats()
);
}
/**
* 服务统计
*/
public static class ServiceStats {
public final long totalRequests;
public final long successRequests;
public final long failedRequests;
public final double avgResponseTime;
public final double successRate;
public final EmulatorPool.PoolStats poolStats;
public ServiceStats(long totalRequests, long successRequests,
long failedRequests, double avgResponseTime,
double successRate, EmulatorPool.PoolStats poolStats) {
this.totalRequests = totalRequests;
this.successRequests = successRequests;
this.failedRequests = failedRequests;
this.avgResponseTime = avgResponseTime;
this.successRate = successRate;
this.poolStats = poolStats;
}
}
}
3. HTTP客户端封装
3.1 DreamWorldApiClient.java
封装了签名逻辑的API客户端。
package com.dreamworld.client;
import com.dreamworld.security.SignatureResult;
import com.dreamworld.security.service.SignatureService;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 梦想世界API客户端
*
* 自动处理签名、重试、错误处理等逻辑。
*/
public class DreamWorldApiClient {
private static final Logger logger = LoggerFactory.getLogger(DreamWorldApiClient.class);
private static final String BASE_URL = "https://api.dreamworld.com";
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private final OkHttpClient httpClient;
private final SignatureService signatureService;
private final ObjectMapper objectMapper;
// 设备信息
private final String deviceId;
private final String appVersion;
private final String osVersion;
public DreamWorldApiClient(SignatureService signatureService) {
this.signatureService = signatureService;
this.objectMapper = new ObjectMapper();
// 配置HTTP客户端
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(new LoggingInterceptor())
.addInterceptor(new RetryInterceptor(3))
.build();
// 设备信息
this.deviceId = "DW" + System.currentTimeMillis();
this.appVersion = "8.x.x";
this.osVersion = "Android 12";
}
/**
* 发送GET请求
*/
public <T> T get(String path, Class<T> responseType) throws IOException {
return get(path, null, responseType);
}
/**
* 发送带参数的GET请求
*/
public <T> T get(String path, Map<String, String> params,
Class<T> responseType) throws IOException {
// 构建URL
HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL + path).newBuilder();
if (params != null) {
params.forEach(urlBuilder::addQueryParameter);
}
String url = urlBuilder.build().toString();
// 生成签名
SignatureResult signature = signatureService.generateSignature(url, null);
// 构建请求
Request request = new Request.Builder()
.url(url)
.headers(buildHeaders(signature))
.get()
.build();
// 执行请求
return executeRequest(request, responseType);
}
/**
* 发送POST请求
*/
public <T> T post(String path, Object body, Class<T> responseType) throws IOException {
String url = BASE_URL + path;
String bodyJson = objectMapper.writeValueAsString(body);
// 生成签名
SignatureResult signature = signatureService.generateSignature(url, bodyJson);
// 构建请求
Request request = new Request.Builder()
.url(url)
.headers(buildHeaders(signature))
.post(RequestBody.create(bodyJson, JSON))
.build();
// 执行请求
return executeRequest(request, responseType);
}
/**
* 构建请求头
*/
private Headers buildHeaders(SignatureResult signature) {
return new Headers.Builder()
// 签名相关
.add("X-DW-Signature", signature.getSignature())
.add("X-DW-DeviceId", signature.getDeviceId())
.add("X-DW-SessionToken", signature.getSessionToken())
.add("X-DW-Timestamp", String.valueOf(System.currentTimeMillis()))
.add("X-DW-Nonce", generateNonce())
// 设备信息
.add("X-DW-AppVersion", appVersion)
.add("X-DW-OSVersion", osVersion)
.add("X-DW-Platform", "Android")
// 通用头
.add("Content-Type", "application/json")
.add("Accept", "application/json")
.add("User-Agent", "DreamWorld/" + appVersion + " Android")
.build();
}
/**
* 执行请求
*/
private <T> T executeRequest(Request request, Class<T> responseType)
throws IOException {
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
handleErrorResponse(response);
}
String body = response.body().string();
return objectMapper.readValue(body, responseType);
}
}
/**
* 处理错误响应
*/
private void handleErrorResponse(Response response) throws IOException {
int code = response.code();
String body = response.body() != null ? response.body().string() : "";
switch (code) {
case 401:
throw new AuthenticationException("认证失败: " + body);
case 403:
throw new ForbiddenException("访问被拒绝: " + body);
case 429:
throw new RateLimitException("请求过于频繁: " + body);
case 500:
case 502:
case 503:
throw new ServerException("服务器错误: " + code);
default:
throw new ApiException("API错误: " + code + " - " + body);
}
}
private String generateNonce() {
return java.util.UUID.randomUUID().toString().replace("-", "");
}
}
3.2 RetryInterceptor.java
自动重试拦截器。
package com.dreamworld.client.interceptor;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Set;
/**
* 重试拦截器
*
* 对于可重试的错误自动进行重试。
*/
public class RetryInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(RetryInterceptor.class);
/** 可重试的HTTP状态码 */
private static final Set<Integer> RETRYABLE_CODES = Set.of(408, 429, 500, 502, 503, 504);
private final int maxRetries;
private final long baseDelayMs;
public RetryInterceptor(int maxRetries) {
this(maxRetries, 1000);
}
public RetryInterceptor(int maxRetries, long baseDelayMs) {
this.maxRetries = maxRetries;
this.baseDelayMs = baseDelayMs;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException lastException = null;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
// 关闭之前的响应
if (response != null) {
response.close();
}
// 执行请求
response = chain.proceed(request);
// 检查是否需要重试
if (response.isSuccessful() || !shouldRetry(response.code())) {
return response;
}
logger.warn("请求失败,准备重试: attempt={}, code={}, url={}",
attempt + 1, response.code(), request.url());
} catch (IOException e) {
lastException = e;
logger.warn("请求异常,准备重试: attempt={}, error={}, url={}",
attempt + 1, e.getMessage(), request.url());
}
// 最后一次尝试不需要等待
if (attempt < maxRetries) {
sleep(calculateDelay(attempt));
}
}
// 所有重试都失败
if (lastException != null) {
throw lastException;
}
return response;
}
/**
* 判断是否应该重试
*/
private boolean shouldRetry(int code) {
return RETRYABLE_CODES.contains(code);
}
/**
* 计算重试延迟(指数退避)
*/
private long calculateDelay(int attempt) {
// 指数退避 + 随机抖动
long delay = baseDelayMs * (1L << attempt);
long jitter = (long) (delay * 0.2 * Math.random());
return Math.min(delay + jitter, 30000); // 最大30秒
}
private void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
4. 数据抓取服务
4.1 ProductCrawlerService.java
商品数据抓取服务。
package com.dreamworld.crawler;
import com.dreamworld.client.DreamWorldApiClient;
import com.dreamworld.crawler.model.*;
import com.dreamworld.crawler.storage.DataStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 商品抓取服务
*
* 负责定时抓取商品数据并存储。
*/
@Service
public class ProductCrawlerService {
private static final Logger logger = LoggerFactory.getLogger(ProductCrawlerService.class);
private final DreamWorldApiClient apiClient;
private final DataStorage storage;
private final ExecutorService executor;
// 抓取配置
private static final int PAGE_SIZE = 20;
private static final int MAX_PAGES = 100;
private static final int CONCURRENT_REQUESTS = 5;
private static final long REQUEST_INTERVAL_MS = 500;
// 统计信息
private final AtomicInteger totalCrawled = new AtomicInteger(0);
private final AtomicInteger totalFailed = new AtomicInteger(0);
public ProductCrawlerService(DreamWorldApiClient apiClient, DataStorage storage) {
this.apiClient = apiClient;
this.storage = storage;
this.executor = Executors.newFixedThreadPool(CONCURRENT_REQUESTS);
}
/**
* 定时抓取任务 - 每小时执行
*/
@Scheduled(cron = "0 0 * * * *")
public void scheduledCrawl() {
logger.info("开始定时抓取任务...");
try {
crawlAllProducts();
} catch (Exception e) {
logger.error("定时抓取任务失败", e);
}
}
/**
* 抓取所有商品
*/
public CrawlResult crawlAllProducts() {
long startTime = System.currentTimeMillis();
List<Product> allProducts = new ArrayList<>();
int failedPages = 0;
try {
// 获取分类列表
List<Category> categories = fetchCategories();
logger.info("获取到 {} 个分类", categories.size());
// 并发抓取各分类
List<Future<List<Product>>> futures = new ArrayList<>();
for (Category category : categories) {
futures.add(executor.submit(() -> crawlCategory(category)));
}
// 收集结果
for (Future<List<Product>> future : futures) {
try {
allProducts.addAll(future.get(5, TimeUnit.MINUTES));
} catch (Exception e) {
failedPages++;
logger.error("抓取分类失败", e);
}
}
// 存储数据
storage.saveProducts(allProducts);
long elapsed = System.currentTimeMillis() - startTime;
logger.info("抓取完成: 商品数={}, 耗时={}ms", allProducts.size(), elapsed);
return new CrawlResult(allProducts.size(), failedPages, elapsed);
} catch (Exception e) {
logger.error("抓取失败", e);
throw new CrawlerException("抓取失败", e);
}
}
/**
* 抓取单个分类的商品
*/
private List<Product> crawlCategory(Category category) {
List<Product> products = new ArrayList<>();
int page = 1;
while (page <= MAX_PAGES) {
try {
// 请求间隔
Thread.sleep(REQUEST_INTERVAL_MS);
// 获取商品列表
ProductListResponse response = apiClient.get(
"/api/v1/products",
Map.of(
"categoryId", category.getId(),
"page", String.valueOf(page),
"pageSize", String.valueOf(PAGE_SIZE)
),
ProductListResponse.class
);
if (response.getProducts().isEmpty()) {
break;
}
products.addAll(response.getProducts());
totalCrawled.addAndGet(response.getProducts().size());
logger.debug("抓取分类 {} 第 {} 页,获取 {} 条",
category.getName(), page, response.getProducts().size());
// 检查是否还有更多
if (!response.hasMore()) {
break;
}
page++;
} catch (Exception e) {
totalFailed.incrementAndGet();
logger.error("抓取分类 {} 第 {} 页失败", category.getName(), page, e);
break;
}
}
return products;
}
/**
* 获取分类列表
*/
private List<Category> fetchCategories() throws Exception {
CategoryListResponse response = apiClient.get(
"/api/v1/categories",
CategoryListResponse.class
);
return response.getCategories();
}
/**
* 获取统计信息
*/
public CrawlerStats getStats() {
return new CrawlerStats(
totalCrawled.get(),
totalFailed.get(),
storage.getProductCount()
);
}
/**
* 抓取结果
*/
public static class CrawlResult {
public final int productCount;
public final int failedPages;
public final long elapsedMs;
public CrawlResult(int productCount, int failedPages, long elapsedMs) {
this.productCount = productCount;
this.failedPages = failedPages;
this.elapsedMs = elapsedMs;
}
}
/**
* 抓取统计
*/
public static class CrawlerStats {
public final int totalCrawled;
public final int totalFailed;
public final int storedProducts;
public CrawlerStats(int totalCrawled, int totalFailed, int storedProducts) {
this.totalCrawled = totalCrawled;
this.totalFailed = totalFailed;
this.storedProducts = storedProducts;
}
}
}
5. 配置管理
5.1 application.yml
Spring Boot配置文件。
# 应用配置
spring:
application:
name: dreamworld-crawler
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/dreamworld?useSSL=false&serverTimezone=UTC
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:password}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 300000
max-lifetime: 1200000
connection-timeout: 30000
# Redis配置
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
database: 0
lettuce:
pool:
min-idle: 2
max-idle: 8
max-active: 16
# 服务器配置
server:
port: 8080
servlet:
context-path: /api
# 签名服务配置
signature:
pool:
min-idle: 2
max-idle: 8
max-total: 16
max-wait-seconds: 30
cache:
ttl-seconds: 60
max-size: 10000
# 抓取配置
crawler:
enabled: true
page-size: 20
max-pages: 100
concurrent-requests: 5
request-interval-ms: 500
schedule:
enabled: true
cron: "0 0 * * * *"
# 监控配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
# 日志配置
logging:
level:
root: INFO
com.dreamworld: DEBUG
com.github.unidbg: WARN
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/dreamworld-crawler.log
max-size: 100MB
max-history: 30
5.2 pom.xml
Maven项目配置。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dreamworld</groupId>
<artifactId>dreamworld-crawler</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>DreamWorld Crawler</name>
<description>梦想世界数据抓取服务</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<java.version>11</java.version>
<unidbg.version>0.9.7</unidbg.version>
<okhttp.version>4.12.0</okhttp.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Unidbg -->
<dependency>
<groupId>com.github.zhkl0228</groupId>
<artifactId>unidbg-android</artifactId>
<version>${unidbg.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- Object Pool -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Monitoring -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- Utils -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
</project>
6. 工具类集合
6.1 CryptoUtils.java
加密工具类。
package com.dreamworld.util;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
/**
* 加密工具类
*/
public final class CryptoUtils {
private CryptoUtils() {}
/**
* MD5哈希
*/
public static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
} catch (Exception e) {
throw new RuntimeException("MD5计算失败", e);
}
}
/**
* SHA256哈希
*/
public static String sha256(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
} catch (Exception e) {
throw new RuntimeException("SHA256计算失败", e);
}
}
/**
* HMAC-SHA256
*/
public static String hmacSha256(String data, String key) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKey);
byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
} catch (Exception e) {
throw new RuntimeException("HMAC-SHA256计算失败", e);
}
}
/**
* AES加密
*/
public static String aesEncrypt(String data, String key, String iv) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(
key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(
iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("AES加密失败", e);
}
}
/**
* AES解密
*/
public static String aesDecrypt(String encryptedData, String key, String iv) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(
key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(
iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES解密失败", e);
}
}
/**
* 字节数组转十六进制
*/
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* 十六进制转字节数组
*/
public static byte[] hexToBytes(String hex) {
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return data;
}
}
6.2 DeviceUtils.java
设备信息工具类。
package com.dreamworld.util;
import java.net.NetworkInterface;
import java.security.MessageDigest;
import java.util.Enumeration;
import java.util.UUID;
/**
* 设备信息工具类
*/
public final class DeviceUtils {
private DeviceUtils() {}
private static String cachedDeviceId;
/**
* 获取设备ID
*/
public static synchronized String getDeviceId() {
if (cachedDeviceId == null) {
cachedDeviceId = generateDeviceId();
}
return cachedDeviceId;
}
/**
* 生成设备ID
*/
private static String generateDeviceId() {
try {
// 尝试使用MAC地址
String macAddress = getMacAddress();
if (macAddress != null) {
return md5(macAddress).substring(0, 16);
}
// 回退到随机UUID
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
} catch (Exception e) {
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
}
/**
* 获取MAC地址
*/
private static String getMacAddress() {
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
byte[] mac = ni.getHardwareAddress();
if (mac != null && mac.length > 0) {
StringBuilder sb = new StringBuilder();
for (byte b : mac) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
}
} catch (Exception e) {
// 忽略
}
return null;
}
/**
* 生成随机IMEI
*/
public static String generateImei() {
StringBuilder sb = new StringBuilder();
sb.append("86"); // 中国TAC
for (int i = 0; i < 12; i++) {
sb.append((int) (Math.random() * 10));
}
// 计算校验位
sb.append(calculateLuhnCheckDigit(sb.toString()));
return sb.toString();
}
/**
* Luhn校验位计算
*/
private static int calculateLuhnCheckDigit(String number) {
int sum = 0;
boolean alternate = true;
for (int i = number.length() - 1; i >= 0; i--) {
int n = Character.getNumericValue(number.charAt(i));
if (alternate) {
n *= 2;
if (n > 9) {
n = (n % 10) + 1;
}
}
sum += n;
alternate = !alternate;
}
return (10 - (sum % 10)) % 10;
}
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(input.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
7. 测试用例
7.1 SecurityChainGeneratorTest.java
核心签名生成器测试。
package com.dreamworld.security;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* 签名生成器测试
*/
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class SecurityChainGeneratorTest {
private static SecurityChainGenerator generator;
@BeforeAll
static void setUp() throws Exception {
generator = new SecurityChainGenerator();
}
@AfterAll
static void tearDown() {
if (generator != null) {
generator.close();
}
}
@Test
@Order(1)
@DisplayName("测试基本签名生成")
void testBasicSignature() {
// Given
String url = "/api/v1/products";
long timestamp = System.currentTimeMillis();
String nonce = "abc123";
// When
SignatureResult result = generator.generateSignature(url, timestamp, nonce, null);
// Then
assertNotNull(result);
assertNotNull(result.getSignature());
assertFalse(result.getSignature().isEmpty());
assertNotNull(result.getDeviceId());
}
@Test
@Order(2)
@DisplayName("测试带请求体的签名")
void testSignatureWithBody() {
// Given
String url = "/api/v1/orders";
long timestamp = System.currentTimeMillis();
String nonce = "def456";
String body = "{"productId":"12345","quantity":1}";
// When
SignatureResult result = generator.generateSignature(url, timestamp, nonce, body);
// Then
assertNotNull(result);
assertNotNull(result.getSignature());
}
@Test
@Order(3)
@DisplayName("测试签名缓存")
void testSignatureCache() {
// Given
String url = "/api/v1/test";
long timestamp = System.currentTimeMillis();
String nonce = "cache_test";
// When - 第一次调用
long start1 = System.nanoTime();
SignatureResult result1 = generator.generateSignature(url, timestamp, nonce, null);
long time1 = System.nanoTime() - start1;
// When - 第二次调用(应该命中缓存)
long start2 = System.nanoTime();
SignatureResult result2 = generator.generateSignature(url, timestamp, nonce, null);
long time2 = System.nanoTime() - start2;
// Then
assertEquals(result1.getSignature(), result2.getSignature());
assertTrue(time2 < time1 / 10, "缓存命中应该快10倍以上");
}
@Test
@Order(4)
@DisplayName("测试不同参数生成不同签名")
void testDifferentParams() {
// Given
long timestamp = System.currentTimeMillis();
// When
SignatureResult result1 = generator.generateSignature(
"/api/v1/a", timestamp, "nonce1", null);
SignatureResult result2 = generator.generateSignature(
"/api/v1/b", timestamp, "nonce2", null);
// Then
assertNotEquals(result1.getSignature(), result2.getSignature());
}
@Test
@Order(5)
@DisplayName("测试并发签名生成")
void testConcurrentSignature() throws Exception {
int threadCount = 10;
java.util.concurrent.CountDownLatch latch =
new java.util.concurrent.CountDownLatch(threadCount);
java.util.concurrent.atomic.AtomicInteger successCount =
new java.util.concurrent.atomic.AtomicInteger(0);
for (int i = 0; i < threadCount; i++) {
final int index = i;
new Thread(() -> {
try {
SignatureResult result = generator.generateSignature(
"/api/v1/concurrent/" + index,
System.currentTimeMillis(),
"nonce_" + index,
null
);
if (result != null && result.getSignature() != null) {
successCount.incrementAndGet();
}
} finally {
latch.countDown();
}
}).start();
}
latch.await();
assertEquals(threadCount, successCount.get(), "所有并发请求都应该成功");
}
}
7.2 EmulatorPoolTest.java
对象池测试。
package com.dreamworld.security.pool;
import com.dreamworld.security.SecurityChainGenerator;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* 对象池测试
*/
class EmulatorPoolTest {
private EmulatorPool pool;
@BeforeEach
void setUp() {
pool = new EmulatorPool();
}
@AfterEach
void tearDown() {
if (pool != null) {
pool.close();
}
}
@Test
@DisplayName("测试借用和归还")
void testBorrowAndReturn() throws Exception {
// When
SecurityChainGenerator gen = pool.borrow();
// Then
assertNotNull(gen);
// 归还
pool.returnObject(gen);
// 验证池状态
EmulatorPool.PoolStats stats = pool.getStats();
assertEquals(0, stats.active);
assertTrue(stats.idle > 0);
}
@Test
@DisplayName("测试对象复用")
void testObjectReuse() throws Exception {
// 借用并归还
SecurityChainGenerator gen1 = pool.borrow();
pool.returnObject(gen1);
// 再次借用
SecurityChainGenerator gen2 = pool.borrow();
// 应该是同一个对象
assertSame(gen1, gen2);
pool.returnObject(gen2);
}
@Test
@DisplayName("测试并发借用")
void testConcurrentBorrow() throws Exception {
int threadCount = 5;
java.util.concurrent.CountDownLatch latch =
new java.util.concurrent.CountDownLatch(threadCount);
java.util.concurrent.atomic.AtomicInteger successCount =
new java.util.concurrent.atomic.AtomicInteger(0);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
SecurityChainGenerator gen = null;
try {
gen = pool.borrow();
// 模拟使用
Thread.sleep(100);
successCount.incrementAndGet();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (gen != null) {
pool.returnObject(gen);
}
latch.countDown();
}
}).start();
}
latch.await();
assertEquals(threadCount, successCount.get());
}
}
代码使用说明
快速开始
- 克隆项目并安装依赖:
git clone https://github.com/example/dreamworld-crawler.git
cd dreamworld-crawler
mvn clean install
- 准备必要文件:
# 将APK和SO文件放到data目录
mkdir -p data
cp /path/to/dreamworld-app.apk data/
cp /path/to/libSecurityCore.so data/
- 运行测试:
mvn test
- 启动服务:
mvn spring-boot:run
注意事项
-
内存配置:Unidbg需要较大内存,建议JVM配置:
-Xms512m -Xmx2g -
文件路径:确保APK和SO文件路径正确
-
并发控制:生产环境建议使用对象池,避免频繁创建模拟器
-
错误处理:所有API调用都应该有适当的错误处理和重试机制