refactor: improve auto-create API semantics and use real device ID

Frontend (account_service.dart):
- Use Android ID instead of random UUID for deviceId
- Add DeviceHardwareInfo class with full hardware details
- Remove provinceCode/cityCode from CreateAccountRequest
- Simplify to: deviceId (required), deviceName (optional JSON), inviterReferralCode (optional)

Backend (identity-service):
- Rename validateDeviceId() to checkDeviceNotRegistered() for clarity
- Rename generateNext() to generateNextUserSequence() for semantics
- Update error message: "该设备已创建过账户"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-06 18:05:11 -08:00
parent bc82f58549
commit 17fd663fe3
6 changed files with 114 additions and 48 deletions

View File

@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"Bash(cat:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)"
],
"deny": [],
"ask": []
}
}

View File

@ -26,8 +26,8 @@ export class AutoCreateAccountHandler {
) {} ) {}
async execute(command: AutoCreateAccountCommand): Promise<AutoCreateAccountResult> { async execute(command: AutoCreateAccountCommand): Promise<AutoCreateAccountResult> {
const deviceValidation = await this.validatorService.validateDeviceId(command.deviceId); const deviceCheck = await this.validatorService.checkDeviceNotRegistered(command.deviceId);
if (!deviceValidation.isValid) throw new ApplicationError(deviceValidation.errorMessage!); if (!deviceCheck.isValid) throw new ApplicationError(deviceCheck.errorMessage!);
let inviterSequence: AccountSequence | null = null; let inviterSequence: AccountSequence | null = null;
if (command.inviterReferralCode) { if (command.inviterReferralCode) {
@ -38,7 +38,7 @@ export class AutoCreateAccountHandler {
inviterSequence = inviter!.accountSequence; inviterSequence = inviter!.accountSequence;
} }
const accountSequence = await this.sequenceGenerator.generateNext(); const accountSequence = await this.sequenceGenerator.generateNextUserSequence();
const account = UserAccount.createAutomatic({ const account = UserAccount.createAutomatic({
accountSequence, accountSequence,

View File

@ -73,8 +73,8 @@ export class UserApplicationService {
inviterSequence = inviter!.accountSequence; inviterSequence = inviter!.accountSequence;
} }
// 3. 生成户序列号 // 3. 生成户序列号
const accountSequence = await this.sequenceGenerator.generateNext(); const accountSequence = await this.sequenceGenerator.generateNextUserSequence();
// 4. 创建用户账户 // 4. 创建用户账户
const account = UserAccount.createAutomatic({ const account = UserAccount.createAutomatic({
@ -301,7 +301,7 @@ export class UserApplicationService {
inviterSequence = inviter!.accountSequence; inviterSequence = inviter!.accountSequence;
} }
const accountSequence = await this.sequenceGenerator.generateNext(); const accountSequence = await this.sequenceGenerator.generateNextUserSequence();
const account = UserAccount.create({ const account = UserAccount.create({
accountSequence, accountSequence,

View File

@ -9,7 +9,7 @@ export class AccountSequenceGeneratorService {
private readonly repository: UserAccountRepository, private readonly repository: UserAccountRepository,
) {} ) {}
async generateNext(): Promise<AccountSequence> { async generateNextUserSequence(): Promise<AccountSequence> {
return this.repository.getNextAccountSequence(); return this.repository.getNextAccountSequence();
} }
} }

View File

@ -30,9 +30,9 @@ export class UserValidatorService {
return ValidationResult.success(); return ValidationResult.success();
} }
async validateDeviceId(deviceId: string): Promise<ValidationResult> { async checkDeviceNotRegistered(deviceId: string): Promise<ValidationResult> {
const existing = await this.repository.findByDeviceId(deviceId); const existing = await this.repository.findByDeviceId(deviceId);
if (existing) return ValidationResult.failure('该设备已创建账户'); if (existing) return ValidationResult.failure('该设备已创建账户');
return ValidationResult.success(); return ValidationResult.success();
} }

View File

@ -8,29 +8,60 @@ import '../storage/storage_keys.dart';
import '../errors/exceptions.dart'; import '../errors/exceptions.dart';
import 'mpc_share_service.dart'; import 'mpc_share_service.dart';
/// ( deviceName )
class DeviceHardwareInfo {
final String? brand; // ( "Xiaomi")
final String? manufacturer; // ( "Xiaomi")
final String? model; // ( "Mi 11")
final String? device; // ( "venus")
final String? product; // ( "venus_eea")
final String? hardware; // ( "qcom")
final String? osVersion; // ( "13")
final int? sdkInt; // SDK ( 33)
final bool? isPhysicalDevice; //
DeviceHardwareInfo({
this.brand,
this.manufacturer,
this.model,
this.device,
this.product,
this.hardware,
this.osVersion,
this.sdkInt,
this.isPhysicalDevice,
});
Map<String, dynamic> toJson() => {
if (brand != null) 'brand': brand,
if (manufacturer != null) 'manufacturer': manufacturer,
if (model != null) 'model': model,
if (device != null) 'device': device,
if (product != null) 'product': product,
if (hardware != null) 'hardware': hardware,
if (osVersion != null) 'osVersion': osVersion,
if (sdkInt != null) 'sdkInt': sdkInt,
if (isPhysicalDevice != null) 'isPhysicalDevice': isPhysicalDevice,
};
}
/// ///
class CreateAccountRequest { class CreateAccountRequest {
final String deviceId; final String deviceId; // : Android ID iOS identifierForVendor
final String? deviceName; final DeviceHardwareInfo? deviceName; // : (JSON)
final String? inviterReferralCode; final String? inviterReferralCode; // :
final String? provinceCode;
final String? cityCode;
CreateAccountRequest({ CreateAccountRequest({
required this.deviceId, required this.deviceId,
this.deviceName, this.deviceName,
this.inviterReferralCode, this.inviterReferralCode,
this.provinceCode,
this.cityCode,
}); });
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'deviceId': deviceId, 'deviceId': deviceId,
if (deviceName != null) 'deviceName': deviceName, if (deviceName != null) 'deviceName': deviceName!.toJson(),
if (inviterReferralCode != null) if (inviterReferralCode != null)
'inviterReferralCode': inviterReferralCode, 'inviterReferralCode': inviterReferralCode,
if (provinceCode != null) 'provinceCode': provinceCode,
if (cityCode != null) 'cityCode': cityCode,
}; };
} }
@ -108,33 +139,59 @@ class AccountService {
}) : _apiClient = apiClient, }) : _apiClient = apiClient,
_secureStorage = secureStorage; _secureStorage = secureStorage;
/// ID ///
Future<String> getOrCreateDeviceId() async { ///
// /// Android: 使 Android ID (64)
var deviceId = await _secureStorage.read(key: StorageKeys.deviceId); /// iOS: 使 identifierForVendor
if (deviceId != null && deviceId.isNotEmpty) { Future<String> getDeviceId() async {
return deviceId; final deviceInfoPlugin = DeviceInfoPlugin();
}
// ID
deviceId = const Uuid().v4();
await _secureStorage.write(key: StorageKeys.deviceId, value: deviceId);
return deviceId;
}
///
Future<String> getDeviceName() async {
final deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) { if (Platform.isAndroid) {
final info = await deviceInfo.androidInfo; final info = await deviceInfoPlugin.androidInfo;
return '${info.brand} ${info.model}'; return info.id; // Android ID
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
final info = await deviceInfo.iosInfo; final info = await deviceInfoPlugin.iosInfo;
return info.name; return info.identifierForVendor ?? const Uuid().v4();
} }
return 'Unknown Device'; // 退 UUID
return const Uuid().v4();
}
///
///
/// Android:
/// iOS:
Future<DeviceHardwareInfo> getDeviceHardwareInfo() async {
final deviceInfoPlugin = DeviceInfoPlugin();
if (Platform.isAndroid) {
final info = await deviceInfoPlugin.androidInfo;
return DeviceHardwareInfo(
brand: info.brand,
manufacturer: info.manufacturer,
model: info.model,
device: info.device,
product: info.product,
hardware: info.hardware,
osVersion: info.version.release,
sdkInt: info.version.sdkInt,
isPhysicalDevice: info.isPhysicalDevice,
);
} else if (Platform.isIOS) {
final info = await deviceInfoPlugin.iosInfo;
return DeviceHardwareInfo(
brand: 'Apple',
manufacturer: 'Apple',
model: info.model,
device: info.name,
osVersion: info.systemVersion,
isPhysicalDevice: info.isPhysicalDevice,
);
}
//
return DeviceHardwareInfo();
} }
/// (APP) /// (APP)
@ -142,13 +199,12 @@ class AccountService {
/// 使 MPC 2-of-3 /// 使 MPC 2-of-3
Future<CreateAccountResponse> createAccount({ Future<CreateAccountResponse> createAccount({
String? inviterReferralCode, String? inviterReferralCode,
String? provinceCode,
String? cityCode,
}) async { }) async {
try { try {
// // ID ()
final deviceId = await getOrCreateDeviceId(); final deviceId = await getDeviceId();
final deviceName = await getDeviceName(); // ()
final deviceName = await getDeviceHardwareInfo();
// API // API
final response = await _apiClient.post( final response = await _apiClient.post(
@ -157,8 +213,6 @@ class AccountService {
deviceId: deviceId, deviceId: deviceId,
deviceName: deviceName, deviceName: deviceName,
inviterReferralCode: inviterReferralCode, inviterReferralCode: inviterReferralCode,
provinceCode: provinceCode,
cityCode: cityCode,
).toJson(), ).toJson(),
); );