暂存提交

This commit is contained in:
2026-07-31 13:52:35 +08:00
parent 11e740706d
commit bc3c18a6e9
35 changed files with 421 additions and 121 deletions
@@ -6,6 +6,7 @@ import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@@ -17,13 +18,14 @@ import java.security.MessageDigest;
@RestController
@RequestMapping("/internal/auth/accounts")
public class InternalAccountController {
private final AccountManagementService service;
@Resource
private AccountManagementService service;
private final String configuredToken;
private byte[] tokenBytes;
public InternalAccountController(AccountManagementService service,
public InternalAccountController(
@Value("${baiyue.security.internal-service-token}") String configuredToken) {
this.service = service;
this.configuredToken = configuredToken;
}
@@ -44,6 +46,11 @@ public class InternalAccountController {
@PathVariable Long id, @Valid @RequestBody UpdateAccountStatusRequest request) {
verify(token); service.updateStatus(id, request); return ApiResponse.success(null);
}
@PutMapping("/{id}/profile")
public ApiResponse<Void> profile(@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
@PathVariable Long id, @Valid @RequestBody UpdateAccountProfileRequest request) {
verify(token); service.updateProfile(id, request); return ApiResponse.success(null);
}
@PutMapping("/{id}/password")
public ApiResponse<Void> reset(@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
@PathVariable Long id, @Valid @RequestBody ResetPasswordRequest request) {
@@ -17,7 +17,10 @@ public final class AccountManagementDtos {
@NotBlank @Size(min = 8, max = 64)
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字") String password,
@NotBlank String displayName, @NotNull Long tenantId,
@NotNull Long membershipId) { }
@NotNull Long membershipId,
@Pattern(regexp = "MERCHANT|PLATFORM", message = "应用端只能是 MERCHANT 或 PLATFORM")
String appCode) { }
public record UpdateAccountProfileRequest(@NotBlank @Size(max = 64) String displayName) { }
public record UpdateAccountStatusRequest(@NotBlank String status) { }
public record ResetPasswordRequest(@NotBlank @Size(min = 8, max = 64)
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字") String newPassword) { }
@@ -11,6 +11,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baiyue.common.audit.BusinessAudit;
import java.util.Locale;
import java.util.Objects;
/** 账号生命周期管理,所有密码只以 BCrypt 摘要写入数据库。 */
@Service
public class AccountManagementService {
@@ -35,7 +38,15 @@ public class AccountManagementService {
@BusinessAudit(event = "ACCOUNT_CREATE", resourceType = "ACCOUNT",
resourceId = "#request.accountId", description = "创建认证账号")
public AccountSummary create(CreateAccountRequest request) {
if (repository.existsByUsername(request.username()) || repository.findById(request.accountId()) != null) {
String appCode = request.appCode() == null ? "MERCHANT"
: request.appCode().trim().toUpperCase(Locale.ROOT);
validateScope(appCode, request.tenantId());
AuthAccount existing = repository.findById(request.accountId());
if (existing != null) {
if (sameIdentity(existing, request, appCode)) return summary(existing);
throw new BizException(ResultCode.CONFLICT, "认证账号标识已被其他成员使用");
}
if (repository.existsByUsername(request.username())) {
throw new BizException(ResultCode.CONFLICT, "登录账号已存在");
}
AuthAccount account = new AuthAccount();
@@ -49,7 +60,7 @@ public class AccountManagementService {
account.setDefaultTenantId(request.tenantId());
account.setOperatorTenantId(request.tenantId());
account.setDefaultMembershipId(request.membershipId());
account.setDefaultAppCode("MERCHANT");
account.setDefaultAppCode(appCode);
account.setDeleted(0);
repository.insert(account);
return summary(account);
@@ -66,6 +77,16 @@ public class AccountManagementService {
bumpVersion(account);
}
@Transactional
@BusinessAudit(event = "ACCOUNT_PROFILE_CHANGE", resourceType = "ACCOUNT", resourceId = "#accountId",
description = "同步账号显示名称", captureAfter = false)
public void updateProfile(Long accountId, UpdateAccountProfileRequest request) {
AuthAccount account = required(accountId);
if (Objects.equals(account.getDisplayName(), request.displayName())) return;
account.setDisplayName(request.displayName());
bumpVersion(account);
}
@Transactional
@BusinessAudit(event = "PASSWORD_RESET", resourceType = "ACCOUNT", resourceId = "#accountId", description = "管理员重置密码", captureAfter = false)
public void resetPassword(Long accountId, ResetPasswordRequest request) {
@@ -108,4 +129,23 @@ public class AccountManagementService {
private AccountSummary summary(AuthAccount a) {
return new AccountSummary(a.getId(), a.getUsername(), a.getDisplayName(), a.getAccountStatus());
}
private void validateScope(String appCode, Long tenantId) {
if (!"MERCHANT".equals(appCode) && !"PLATFORM".equals(appCode)) {
throw new BizException(ResultCode.BAD_REQUEST, "应用端只能是 MERCHANT 或 PLATFORM");
}
if (("PLATFORM".equals(appCode) && tenantId != 0L)
|| ("MERCHANT".equals(appCode) && tenantId <= 0L)) {
throw new BizException(ResultCode.BAD_REQUEST, "账号应用端与租户范围不匹配");
}
}
private boolean sameIdentity(AuthAccount account, CreateAccountRequest request, String appCode) {
return Objects.equals(account.getUsername(), request.username())
&& Objects.equals(account.getDefaultTenantId(), request.tenantId())
&& Objects.equals(account.getOperatorTenantId(), request.tenantId())
&& Objects.equals(account.getDefaultMembershipId(), request.membershipId())
&& Objects.equals(account.getDefaultAppCode(), appCode)
&& !Integer.valueOf(1).equals(account.getDeleted());
}
}