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

View File

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

View File

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

View File

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

View File

@ -8,29 +8,60 @@ import '../storage/storage_keys.dart';
import '../errors/exceptions.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 {
final String deviceId;
final String? deviceName;
final String? inviterReferralCode;
final String? provinceCode;
final String? cityCode;
final String deviceId; // : Android ID iOS identifierForVendor
final DeviceHardwareInfo? deviceName; // : (JSON)
final String? inviterReferralCode; // :
CreateAccountRequest({
required this.deviceId,
this.deviceName,
this.inviterReferralCode,
this.provinceCode,
this.cityCode,
});
Map<String, dynamic> toJson() => {
'deviceId': deviceId,
if (deviceName != null) 'deviceName': deviceName,
if (deviceName != null) 'deviceName': deviceName!.toJson(),
if (inviterReferralCode != null)
'inviterReferralCode': inviterReferralCode,
if (provinceCode != null) 'provinceCode': provinceCode,
if (cityCode != null) 'cityCode': cityCode,
};
}
@ -108,33 +139,59 @@ class AccountService {
}) : _apiClient = apiClient,
_secureStorage = secureStorage;
/// ID
Future<String> getOrCreateDeviceId() async {
//
var deviceId = await _secureStorage.read(key: StorageKeys.deviceId);
if (deviceId != null && deviceId.isNotEmpty) {
return deviceId;
}
// ID
deviceId = const Uuid().v4();
await _secureStorage.write(key: StorageKeys.deviceId, value: deviceId);
return deviceId;
}
///
Future<String> getDeviceName() async {
final deviceInfo = DeviceInfoPlugin();
///
///
/// Android: 使 Android ID (64)
/// iOS: 使 identifierForVendor
Future<String> getDeviceId() async {
final deviceInfoPlugin = DeviceInfoPlugin();
if (Platform.isAndroid) {
final info = await deviceInfo.androidInfo;
return '${info.brand} ${info.model}';
final info = await deviceInfoPlugin.androidInfo;
return info.id; // Android ID
} else if (Platform.isIOS) {
final info = await deviceInfo.iosInfo;
return info.name;
final info = await deviceInfoPlugin.iosInfo;
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)
@ -142,13 +199,12 @@ class AccountService {
/// 使 MPC 2-of-3
Future<CreateAccountResponse> createAccount({
String? inviterReferralCode,
String? provinceCode,
String? cityCode,
}) async {
try {
//
final deviceId = await getOrCreateDeviceId();
final deviceName = await getDeviceName();
// ID ()
final deviceId = await getDeviceId();
// ()
final deviceName = await getDeviceHardwareInfo();
// API
final response = await _apiClient.post(
@ -157,8 +213,6 @@ class AccountService {
deviceId: deviceId,
deviceName: deviceName,
inviterReferralCode: inviterReferralCode,
provinceCode: provinceCode,
cityCode: cityCode,
).toJson(),
);