fix:异常信息返回中文化
This commit is contained in:
parent
e9240a38d6
commit
3040ed443b
@ -62,7 +62,7 @@ public class GlobalExceptionHandler {
|
|||||||
|
|
||||||
@ExceptionHandler(MultipartException.class)
|
@ExceptionHandler(MultipartException.class)
|
||||||
public ResponseEntity<ApiResponse<Void>> handleMultipartException(MultipartException ex, HttpServletRequest request) {
|
public ResponseEntity<ApiResponse<Void>> handleMultipartException(MultipartException ex, HttpServletRequest request) {
|
||||||
return response(HttpStatus.BAD_REQUEST, ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "failed to parse multipart request")
|
return response(HttpStatus.BAD_REQUEST, ApiResponse.fail(ErrorCode.VALIDATE_FAILED.getCode(), "解析 multipart 上传请求失败")
|
||||||
.withPath(request.getRequestURI()));
|
.withPath(request.getRequestURI()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ public class GlobalExceptionHandler {
|
|||||||
}
|
}
|
||||||
log.warn("Replay protection 执行失败", ex);
|
log.warn("Replay protection 执行失败", ex);
|
||||||
return response(HttpStatus.SERVICE_UNAVAILABLE,
|
return response(HttpStatus.SERVICE_UNAVAILABLE,
|
||||||
ApiResponse.fail(ErrorCode.SERVICE_UNAVAILABLE.getCode(), "replay protection unavailable")
|
ApiResponse.fail(ErrorCode.SERVICE_UNAVAILABLE.getCode(), "防重放校验服务不可用")
|
||||||
.withPath(request.getRequestURI()));
|
.withPath(request.getRequestURI()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ public class GlobalExceptionHandler {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
return response(HttpStatus.NOT_FOUND,
|
return response(HttpStatus.NOT_FOUND,
|
||||||
ApiResponse.fail(ErrorCode.NOT_FOUND.getCode(), "resource not found")
|
ApiResponse.fail(ErrorCode.NOT_FOUND.getCode(), "资源不存在")
|
||||||
.withPath(request.getRequestURI()));
|
.withPath(request.getRequestURI()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,13 +14,13 @@ public class PcieErrorMapper {
|
|||||||
lowByteErrorMap.put(0x1F, "UKey错误");
|
lowByteErrorMap.put(0x1F, "UKey错误");
|
||||||
lowByteErrorMap.put(0x20, "生成密钥错误");
|
lowByteErrorMap.put(0x20, "生成密钥错误");
|
||||||
lowByteErrorMap.put(0x21, "状态错误");
|
lowByteErrorMap.put(0x21, "状态错误");
|
||||||
lowByteErrorMap.put(0x22, "retry exceeded");
|
lowByteErrorMap.put(0x22, "重试次数超限");
|
||||||
lowByteErrorMap.put(0x23, "device busy");
|
lowByteErrorMap.put(0x23, "设备忙");
|
||||||
lowByteErrorMap.put(0x24, "error status");
|
lowByteErrorMap.put(0x24, "状态错误");
|
||||||
lowByteErrorMap.put(0x25, "init status");
|
lowByteErrorMap.put(0x25, "初始化状态错误");
|
||||||
lowByteErrorMap.put(0x26, "already logined");
|
lowByteErrorMap.put(0x26, "已登录");
|
||||||
lowByteErrorMap.put(0x27, "timeout");
|
lowByteErrorMap.put(0x27, "操作超时");
|
||||||
lowByteErrorMap.put(0x1D, "invalid argument");
|
lowByteErrorMap.put(0x1D, "参数无效");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toMessage(int retCode) {
|
public String toMessage(int retCode) {
|
||||||
|
|||||||
@ -2115,7 +2115,7 @@ public class JnaPcieCryptoService implements PcieCryptoService {
|
|||||||
|
|
||||||
private static List<RecoverUserKeyRequest> requireUserKeyRequests(List<RecoverUserKeyRequest> requests) {
|
private static List<RecoverUserKeyRequest> requireUserKeyRequests(List<RecoverUserKeyRequest> requests) {
|
||||||
if (requests == null || requests.isEmpty()) {
|
if (requests == null || requests.isEmpty()) {
|
||||||
throw new IllegalArgumentException("userKeyRequests must not be empty");
|
throw new IllegalArgumentException("userKeyRequests不能为空");
|
||||||
}
|
}
|
||||||
for (RecoverUserKeyRequest request : requests) {
|
for (RecoverUserKeyRequest request : requests) {
|
||||||
RecoverUserKeyRequest req = requireRequest("userKeyRequest", request);
|
RecoverUserKeyRequest req = requireRequest("userKeyRequest", request);
|
||||||
@ -2447,21 +2447,21 @@ public class JnaPcieCryptoService implements PcieCryptoService {
|
|||||||
|
|
||||||
private static int requirePositive(String name, int value) {
|
private static int requirePositive(String name, int value) {
|
||||||
if (value <= 0) {
|
if (value <= 0) {
|
||||||
throw new IllegalArgumentException(name + " must be > 0");
|
throw new IllegalArgumentException(name + "必须大于0");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int requireNonNegative(String name, int value) {
|
private static int requireNonNegative(String name, int value) {
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
throw new IllegalArgumentException(name + " must be >= 0");
|
throw new IllegalArgumentException(name + "必须大于等于0");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int requireByteRange(String name, int value) {
|
private static int requireByteRange(String name, int value) {
|
||||||
if (value < 0 || value > 255) {
|
if (value < 0 || value > 255) {
|
||||||
throw new IllegalArgumentException(name + " must be in [0,255]");
|
throw new IllegalArgumentException(name + "必须在[0,255]范围内");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -2469,7 +2469,7 @@ public class JnaPcieCryptoService implements PcieCryptoService {
|
|||||||
private int requireValidAlgId(String name, int algId) {
|
private int requireValidAlgId(String name, int algId) {
|
||||||
int value = requirePositive(name, algId);
|
int value = requirePositive(name, algId);
|
||||||
if (strictAlgIdValidation && !Gm0018AlgorithmIds.isSupported(value)) {
|
if (strictAlgIdValidation && !Gm0018AlgorithmIds.isSupported(value)) {
|
||||||
throw new IllegalArgumentException(name + " is not a supported GM/T 0018 algorithm id: 0x" + Integer.toHexString(value));
|
throw new IllegalArgumentException(name + "不是支持的GM/T 0018算法标识:0x" + Integer.toHexString(value));
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -2482,14 +2482,14 @@ public class JnaPcieCryptoService implements PcieCryptoService {
|
|||||||
|
|
||||||
private static byte[] requireNonEmptyBytes(String name, byte[] value) {
|
private static byte[] requireNonEmptyBytes(String name, byte[] value) {
|
||||||
if (value == null || value.length == 0) {
|
if (value == null || value.length == 0) {
|
||||||
throw new IllegalArgumentException(name + " must not be empty");
|
throw new IllegalArgumentException(name + "不能为空");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] requireBytes(String name, byte[] value) {
|
private static byte[] requireBytes(String name, byte[] value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new IllegalArgumentException(name + " must not be null");
|
throw new IllegalArgumentException(name + "不能为null");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -2557,10 +2557,10 @@ public class JnaPcieCryptoService implements PcieCryptoService {
|
|||||||
int size = structure.size();
|
int size = structure.size();
|
||||||
if (allowPartial) {
|
if (allowPartial) {
|
||||||
if (source.length > size) {
|
if (source.length > size) {
|
||||||
throw new IllegalArgumentException(name + " length must be <= " + size + ", actual: " + source.length);
|
throw new IllegalArgumentException(name + "长度必须小于等于" + size + ",实际为:" + source.length);
|
||||||
}
|
}
|
||||||
} else if (source.length != size) {
|
} else if (source.length != size) {
|
||||||
throw new IllegalArgumentException(name + " length must be exactly " + size + ", actual: " + source.length);
|
throw new IllegalArgumentException(name + "长度必须等于" + size + ",实际为:" + source.length);
|
||||||
}
|
}
|
||||||
Pointer pointer = structure.getPointer();
|
Pointer pointer = structure.getPointer();
|
||||||
pointer.clear(size);
|
pointer.clear(size);
|
||||||
@ -2571,7 +2571,7 @@ public class JnaPcieCryptoService implements PcieCryptoService {
|
|||||||
|
|
||||||
private static <T> T requireRequest(String name, T request) {
|
private static <T> T requireRequest(String name, T request) {
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
throw new IllegalArgumentException(name + " must not be null");
|
throw new IllegalArgumentException(name + "不能为null");
|
||||||
}
|
}
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -915,7 +915,7 @@ public class MockPcieCryptoService implements PcieCryptoService {
|
|||||||
recoverIkComponent(2, req.getAuthIkComponent());
|
recoverIkComponent(2, req.getAuthIkComponent());
|
||||||
recoverIkComponent(1, req.getDeviceIkComponent());
|
recoverIkComponent(1, req.getDeviceIkComponent());
|
||||||
if (req.getUserKeyRequests() == null || req.getUserKeyRequests().isEmpty()) {
|
if (req.getUserKeyRequests() == null || req.getUserKeyRequests().isEmpty()) {
|
||||||
throw new IllegalArgumentException("userKeyRequests must not be empty");
|
throw new IllegalArgumentException("userKeyRequests不能为空");
|
||||||
}
|
}
|
||||||
req.getUserKeyRequests().forEach(this::recoverUserKey);
|
req.getUserKeyRequests().forEach(this::recoverUserKey);
|
||||||
if (!checkLmk()) {
|
if (!checkLmk()) {
|
||||||
@ -1099,49 +1099,49 @@ public class MockPcieCryptoService implements PcieCryptoService {
|
|||||||
|
|
||||||
private static int requirePositive(String name, int value) {
|
private static int requirePositive(String name, int value) {
|
||||||
if (value <= 0) {
|
if (value <= 0) {
|
||||||
throw new IllegalArgumentException(name + " must be > 0");
|
throw new IllegalArgumentException(name + "必须大于0");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int requireNonNegative(String name, int value) {
|
private static int requireNonNegative(String name, int value) {
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
throw new IllegalArgumentException(name + " must be >= 0");
|
throw new IllegalArgumentException(name + "必须大于等于0");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int requireByteRange(String name, int value) {
|
private static int requireByteRange(String name, int value) {
|
||||||
if (value < 0 || value > 255) {
|
if (value < 0 || value > 255) {
|
||||||
throw new IllegalArgumentException(name + " must be in [0,255]");
|
throw new IllegalArgumentException(name + "必须在[0,255]范围内");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] requireNonEmptyBytes(String name, byte[] value) {
|
private static byte[] requireNonEmptyBytes(String name, byte[] value) {
|
||||||
if (value == null || value.length == 0) {
|
if (value == null || value.length == 0) {
|
||||||
throw new IllegalArgumentException(name + " must not be empty");
|
throw new IllegalArgumentException(name + "不能为空");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] requireBytes(String name, byte[] value) {
|
private static byte[] requireBytes(String name, byte[] value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new IllegalArgumentException(name + " must not be null");
|
throw new IllegalArgumentException(name + "不能为null");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String requireNonBlank(String name, String value) {
|
private static String requireNonBlank(String name, String value) {
|
||||||
if (value == null || value.isBlank()) {
|
if (value == null || value.isBlank()) {
|
||||||
throw new IllegalArgumentException(name + " must not be blank");
|
throw new IllegalArgumentException(name + "不能为空");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T requireRequest(String name, T request) {
|
private static <T> T requireRequest(String name, T request) {
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
throw new IllegalArgumentException(name + " must not be null");
|
throw new IllegalArgumentException(name + "不能为null");
|
||||||
}
|
}
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -164,12 +164,12 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
loadRole(targetRoleCode);
|
loadRole(targetRoleCode);
|
||||||
RoleCode targetRole = resolveRoleCode(targetRoleCode);
|
RoleCode targetRole = resolveRoleCode(targetRoleCode);
|
||||||
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
|
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
|
||||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid exceeds role ukey requirement");
|
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid超出角色UKey席位要求");
|
||||||
}
|
}
|
||||||
AuthFullAccountEntity account = authFullAccountRepository.findByRoleCodeAndUid(targetRoleCode, uid)
|
AuthFullAccountEntity account = authFullAccountRepository.findByRoleCodeAndUid(targetRoleCode, uid)
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在"));
|
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "目标角色完整账号不存在"));
|
||||||
if (!passwordHasher.matches(oldPassword, account.getPasswordSalt(), account.getPasswordHash())) {
|
if (!passwordHasher.matches(oldPassword, account.getPasswordSalt(), account.getPasswordHash())) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "old password is incorrect");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "旧密码错误");
|
||||||
}
|
}
|
||||||
applyAdminPasswordChange(account, newPassword);
|
applyAdminPasswordChange(account, newPassword);
|
||||||
authFullAccountRepository.update(account);
|
authFullAccountRepository.update(account);
|
||||||
@ -476,7 +476,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
|
|
||||||
private void validateRoleUid(RoleCode targetRole, Integer uid) {
|
private void validateRoleUid(RoleCode targetRole, Integer uid) {
|
||||||
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
|
if (uid == null || uid < 1 || uid > targetRole.getRequiredUkeyCount()) {
|
||||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid exceeds role ukey requirement");
|
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "uid超出角色UKey席位要求");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,7 +484,7 @@ public class AuthAdminServiceImpl implements AuthAdminService {
|
|||||||
return List.of(RoleCode.values()).stream()
|
return List.of(RoleCode.values()).stream()
|
||||||
.filter(item -> item.getCode().equals(roleCode))
|
.filter(item -> item.getCode().equals(roleCode))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "invalid roleCode"));
|
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "roleCode无效"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDateTime now() {
|
private LocalDateTime now() {
|
||||||
|
|||||||
@ -154,7 +154,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
RoleUkeyBindingEntity binding = bindingsByUid.getOrDefault(proof.getUid(), List.of()).stream()
|
RoleUkeyBindingEntity binding = bindingsByUid.getOrDefault(proof.getUid(), List.of()).stream()
|
||||||
.filter(item -> item.getUkeyPubkey().equals(proof.getPubKey()))
|
.filter(item -> item.getUkeyPubkey().equals(proof.getPubKey()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey auth info does not match bound role"));
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "UKey认证信息与绑定角色不匹配"));
|
||||||
compatUkeyVerifier.verifyIssuedBinding(buildIssuePayload(request.getRoleCode(), proof, authKeyPair), proof.getIssueSignature());
|
compatUkeyVerifier.verifyIssuedBinding(buildIssuePayload(request.getRoleCode(), proof, authKeyPair), proof.getIssueSignature());
|
||||||
compatUkeyVerifier.verifyLoginSignature(proof.getPubKey(), proof.getLoginPayload(), proof.getLoginSignature());
|
compatUkeyVerifier.verifyLoginSignature(proof.getPubKey(), proof.getLoginPayload(), proof.getLoginSignature());
|
||||||
matchedSerials.add(binding.getUkeySerial());
|
matchedSerials.add(binding.getUkeySerial());
|
||||||
@ -208,7 +208,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
PasswordComplexityValidator.validate(newPassword);
|
PasswordComplexityValidator.validate(newPassword);
|
||||||
AuthSessionEntity session = requireActiveSession(sessionToken);
|
AuthSessionEntity session = requireActiveSession(sessionToken);
|
||||||
if (uid == null || !containsAuthenticatedUid(session, uid)) {
|
if (uid == null || !containsAuthenticatedUid(session, uid)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account is not authenticated in current session");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "当前会话未完成账号认证");
|
||||||
}
|
}
|
||||||
AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid)
|
AuthFullAccountEntity fullAccount = authFullAccountRepository.findByRoleCodeAndUid(session.getRoleCode(), uid)
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
.orElseThrow(() -> new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号不存在"));
|
||||||
@ -237,7 +237,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
|
|
||||||
private void validateRoleStatus(RoleAccountEntity roleAccount) {
|
private void validateRoleStatus(RoleAccountEntity roleAccount) {
|
||||||
if (!RoleAccountStatus.ACTIVE.name().equals(roleAccount.getStatus())) {
|
if (!RoleAccountStatus.ACTIVE.name().equals(roleAccount.getStatus())) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role is not enabled");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "角色未启用");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
if (accounts == null || accounts.size() != expectedCount) {
|
if (accounts == null || accounts.size() != expectedCount) {
|
||||||
throw new BizException(
|
throw new BizException(
|
||||||
ErrorCode.UNAUTHORIZED.getCode(),
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
roleAccount.getRoleCode() + " requires exactly " + expectedCount + " account passwords"
|
roleAccount.getRoleCode() + "必须提交" + expectedCount + "个账号密码"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +258,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
for (PasswordLoginAccountRequest account : accounts) {
|
for (PasswordLoginAccountRequest account : accounts) {
|
||||||
Integer uid = account.getUid();
|
Integer uid = account.getUid();
|
||||||
if (uid == null || !uniqueUids.add(uid)) {
|
if (uid == null || !uniqueUids.add(uid)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "duplicate account uid submitted");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "提交了重复的账号uid");
|
||||||
}
|
}
|
||||||
AuthFullAccountEntity roleAccountSeat = authFullAccountRepository
|
AuthFullAccountEntity roleAccountSeat = authFullAccountRepository
|
||||||
.findByRoleCodeAndUid(roleAccount.getRoleCode(), uid)
|
.findByRoleCodeAndUid(roleAccount.getRoleCode(), uid)
|
||||||
@ -274,11 +274,11 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
|
|
||||||
private void validateFullAccountStatus(AuthFullAccountEntity fullAccount) {
|
private void validateFullAccountStatus(AuthFullAccountEntity fullAccount) {
|
||||||
if (RoleAccountStatus.UNENABLED.name().equals(fullAccount.getStatus())) {
|
if (RoleAccountStatus.UNENABLED.name().equals(fullAccount.getStatus())) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is not enabled");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "完整账号未启用");
|
||||||
}
|
}
|
||||||
if (RoleAccountStatus.LOCKED.name().equals(fullAccount.getStatus())
|
if (RoleAccountStatus.LOCKED.name().equals(fullAccount.getStatus())
|
||||||
&& (fullAccount.getLockedUntil() == null || fullAccount.getLockedUntil().isAfter(now()))) {
|
&& (fullAccount.getLockedUntil() == null || fullAccount.getLockedUntil().isAfter(now()))) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is locked");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "完整账号已锁定");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,10 +290,10 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
fullAccount.setStatus(RoleAccountStatus.LOCKED.name());
|
fullAccount.setStatus(RoleAccountStatus.LOCKED.name());
|
||||||
fullAccount.setLockedUntil(now().plusMinutes(IDLE_TIMEOUT_MINUTES));
|
fullAccount.setLockedUntil(now().plusMinutes(IDLE_TIMEOUT_MINUTES));
|
||||||
authFullAccountRepository.update(fullAccount);
|
authFullAccountRepository.update(fullAccount);
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "full account is locked");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "完整账号已锁定");
|
||||||
}
|
}
|
||||||
authFullAccountRepository.update(fullAccount);
|
authFullAccountRepository.update(fullAccount);
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "password is incorrect");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "密码错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetValidatedAccounts(List<AuthFullAccountEntity> validatedAccounts) {
|
private void resetValidatedAccounts(List<AuthFullAccountEntity> validatedAccounts) {
|
||||||
@ -320,7 +320,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
if (fullAccounts == null || fullAccounts.size() != expectedCount) {
|
if (fullAccounts == null || fullAccounts.size() != expectedCount) {
|
||||||
throw new BizException(
|
throw new BizException(
|
||||||
ErrorCode.UNAUTHORIZED.getCode(),
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
roleAccount.getRoleCode() + " requires exactly " + expectedCount + " full-account passwords"
|
roleAccount.getRoleCode() + "必须提交" + expectedCount + "个完整账号密码"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,7 +329,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
for (FullLoginAccountRequest account : fullAccounts) {
|
for (FullLoginAccountRequest account : fullAccounts) {
|
||||||
Integer uid = account.getUid();
|
Integer uid = account.getUid();
|
||||||
if (uid == null || !uniqueUids.add(uid)) {
|
if (uid == null || !uniqueUids.add(uid)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "duplicate full-account uid submitted");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "提交了重复的完整账号uid");
|
||||||
}
|
}
|
||||||
AuthFullAccountEntity fullAccount = authFullAccountRepository
|
AuthFullAccountEntity fullAccount = authFullAccountRepository
|
||||||
.findByRoleCodeAndUid(roleAccount.getRoleCode(), uid)
|
.findByRoleCodeAndUid(roleAccount.getRoleCode(), uid)
|
||||||
@ -347,17 +347,17 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
List<AuthFullAccountEntity> fixedAccounts = authFullAccountRepository.findByRoleCode(roleCode);
|
List<AuthFullAccountEntity> fixedAccounts = authFullAccountRepository.findByRoleCode(roleCode);
|
||||||
int requiredCount = authPolicyService.requiredUkeyCount(RoleCode.valueOf(roleCode));
|
int requiredCount = authPolicyService.requiredUkeyCount(RoleCode.valueOf(roleCode));
|
||||||
if (fixedAccounts.size() < requiredCount) {
|
if (fixedAccounts.size() < requiredCount) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "role fixed account mapping is incomplete");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "角色固定账号映射不完整");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FullLoginAccountRequest> requests = new ArrayList<>();
|
List<FullLoginAccountRequest> requests = new ArrayList<>();
|
||||||
for (UkeyLoginProof proof : proofs.stream().sorted(java.util.Comparator.comparing(UkeyLoginProof::getUid)).toList()) {
|
for (UkeyLoginProof proof : proofs.stream().sorted(java.util.Comparator.comparing(UkeyLoginProof::getUid)).toList()) {
|
||||||
Integer uid = proof.getUid();
|
Integer uid = proof.getUid();
|
||||||
if (uid == null || uid < 1 || uid > fixedAccounts.size()) {
|
if (uid == null || uid < 1 || uid > fixedAccounts.size()) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "uid does not match fixed role account");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "uid与固定角色账号不匹配");
|
||||||
}
|
}
|
||||||
if (proof.getPassword() == null || proof.getPassword().isBlank()) {
|
if (proof.getPassword() == null || proof.getPassword().isBlank()) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "account password不能为空");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "账号密码不能为空");
|
||||||
}
|
}
|
||||||
FullLoginAccountRequest accountRequest = new FullLoginAccountRequest();
|
FullLoginAccountRequest accountRequest = new FullLoginAccountRequest();
|
||||||
accountRequest.setUid(uid);
|
accountRequest.setUid(uid);
|
||||||
@ -370,7 +370,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
private void ensureMasterKeyReady() {
|
private void ensureMasterKeyReady() {
|
||||||
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
|
MasterKeyStatus.StatusDetail status = lmkService.getMasterKeyStatus();
|
||||||
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
if (status == null || status.getCode() == MasterKeyStatus.ABNORMAL.getCode()) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "master key is not ready");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "主密钥未就绪");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,7 +386,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
Set<String> uniqueSerials = new LinkedHashSet<>(ukeySerials);
|
Set<String> uniqueSerials = new LinkedHashSet<>(ukeySerials);
|
||||||
int requiredUkeyCount = authPolicyService.requiredUkeyCount(RoleCode.valueOf(roleAccount.getRoleCode()));
|
int requiredUkeyCount = authPolicyService.requiredUkeyCount(RoleCode.valueOf(roleAccount.getRoleCode()));
|
||||||
if (uniqueSerials.size() != requiredUkeyCount) {
|
if (uniqueSerials.size() != requiredUkeyCount) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "UKey数量不满足角色要求");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RoleUkeyBindingEntity> bindings = roleUkeyBindingRepository.findActiveByRoleCode(roleAccount.getRoleCode());
|
List<RoleUkeyBindingEntity> bindings = roleUkeyBindingRepository.findActiveByRoleCode(roleAccount.getRoleCode());
|
||||||
@ -394,7 +394,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
.map(RoleUkeyBindingEntity::getUkeySerial)
|
.map(RoleUkeyBindingEntity::getUkeySerial)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
if (!boundSerials.containsAll(uniqueSerials)) {
|
if (!boundSerials.containsAll(uniqueSerials)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey verification 执行失败");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "UKey校验执行失败");
|
||||||
}
|
}
|
||||||
long matchedUidCount = bindings.stream()
|
long matchedUidCount = bindings.stream()
|
||||||
.filter(binding -> uniqueSerials.contains(binding.getUkeySerial()))
|
.filter(binding -> uniqueSerials.contains(binding.getUkeySerial()))
|
||||||
@ -402,7 +402,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
.distinct()
|
.distinct()
|
||||||
.count();
|
.count();
|
||||||
if (matchedUidCount != requiredUkeyCount) {
|
if (matchedUidCount != requiredUkeyCount) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "UKey数量不满足角色要求");
|
||||||
}
|
}
|
||||||
return authPolicyService.resolveAuthLevel(AuthMethod.UKEY);
|
return authPolicyService.resolveAuthLevel(AuthMethod.UKEY);
|
||||||
}
|
}
|
||||||
@ -413,20 +413,20 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
List<UkeyLoginProof> proofs
|
List<UkeyLoginProof> proofs
|
||||||
) {
|
) {
|
||||||
if (proofs == null || proofs.size() != authPolicyService.requiredUkeyCount(roleCode)) {
|
if (proofs == null || proofs.size() != authPolicyService.requiredUkeyCount(roleCode)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "UKey数量不满足角色要求");
|
||||||
}
|
}
|
||||||
Set<Integer> requestUids = proofs.stream()
|
Set<Integer> requestUids = proofs.stream()
|
||||||
.map(UkeyLoginProof::getUid)
|
.map(UkeyLoginProof::getUid)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
int requiredCount = authPolicyService.requiredUkeyCount(roleCode);
|
int requiredCount = authPolicyService.requiredUkeyCount(roleCode);
|
||||||
if (requestUids.size() != requiredCount) {
|
if (requestUids.size() != requiredCount) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "UKey数量不满足角色要求");
|
||||||
}
|
}
|
||||||
Set<Integer> activeUids = activeBindings.stream()
|
Set<Integer> activeUids = activeBindings.stream()
|
||||||
.map(RoleUkeyBindingEntity::getUid)
|
.map(RoleUkeyBindingEntity::getUid)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
if (!activeUids.containsAll(requestUids)) {
|
if (!activeUids.containsAll(requestUids)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "ukey count does not satisfy role requirement");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "UKey数量不满足角色要求");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,7 +437,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
try {
|
try {
|
||||||
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair, RoleCode.valueOf(roleCode).getCode()));
|
return objectMapper.writeValueAsString(UKeySignEntity.getInstance(dto, authKeyPair, RoleCode.valueOf(roleCode).getCode()));
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
throw new IllegalStateException("serialize ukey issue payload 执行失败", ex);
|
throw new IllegalStateException("序列化UKey签发载荷失败", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,7 +502,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
try {
|
try {
|
||||||
return objectMapper.writeValueAsString(authenticatedPrincipals);
|
return objectMapper.writeValueAsString(authenticatedPrincipals);
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
throw new IllegalStateException("serialize authenticated principals 执行失败", ex);
|
throw new IllegalStateException("序列化已认证主体失败", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,7 +536,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
new TypeReference<List<AuthSessionPrincipal>>() { }
|
new TypeReference<List<AuthSessionPrincipal>>() { }
|
||||||
);
|
);
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
throw new IllegalStateException("deserialize authenticated principals 执行失败", ex);
|
throw new IllegalStateException("反序列化已认证主体失败", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,7 @@ public class InMemoryUkeyLoginRandomService implements UkeyLoginRandomService {
|
|||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "登录随机数不存在或已过期");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "登录随机数不存在或已过期");
|
||||||
}
|
}
|
||||||
if (randoms == null || randoms.size() != issuedRandoms.randoms().size() || !issuedRandoms.randoms().containsAll(randoms)) {
|
if (randoms == null || randoms.size() != issuedRandoms.randoms().size() || !issuedRandoms.randoms().containsAll(randoms)) {
|
||||||
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "login random verification 执行失败");
|
throw new BizException(ErrorCode.UNAUTHORIZED.getCode(), "登录随机数校验失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -533,7 +533,7 @@ public class ResourcePackageServiceImpl implements ResourcePackageService {
|
|||||||
}
|
}
|
||||||
String resourceCode = trim(item.getResourceCode()).isEmpty() ? "UNKNOWN" : trim(item.getResourceCode());
|
String resourceCode = trim(item.getResourceCode()).isEmpty() ? "UNKNOWN" : trim(item.getResourceCode());
|
||||||
String resolvedPath = trim(item.getResolvedPath());
|
String resolvedPath = trim(item.getResolvedPath());
|
||||||
String message = "required backup resource is unavailable: code=" + resourceCode + ", reason=" + reason;
|
String message = "必需备份资源不可用:code=" + resourceCode + ", reason=" + reason;
|
||||||
if (!resolvedPath.isEmpty()) {
|
if (!resolvedPath.isEmpty()) {
|
||||||
message += ", path=" + resolvedPath;
|
message += ", path=" + resolvedPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService {
|
|||||||
resourceTaskAdmissionGuard.assertCanStartBackup();
|
resourceTaskAdmissionGuard.assertCanStartBackup();
|
||||||
// 资源备份必须基于“最近一次成功初始化”快照收集上下文,避免前端重新传一套可能失真的参数。
|
// 资源备份必须基于“最近一次成功初始化”快照收集上下文,避免前端重新传一套可能失真的参数。
|
||||||
InitTaskEntity initTask = initTaskRepository.findLatestByTaskTypeAndStatus("INIT", "SUCCESS")
|
InitTaskEntity initTask = initTaskRepository.findLatestByTaskTypeAndStatus("INIT", "SUCCESS")
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task is missing"));
|
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "缺少成功的初始化任务"));
|
||||||
|
|
||||||
// taskId 用于后台任务表和工作目录;backupId 写入备份包 manifest,
|
// taskId 用于后台任务表和工作目录;backupId 写入备份包 manifest,
|
||||||
// 恢复创建时用户需要确认同一个 backupId,避免把预检包和恢复包搞混。
|
// 恢复创建时用户需要确认同一个 backupId,避免把预检包和恢复包搞混。
|
||||||
@ -184,7 +184,7 @@ public class ResourceBackupServiceImpl implements ResourceBackupService {
|
|||||||
result.setContentLength(Files.size(packagePath));
|
result.setContentLength(Files.size(packagePath));
|
||||||
return result;
|
return result;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to read resource backup package");
|
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "读取资源备份包失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ public class ResourceRestoreServiceImpl implements ResourceRestoreService {
|
|||||||
ResourceRestorePrecheckResponse precheck = context.getResponse();
|
ResourceRestorePrecheckResponse precheck = context.getResponse();
|
||||||
validatePrecheck(precheck);
|
validatePrecheck(precheck);
|
||||||
if (!trim(request.getConfirmBackupId()).equals(trim(precheck.getBackupId()))) {
|
if (!trim(request.getConfirmBackupId()).equals(trim(precheck.getBackupId()))) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "confirmed backup id does not match precheck result");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "确认的备份ID与预检结果不匹配");
|
||||||
}
|
}
|
||||||
FileRecordEntity packageFile = fileRecordRepository.findByFileId(context.getFileId())
|
FileRecordEntity packageFile = fileRecordRepository.findByFileId(context.getFileId())
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份包文件不存在:" + trim(context.getFileId())));
|
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源备份包文件不存在:" + trim(context.getFileId())));
|
||||||
@ -223,7 +223,7 @@ public class ResourceRestoreServiceImpl implements ResourceRestoreService {
|
|||||||
}
|
}
|
||||||
// 兼容性和过期时间在创建恢复时再次校验,防止用户拿旧的或不匹配的预检结果继续恢复。
|
// 兼容性和过期时间在创建恢复时再次校验,防止用户拿旧的或不匹配的预检结果继续恢复。
|
||||||
if (!Boolean.TRUE.equals(precheck.getCompatible())) {
|
if (!Boolean.TRUE.equals(precheck.getCompatible())) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "resource restore precheck is not compatible");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "资源恢复预检结果不兼容");
|
||||||
}
|
}
|
||||||
String expiresAt = trim(precheck.getExpiresAt());
|
String expiresAt = trim(precheck.getExpiresAt());
|
||||||
if (!expiresAt.isEmpty()) {
|
if (!expiresAt.isEmpty()) {
|
||||||
|
|||||||
@ -42,7 +42,7 @@ public class TimeConfigServiceImpl implements TimeConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ZoneId.getAvailableZoneIds().contains(timezone)) {
|
if (!ZoneId.getAvailableZoneIds().contains(timezone)) {
|
||||||
throw new IllegalArgumentException( "Invalid timezone");
|
throw new IllegalArgumentException("时区不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
String datetime = request.getDatetime();
|
String datetime = request.getDatetime();
|
||||||
@ -131,7 +131,7 @@ public class TimeConfigServiceImpl implements TimeConfigService {
|
|||||||
|
|
||||||
String status = executeCommand("chronyc", "tracking");
|
String status = executeCommand("chronyc", "tracking");
|
||||||
if (!status.contains("Reference ID")) {
|
if (!status.contains("Reference ID")) {
|
||||||
throw new RuntimeException("NTP sync status check 执行失败");
|
throw new RuntimeException("NTP同步状态检查失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -141,10 +141,10 @@ public class TimeConfigServiceImpl implements TimeConfigService {
|
|||||||
executeCommand("systemctl", "restart", "chronyd");
|
executeCommand("systemctl", "restart", "chronyd");
|
||||||
}
|
}
|
||||||
} catch (Exception rollbackEx) {
|
} catch (Exception rollbackEx) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. CRITICAL: Rollback also 执行失败!");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "应用新的Chrony配置失败,且回滚也失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. Rolled back to original state.");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "应用新的Chrony配置失败,已回滚到原始状态");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("未知模式:" + mode);
|
throw new IllegalArgumentException("未知模式:" + mode);
|
||||||
|
|||||||
@ -78,7 +78,7 @@ public final class CryptoPayloadCodec {
|
|||||||
|
|
||||||
private static String requireText(String fieldName, String value) {
|
private static String requireText(String fieldName, String value) {
|
||||||
if (value == null || value.isBlank()) {
|
if (value == null || value.isBlank()) {
|
||||||
throw new IllegalArgumentException(fieldName + " must not be blank");
|
throw new IllegalArgumentException(fieldName + "不能为空");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -88,10 +88,10 @@ public final class CryptoPayloadCodec {
|
|||||||
try {
|
try {
|
||||||
decoded = HexFormat.of().parseHex(hex);
|
decoded = HexFormat.of().parseHex(hex);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new IllegalArgumentException(fieldName + " must be valid HEX", e);
|
throw new IllegalArgumentException(fieldName + "必须是有效HEX", e);
|
||||||
}
|
}
|
||||||
if (decoded.length != expectedBytes) {
|
if (decoded.length != expectedBytes) {
|
||||||
throw new IllegalArgumentException(fieldName + " length must be " + expectedBytes + " bytes");
|
throw new IllegalArgumentException(fieldName + "长度必须为" + expectedBytes + "字节");
|
||||||
}
|
}
|
||||||
System.arraycopy(decoded, 0, target, offset, decoded.length);
|
System.arraycopy(decoded, 0, target, offset, decoded.length);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,7 @@ public class FileServiceImpl implements FileService {
|
|||||||
Files.createDirectories(target.getParent());
|
Files.createDirectories(target.getParent());
|
||||||
file.transferTo(target);
|
file.transferTo(target);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to save upload file");
|
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "保存上传文件失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接落库存储绝对路径,避免后续再依赖可能变化的配置重新推导文件位置。
|
// 直接落库存储绝对路径,避免后续再依赖可能变化的配置重新推导文件位置。
|
||||||
@ -84,7 +84,7 @@ public class FileServiceImpl implements FileService {
|
|||||||
// 文件大小以磁盘上的真实文件为准,保证查询结果反映当前实际落盘状态。
|
// 文件大小以磁盘上的真实文件为准,保证查询结果反映当前实际落盘状态。
|
||||||
response.setSize(Files.exists(path) ? Files.size(path) : 0L);
|
response.setSize(Files.exists(path) ? Files.size(path) : 0L);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to read file detail");
|
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "读取文件详情失败");
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -387,7 +387,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
|
|||||||
String raw = firstNonBlank(configuredPath, defaultPath);
|
String raw = firstNonBlank(configuredPath, defaultPath);
|
||||||
Path path = Paths.get(raw).normalize().toAbsolutePath();
|
Path path = Paths.get(raw).normalize().toAbsolutePath();
|
||||||
if (!Files.exists(path) || !Files.isRegularFile(path)) {
|
if (!Files.exists(path) || !Files.isRegularFile(path)) {
|
||||||
throw new IllegalArgumentException(fieldName + " not found: " + path);
|
throw new IllegalArgumentException(fieldName + "不存在:" + path);
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@ -1427,7 +1427,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
|
|||||||
private void verifyCommon(Map<String, String> configMap, PlanContext context, List<String> failures) {
|
private void verifyCommon(Map<String, String> configMap, PlanContext context, List<String> failures) {
|
||||||
requireNotBlank(configMap, "MY_CIPSID_OR_BIC", failures);
|
requireNotBlank(configMap, "MY_CIPSID_OR_BIC", failures);
|
||||||
if (!normalize(unquote(configMap.get("MY_CIPSID_OR_BIC"))).equals(normalize(context.orgCode()))) {
|
if (!normalize(unquote(configMap.get("MY_CIPSID_OR_BIC"))).equals(normalize(context.orgCode()))) {
|
||||||
failures.add("MY_CIPSID_OR_BIC mismatch");
|
failures.add("MY_CIPSID_OR_BIC不匹配");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -258,7 +258,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
public CurrentInitConfigResponse loadCurrentInitConfig() {
|
public CurrentInitConfigResponse loadCurrentInitConfig() {
|
||||||
Optional<InitTaskEntity> latestInit = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_INIT, "SUCCESS");
|
Optional<InitTaskEntity> latestInit = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_INIT, "SUCCESS");
|
||||||
if (latestInit.isEmpty()) {
|
if (latestInit.isEmpty()) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device is not initialized");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设备未初始化");
|
||||||
}
|
}
|
||||||
return resolveCurrentInitConfigFromTask(latestInit.get());
|
return resolveCurrentInitConfigFromTask(latestInit.get());
|
||||||
}
|
}
|
||||||
@ -306,17 +306,17 @@ public class InitServiceImpl implements InitService {
|
|||||||
InitTaskStepEntity step = requireStep(taskId, stepNo);
|
InitTaskStepEntity step = requireStep(taskId, stepNo);
|
||||||
|
|
||||||
if (isBlank(step.getLogPath())) {
|
if (isBlank(step.getLogPath())) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "log file not generated for task step");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "任务步骤日志文件尚未生成");
|
||||||
}
|
}
|
||||||
|
|
||||||
Path logPath = Paths.get(step.getLogPath()).normalize().toAbsolutePath();
|
Path logPath = Paths.get(step.getLogPath()).normalize().toAbsolutePath();
|
||||||
Path logDir = Paths.get(initExecutorProperties.getLogDir()).normalize().toAbsolutePath();
|
Path logDir = Paths.get(initExecutorProperties.getLogDir()).normalize().toAbsolutePath();
|
||||||
// 只允许读取配置日志目录下的文件,防止路径穿越读取任意系统文件
|
// 只允许读取配置日志目录下的文件,防止路径穿越读取任意系统文件
|
||||||
if (!logPath.startsWith(logDir)) {
|
if (!logPath.startsWith(logDir)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "log path is out of allowed directory");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "日志路径超出允许目录");
|
||||||
}
|
}
|
||||||
if (!Files.exists(logPath) || !Files.isRegularFile(logPath)) {
|
if (!Files.exists(logPath) || !Files.isRegularFile(logPath)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "log file does not exist");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "日志文件不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -329,7 +329,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
response.setContent(content);
|
response.setContent(content);
|
||||||
return response;
|
return response;
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to read log file");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "读取日志文件失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,7 +352,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
InitTaskEntity task = requireTask(taskId);
|
InitTaskEntity task = requireTask(taskId);
|
||||||
List<InitTaskStepEntity> steps = initTaskStepRepository.findByTaskIdOrderByStepNo(taskId);
|
List<InitTaskStepEntity> steps = initTaskStepRepository.findByTaskIdOrderByStepNo(taskId);
|
||||||
if (steps.isEmpty()) {
|
if (steps.isEmpty()) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "init task has no steps: " + taskId);
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "初始化任务没有步骤:" + taskId);
|
||||||
}
|
}
|
||||||
if (steps.stream().allMatch(step -> "SUCCESS".equals(step.getStatus()))) {
|
if (steps.stream().allMatch(step -> "SUCCESS".equals(step.getStatus()))) {
|
||||||
task.setStatus("SUCCESS");
|
task.setStatus("SUCCESS");
|
||||||
@ -478,7 +478,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
try {
|
try {
|
||||||
return objectMapper.writeValueAsString(root);
|
return objectMapper.writeValueAsString(root);
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to serialize init plan");
|
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "序列化初始化计划失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,7 +501,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
try {
|
try {
|
||||||
return objectMapper.writeValueAsString(root);
|
return objectMapper.writeValueAsString(root);
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to serialize reset plan");
|
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "序列化重置计划失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,7 +587,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
|
|
||||||
private List<String> resetSteps(String productType, String mqType) {
|
private List<String> resetSteps(String productType, String mqType) {
|
||||||
if ("DIRECT".equals(productType)) {
|
if ("DIRECT".equals(productType)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "reset is not supported for current product type: " + productType);
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "当前产品类型不支持重置:" + productType);
|
||||||
}
|
}
|
||||||
return List.of(
|
return List.of(
|
||||||
"STOP_APPS",
|
"STOP_APPS",
|
||||||
@ -627,7 +627,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
private String resolveSupportedResetProductType() {
|
private String resolveSupportedResetProductType() {
|
||||||
String productType = resolvePresetProductType();
|
String productType = resolvePresetProductType();
|
||||||
if ("DIRECT".equals(productType)) {
|
if ("DIRECT".equals(productType)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "reset is not supported for current product type: " + productType);
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "当前产品类型不支持重置:" + productType);
|
||||||
}
|
}
|
||||||
return productType;
|
return productType;
|
||||||
}
|
}
|
||||||
@ -672,11 +672,11 @@ public class InitServiceImpl implements InitService {
|
|||||||
String productType = resolveSupportedResetProductType();
|
String productType = resolveSupportedResetProductType();
|
||||||
Optional<InitTaskEntity> latestInit = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_INIT, "SUCCESS");
|
Optional<InitTaskEntity> latestInit = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_INIT, "SUCCESS");
|
||||||
if (latestInit.isEmpty()) {
|
if (latestInit.isEmpty()) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device is not initialized, reset is forbidden");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设备未初始化,禁止重置");
|
||||||
}
|
}
|
||||||
Optional<InitTaskEntity> latestReset = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_RESET, "SUCCESS");
|
Optional<InitTaskEntity> latestReset = initTaskRepository.findLatestByTaskTypeAndStatus(TASK_TYPE_RESET, "SUCCESS");
|
||||||
if (latestReset.isPresent() && compareTaskRecency(latestReset.get(), latestInit.get()) >= 0) {
|
if (latestReset.isPresent() && compareTaskRecency(latestReset.get(), latestInit.get()) >= 0) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "device is not initialized, reset is forbidden");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设备未初始化,禁止重置");
|
||||||
}
|
}
|
||||||
|
|
||||||
ResolvedResetContext context = resolveResetContextFromInitTask(latestInit.get());
|
ResolvedResetContext context = resolveResetContextFromInitTask(latestInit.get());
|
||||||
@ -692,10 +692,10 @@ public class InitServiceImpl implements InitService {
|
|||||||
|
|
||||||
private ResolvedResetContext resolveResetContextFromInitTask(InitTaskEntity task) {
|
private ResolvedResetContext resolveResetContextFromInitTask(InitTaskEntity task) {
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task is missing");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "缺少成功的初始化任务");
|
||||||
}
|
}
|
||||||
if (isBlank(task.getInitPlanJson())) {
|
if (isBlank(task.getInitPlanJson())) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task snapshot is missing");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功初始化任务快照缺失");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
JsonNode root = objectMapper.readTree(task.getInitPlanJson());
|
JsonNode root = objectMapper.readTree(task.getInitPlanJson());
|
||||||
@ -707,20 +707,20 @@ public class InitServiceImpl implements InitService {
|
|||||||
String channelUsername = firstNonBlank(text(mq, "channelUsername"), text(request, "channelUsername"));
|
String channelUsername = firstNonBlank(text(mq, "channelUsername"), text(request, "channelUsername"));
|
||||||
|
|
||||||
if (isBlank(productType) || isBlank(orgCode) || isBlank(mqType)) {
|
if (isBlank(productType) || isBlank(orgCode) || isBlank(mqType)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task snapshot is incomplete");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功初始化任务快照不完整");
|
||||||
}
|
}
|
||||||
return new ResolvedResetContext(productType, normalize(orgCode), normalizeUpper(mqType), normalize(channelUsername), task.getTaskId());
|
return new ResolvedResetContext(productType, normalize(orgCode), normalizeUpper(mqType), normalize(channelUsername), task.getTaskId());
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse 成功ful init task snapshot");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "解析成功初始化任务快照失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CurrentInitConfigResponse resolveCurrentInitConfigFromTask(InitTaskEntity task) {
|
private CurrentInitConfigResponse resolveCurrentInitConfigFromTask(InitTaskEntity task) {
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task is missing");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "缺少成功的初始化任务");
|
||||||
}
|
}
|
||||||
if (isBlank(task.getInitPlanJson())) {
|
if (isBlank(task.getInitPlanJson())) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功ful init task snapshot is missing");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "成功初始化任务快照缺失");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
JsonNode root = objectMapper.readTree(task.getInitPlanJson());
|
JsonNode root = objectMapper.readTree(task.getInitPlanJson());
|
||||||
@ -747,7 +747,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
response.setCfmqFileNames(buildCfmqFileNames(mq, mqType));
|
response.setCfmqFileNames(buildCfmqFileNames(mq, mqType));
|
||||||
return response;
|
return response;
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to parse 成功ful init task snapshot");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "解析成功初始化任务快照失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -869,13 +869,13 @@ public class InitServiceImpl implements InitService {
|
|||||||
|
|
||||||
private static void requireAllowed(String field, String value, Set<String> allowed) {
|
private static void requireAllowed(String field, String value, Set<String> allowed) {
|
||||||
if (!allowed.contains(value)) {
|
if (!allowed.contains(value)) {
|
||||||
throw new IllegalArgumentException(field + " must be one of " + allowed);
|
throw new IllegalArgumentException(field + "必须是以下值之一:" + allowed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void requireNotBlank(String field, String value) {
|
private static void requireNotBlank(String field, String value) {
|
||||||
if (isBlank(value)) {
|
if (isBlank(value)) {
|
||||||
throw new IllegalArgumentException(field + " is required");
|
throw new IllegalArgumentException(field + "不能为空");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,7 +887,7 @@ public class InitServiceImpl implements InitService {
|
|||||||
|
|
||||||
private static void requireFileId(String field, String value) {
|
private static void requireFileId(String field, String value) {
|
||||||
if (isBlank(value) || !FILE_ID_PATTERN.matcher(normalize(value)).matches()) {
|
if (isBlank(value) || !FILE_ID_PATTERN.matcher(normalize(value)).matches()) {
|
||||||
throw new IllegalArgumentException(field + " must be a fileId");
|
throw new IllegalArgumentException(field + "必须是fileId");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -398,12 +398,12 @@ public class LmkServiceImpl implements LmkService {
|
|||||||
|
|
||||||
private byte[] decodeConfiguredPin(String fieldName, String base64Value) {
|
private byte[] decodeConfiguredPin(String fieldName, String base64Value) {
|
||||||
if (base64Value == null || base64Value.isBlank()) {
|
if (base64Value == null || base64Value.isBlank()) {
|
||||||
throw new IllegalStateException(fieldName + " is not configured");
|
throw new IllegalStateException(fieldName + "未配置");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Base64.getDecoder().decode(base64Value);
|
return Base64.getDecoder().decode(base64Value);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new IllegalStateException(fieldName + " is not valid Base64", e);
|
throw new IllegalStateException(fieldName + "不是有效Base64");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +417,7 @@ public class LmkServiceImpl implements LmkService {
|
|||||||
|
|
||||||
private Map<Integer, String> parseNativeComponentMap(byte[] componentBytes, String name) {
|
private Map<Integer, String> parseNativeComponentMap(byte[] componentBytes, String name) {
|
||||||
if (componentBytes.length % NATIVE_COMPONENT_COUNT != 0) {
|
if (componentBytes.length % NATIVE_COMPONENT_COUNT != 0) {
|
||||||
throw new IllegalStateException(name + " native component bytes length is invalid: " + componentBytes.length);
|
throw new IllegalStateException(name + "原生分量字节长度无效:" + componentBytes.length);
|
||||||
}
|
}
|
||||||
// 原生分量总长度必须能被 3 整除;每一段等长,依次映射为 1/2/3 号分量。
|
// 原生分量总长度必须能被 3 整除;每一段等长,依次映射为 1/2/3 号分量。
|
||||||
int componentLength = componentBytes.length / NATIVE_COMPONENT_COUNT;
|
int componentLength = componentBytes.length / NATIVE_COMPONENT_COUNT;
|
||||||
@ -440,18 +440,18 @@ public class LmkServiceImpl implements LmkService {
|
|||||||
private String requiredComponent(Map<Integer, String> componentMap, int componentIndex, String name) {
|
private String requiredComponent(Map<Integer, String> componentMap, int componentIndex, String name) {
|
||||||
String component = componentMap.get(componentIndex);
|
String component = componentMap.get(componentIndex);
|
||||||
if (component == null || component.isBlank()) {
|
if (component == null || component.isBlank()) {
|
||||||
throw new IllegalArgumentException(name + " component missing: " + componentIndex);
|
throw new IllegalArgumentException(name + "分量缺失:" + componentIndex);
|
||||||
}
|
}
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String requiredPacketComponent(Map<Integer, String> componentMap, int componentIndex, String name, int packetIndex) {
|
private String requiredPacketComponent(Map<Integer, String> componentMap, int componentIndex, String name, int packetIndex) {
|
||||||
if (componentMap == null) {
|
if (componentMap == null) {
|
||||||
throw new IllegalArgumentException(name + " component missing in packet " + packetIndex);
|
throw new IllegalArgumentException(name + "在分包" + packetIndex + "中缺少分量");
|
||||||
}
|
}
|
||||||
String component = componentMap.get(componentIndex);
|
String component = componentMap.get(componentIndex);
|
||||||
if (component == null || component.isBlank()) {
|
if (component == null || component.isBlank()) {
|
||||||
throw new IllegalArgumentException(name + " component " + componentIndex + " missing in packet " + packetIndex);
|
throw new IllegalArgumentException(name + "分量" + componentIndex + "在分包" + packetIndex + "中缺失");
|
||||||
}
|
}
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
@ -462,7 +462,7 @@ public class LmkServiceImpl implements LmkService {
|
|||||||
String packet1Overlap = requiredPacketComponent(packet1Components, NATIVE_COMPONENT_TWO, name, PACKET_INDEX_ONE);
|
String packet1Overlap = requiredPacketComponent(packet1Components, NATIVE_COMPONENT_TWO, name, PACKET_INDEX_ONE);
|
||||||
String packet2Overlap = requiredPacketComponent(packet2Components, NATIVE_COMPONENT_TWO, name, PACKET_INDEX_TWO);
|
String packet2Overlap = requiredPacketComponent(packet2Components, NATIVE_COMPONENT_TWO, name, PACKET_INDEX_TWO);
|
||||||
if (!packet1Overlap.equals(packet2Overlap)) {
|
if (!packet1Overlap.equals(packet2Overlap)) {
|
||||||
throw new IllegalArgumentException(name + " component 2 is inconsistent");
|
throw new IllegalArgumentException(name + "分量2不一致");
|
||||||
}
|
}
|
||||||
return packet1Overlap;
|
return packet1Overlap;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,7 +85,7 @@ public class ReplayProtectionServiceImpl implements ReplayProtectionService {
|
|||||||
|
|
||||||
var reloaded = replayNonceRepository.findByScopeAndPrincipalIdAndNonce(scope, principalId, nonce);
|
var reloaded = replayNonceRepository.findByScopeAndPrincipalIdAndNonce(scope, principalId, nonce);
|
||||||
if (reloaded.isEmpty()) {
|
if (reloaded.isEmpty()) {
|
||||||
throw new ReplayProtectionException("replay nonce claim 执行失败 without existing record");
|
throw new ReplayProtectionException("防重放nonce占用失败,且未找到已存在记录");
|
||||||
}
|
}
|
||||||
return buildReplayResult(request, reloaded.get());
|
return buildReplayResult(request, reloaded.get());
|
||||||
}
|
}
|
||||||
@ -170,7 +170,7 @@ public class ReplayProtectionServiceImpl implements ReplayProtectionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String buildMismatchDetail(ReplayCheckRequest request) {
|
private String buildMismatchDetail(ReplayCheckRequest request) {
|
||||||
return "request fingerprint mismatch: scope=" + request.getScope().name()
|
return "请求指纹不匹配:scope=" + request.getScope().name()
|
||||||
+ ", method=" + normalize(request.getRequestMethod())
|
+ ", method=" + normalize(request.getRequestMethod())
|
||||||
+ ", path=" + normalize(request.getRequestPath())
|
+ ", path=" + normalize(request.getRequestPath())
|
||||||
+ ", bodyHash=" + normalize(request.getBodyHash());
|
+ ", bodyHash=" + normalize(request.getBodyHash());
|
||||||
|
|||||||
@ -199,7 +199,7 @@ public class UpgradeTaskRunner {
|
|||||||
Path resolved = stagedDir.resolve(trim(scriptPath)).normalize().toAbsolutePath();
|
Path resolved = stagedDir.resolve(trim(scriptPath)).normalize().toAbsolutePath();
|
||||||
// 防止脚本路径通过 ../ 跳出升级包解压目录。
|
// 防止脚本路径通过 ../ 跳出升级包解压目录。
|
||||||
if (!resolved.startsWith(stagedDir) || !Files.exists(resolved)) {
|
if (!resolved.startsWith(stagedDir) || !Files.exists(resolved)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade " + phase + " script not found");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级" + phase + "脚本不存在");
|
||||||
}
|
}
|
||||||
Process process = new ProcessBuilder("bash", resolved.toString(), task.getTaskId())
|
Process process = new ProcessBuilder("bash", resolved.toString(), task.getTaskId())
|
||||||
.directory(stagedDir.toFile())
|
.directory(stagedDir.toFile())
|
||||||
@ -218,7 +218,7 @@ public class UpgradeTaskRunner {
|
|||||||
: "upgrade 成功, scheduling detached TMS restart");
|
: "upgrade 成功, scheduling detached TMS restart");
|
||||||
triggerDetachedTmsRestart(logPath, rollback);
|
triggerDetachedTmsRestart(logPath, rollback);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to schedule detached tms restart");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "调度TMS后台重启失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -68,11 +68,11 @@ public class UpgradePackageService {
|
|||||||
String targetVersion = normalize(manifest.getVersion());
|
String targetVersion = normalize(manifest.getVersion());
|
||||||
|
|
||||||
if (!currentVersion.isEmpty() && versionComparator.compare(targetVersion, currentVersion) < 0) {
|
if (!currentVersion.isEmpty() && versionComparator.compare(targetVersion, currentVersion) < 0) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "target version is lower than current version");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "目标版本低于当前版本");
|
||||||
}
|
}
|
||||||
String minCompatibleVersion = normalize(manifest.getMinCompatibleVersion());
|
String minCompatibleVersion = normalize(manifest.getMinCompatibleVersion());
|
||||||
if (!currentVersion.isEmpty() && !minCompatibleVersion.isEmpty() && versionComparator.compare(currentVersion, minCompatibleVersion) < 0) {
|
if (!currentVersion.isEmpty() && !minCompatibleVersion.isEmpty() && versionComparator.compare(currentVersion, minCompatibleVersion) < 0) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "current version does not satisfy minimum compatible version");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "当前版本不满足最低兼容版本要求");
|
||||||
}
|
}
|
||||||
|
|
||||||
UpgradePreviewResponse response = new UpgradePreviewResponse();
|
UpgradePreviewResponse response = new UpgradePreviewResponse();
|
||||||
@ -145,7 +145,7 @@ public class UpgradePackageService {
|
|||||||
String currentProductType = normalizeUpper(cisdPresetProperties.getProductType());
|
String currentProductType = normalizeUpper(cisdPresetProperties.getProductType());
|
||||||
String packageType = normalizeUpper(packageProductType);
|
String packageType = normalizeUpper(packageProductType);
|
||||||
if (!currentProductType.isEmpty() && !packageType.equals(currentProductType)) {
|
if (!currentProductType.isEmpty() && !packageType.equals(currentProductType)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package product type does not match current device");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包产品类型与当前设备不匹配");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ public class UpgradePackageService {
|
|||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级payload.zip不存在");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级payload.zip不存在");
|
||||||
}
|
}
|
||||||
if (!expected.equals(sm3Hex(payloadZip))) {
|
if (!expected.equals(sm3Hex(payloadZip))) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package payload sm3 mismatch");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包payload SM3摘要不匹配");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,7 @@ public class UpgradeService {
|
|||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), blankAs(preview.getBlockReason(), "升级预检不允许继续"));
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), blankAs(preview.getBlockReason(), "升级预检不允许继续"));
|
||||||
}
|
}
|
||||||
if (!trim(request.getTaskType()).equals(trim(preview.getTaskType()))) {
|
if (!trim(request.getTaskType()).equals(trim(preview.getTaskType()))) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "selected taskType does not match package taskType");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "选择的taskType与升级包taskType不匹配");
|
||||||
}
|
}
|
||||||
FileRecordEntity fileRecord = fileRecordRepository.findByFileId(request.getFileId().trim())
|
FileRecordEntity fileRecord = fileRecordRepository.findByFileId(request.getFileId().trim())
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包文件不存在:" + request.getFileId().trim()));
|
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包文件不存在:" + request.getFileId().trim()));
|
||||||
@ -148,7 +148,7 @@ public class UpgradeService {
|
|||||||
// 第一版按一体机单机串行升级处理,避免同时升级 TMS、收发器或固件造成状态不可控。
|
// 第一版按一体机单机串行升级处理,避免同时升级 TMS、收发器或固件造成状态不可控。
|
||||||
UpgradeTaskEntity runningTask = upgradeTaskRepository.findRunningTask().orElse(null);
|
UpgradeTaskEntity runningTask = upgradeTaskRepository.findRunningTask().orElse(null);
|
||||||
if (runningTask != null && !runningTask.getTaskId().equals(taskId)) {
|
if (runningTask != null && !runningTask.getTaskId().equals(taskId)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "another upgrade task is running");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "已有升级任务正在执行");
|
||||||
}
|
}
|
||||||
if (activeExecutions.containsKey(taskId)) {
|
if (activeExecutions.containsKey(taskId)) {
|
||||||
return toResponse(loadTask(taskId));
|
return toResponse(loadTask(taskId));
|
||||||
@ -179,7 +179,7 @@ public class UpgradeService {
|
|||||||
}
|
}
|
||||||
UpgradeTaskEntity runningTask = upgradeTaskRepository.findRunningTask().orElse(null);
|
UpgradeTaskEntity runningTask = upgradeTaskRepository.findRunningTask().orElse(null);
|
||||||
if (runningTask != null && !runningTask.getTaskId().equals(taskId)) {
|
if (runningTask != null && !runningTask.getTaskId().equals(taskId)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "another upgrade task is running");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "已有升级任务正在执行");
|
||||||
}
|
}
|
||||||
if (activeExecutions.containsKey(taskId)) {
|
if (activeExecutions.containsKey(taskId)) {
|
||||||
return toResponse(loadTask(taskId));
|
return toResponse(loadTask(taskId));
|
||||||
@ -235,16 +235,16 @@ public class UpgradeService {
|
|||||||
UpgradeTaskEntity task = loadTask(taskId);
|
UpgradeTaskEntity task = loadTask(taskId);
|
||||||
String logPath = trim(task.getDetailLogPath());
|
String logPath = trim(task.getDetailLogPath());
|
||||||
if (logPath.isEmpty()) {
|
if (logPath.isEmpty()) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade log not generated");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级日志尚未生成");
|
||||||
}
|
}
|
||||||
Path path = Path.of(logPath).normalize().toAbsolutePath();
|
Path path = Path.of(logPath).normalize().toAbsolutePath();
|
||||||
Path allowedRoot = Path.of(trim(upgradeProperties.getLogDir())).normalize().toAbsolutePath();
|
Path allowedRoot = Path.of(trim(upgradeProperties.getLogDir())).normalize().toAbsolutePath();
|
||||||
// 日志路径必须落在升级日志根目录下,防止通过篡改任务记录读取任意文件。
|
// 日志路径必须落在升级日志根目录下,防止通过篡改任务记录读取任意文件。
|
||||||
if (!path.startsWith(allowedRoot)) {
|
if (!path.startsWith(allowedRoot)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade log path is out of allowed directory");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级日志路径超出允许目录");
|
||||||
}
|
}
|
||||||
if (!Files.exists(path)) {
|
if (!Files.exists(path)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade log file does not exist");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级日志文件不存在");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
UpgradeLogResponse response = new UpgradeLogResponse();
|
UpgradeLogResponse response = new UpgradeLogResponse();
|
||||||
@ -253,7 +253,7 @@ public class UpgradeService {
|
|||||||
response.setContent(Files.readString(path));
|
response.setContent(Files.readString(path));
|
||||||
return response;
|
return response;
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to read upgrade log");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "读取升级日志失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +304,7 @@ public class UpgradeService {
|
|||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to prepare upgrade log file");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "准备升级日志文件失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat
|
|||||||
|
|
||||||
// 第一版升级包仅对 manifest.json 做离线软验签,避免在包内脚本执行前信任未授权包。
|
// 第一版升级包仅对 manifest.json 做离线软验签,避免在包内脚本执行前信任未授权包。
|
||||||
PublicKey publicKey = loadPublicKey();
|
PublicKey publicKey = loadPublicKey();
|
||||||
byte[] manifestBytes = readBytes(manifestPath, "failed to read manifest for signature verification");
|
byte[] manifestBytes = readBytes(manifestPath, "读取验签manifest失败");
|
||||||
byte[] signatureBytes = loadSignature(signaturePath);
|
byte[] signatureBytes = loadSignature(signaturePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -59,14 +59,14 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat
|
|||||||
} catch (BizException ex) {
|
} catch (BizException ex) {
|
||||||
throw ex;
|
throw ex;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package signature verification 执行失败");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包签名验签失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PublicKey loadPublicKey() {
|
private PublicKey loadPublicKey() {
|
||||||
Path pemPath = Path.of(normalize(upgradeProperties.getSignaturePublicKeyPemPath())).normalize().toAbsolutePath();
|
Path pemPath = Path.of(normalize(upgradeProperties.getSignaturePublicKeyPemPath())).normalize().toAbsolutePath();
|
||||||
if (normalize(upgradeProperties.getSignaturePublicKeyPemPath()).isEmpty()) {
|
if (normalize(upgradeProperties.getSignaturePublicKeyPemPath()).isEmpty()) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade signature public key pem path not configured");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级签名公钥PEM路径未配置");
|
||||||
}
|
}
|
||||||
if (!Files.exists(pemPath)) {
|
if (!Files.exists(pemPath)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级签名公钥PEM文件不存在");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级签名公钥PEM文件不存在");
|
||||||
@ -84,14 +84,14 @@ public class SoftUpgradePackageSignatureVerifier implements UpgradePackageSignat
|
|||||||
} catch (BizException ex) {
|
} catch (BizException ex) {
|
||||||
throw ex;
|
throw ex;
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to read upgrade signature public key pem");
|
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "读取升级签名公钥PEM失败");
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级签名公钥PEM无效");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级签名公钥PEM无效");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] loadSignature(Path signaturePath) {
|
private byte[] loadSignature(Path signaturePath) {
|
||||||
byte[] raw = readBytes(signaturePath, "failed to read upgrade signature file");
|
byte[] raw = readBytes(signaturePath, "读取升级签名文件失败");
|
||||||
if (raw.length == 0) {
|
if (raw.length == 0) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包签名文件为空");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包签名文件为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ public class UpgradePackageStagingService {
|
|||||||
unzip(packagePath, taskDir, "");
|
unzip(packagePath, taskDir, "");
|
||||||
return taskDir;
|
return taskDir;
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to stage upgrade package");
|
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "暂存升级包失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ public class UpgradePackageStagingService {
|
|||||||
deleteDirectoryIfExists(root.resolve("payload"));
|
deleteDirectoryIfExists(root.resolve("payload"));
|
||||||
unzip(payloadZip, root, "payload/");
|
unzip(payloadZip, root, "payload/");
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to extract upgrade payload.zip");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "解压升级payload.zip失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,12 +56,12 @@ public class UpgradePackageStagingService {
|
|||||||
while ((entry = zipInputStream.getNextEntry()) != null) {
|
while ((entry = zipInputStream.getNextEntry()) != null) {
|
||||||
String entryName = entry.getName();
|
String entryName = entry.getName();
|
||||||
if (!requiredEntryPrefix.isEmpty() && !entryName.startsWith(requiredEntryPrefix)) {
|
if (!requiredEntryPrefix.isEmpty() && !entryName.startsWith(requiredEntryPrefix)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "invalid upgrade payload entry");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级payload条目无效");
|
||||||
}
|
}
|
||||||
Path target = targetDir.resolve(entry.getName()).normalize().toAbsolutePath();
|
Path target = targetDir.resolve(entry.getName()).normalize().toAbsolutePath();
|
||||||
// 防止恶意 zip 条目通过 ../ 写出 staging 目录。
|
// 防止恶意 zip 条目通过 ../ 写出 staging 目录。
|
||||||
if (!target.startsWith(targetDir)) {
|
if (!target.startsWith(targetDir)) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "invalid upgrade package entry");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "升级包条目无效");
|
||||||
}
|
}
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
Files.createDirectories(target);
|
Files.createDirectories(target);
|
||||||
|
|||||||
@ -58,7 +58,7 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor {
|
|||||||
String timestamp = trimHeader(request, TIMESTAMP_HEADER);
|
String timestamp = trimHeader(request, TIMESTAMP_HEADER);
|
||||||
String nonce = trimHeader(request, NONCE_HEADER);
|
String nonce = trimHeader(request, NONCE_HEADER);
|
||||||
if (isBlank(timestamp) || isBlank(nonce)) {
|
if (isBlank(timestamp) || isBlank(nonce)) {
|
||||||
writeJson(response, HttpServletResponse.SC_BAD_REQUEST, ErrorCode.BAD_REQUEST.getCode(), "missing replay protection headers");
|
writeJson(response, HttpServletResponse.SC_BAD_REQUEST, ErrorCode.BAD_REQUEST.getCode(), "防重放请求头缺失");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor {
|
|||||||
try {
|
try {
|
||||||
requestTimestamp = Long.parseLong(timestamp);
|
requestTimestamp = Long.parseLong(timestamp);
|
||||||
} catch (NumberFormatException ex) {
|
} catch (NumberFormatException ex) {
|
||||||
writeJson(response, HttpServletResponse.SC_BAD_REQUEST, ErrorCode.BAD_REQUEST.getCode(), "invalid replay timestamp");
|
writeJson(response, HttpServletResponse.SC_BAD_REQUEST, ErrorCode.BAD_REQUEST.getCode(), "防重放时间戳格式无效");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor {
|
|||||||
try {
|
try {
|
||||||
ReplayCheckResult result = replayProtectionService.check(replayRequest);
|
ReplayCheckResult result = replayProtectionService.check(replayRequest);
|
||||||
if (result.isReplayed()) {
|
if (result.isReplayed()) {
|
||||||
writeJson(response, HttpServletResponse.SC_CONFLICT, ErrorCode.CONFLICT.getCode(), "replayed nonce");
|
writeJson(response, HttpServletResponse.SC_CONFLICT, ErrorCode.CONFLICT.getCode(), "nonce已被重放");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (ReplayProtectionException ex) {
|
} catch (ReplayProtectionException ex) {
|
||||||
@ -153,6 +153,6 @@ public class InternalApiReplayInterceptor implements HandlerInterceptor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, ErrorCode.SERVICE_UNAVAILABLE.getCode(),
|
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, ErrorCode.SERVICE_UNAVAILABLE.getCode(),
|
||||||
"replay protection unavailable");
|
"防重放校验服务不可用");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,14 +51,14 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
|||||||
String signature = trimHeader(request, SIGNATURE_HEADER);
|
String signature = trimHeader(request, SIGNATURE_HEADER);
|
||||||
|
|
||||||
if (isBlank(appId) || isBlank(timestamp) || isBlank(nonce) || isBlank(signature)) {
|
if (isBlank(appId) || isBlank(timestamp) || isBlank(nonce) || isBlank(signature)) {
|
||||||
writeUnauthorized(response, "missing openapi auth headers");
|
writeUnauthorized(response, "OpenAPI认证请求头缺失");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> clients = securityProperties.getOpenapi().getClients();
|
Map<String, String> clients = securityProperties.getOpenapi().getClients();
|
||||||
String appSecret = clients.get(appId);
|
String appSecret = clients.get(appId);
|
||||||
if (isBlank(appSecret)) {
|
if (isBlank(appSecret)) {
|
||||||
writeUnauthorized(response, "unknown app id");
|
writeUnauthorized(response, "未知的appId");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
|||||||
try {
|
try {
|
||||||
requestEpoch = Long.parseLong(timestamp);
|
requestEpoch = Long.parseLong(timestamp);
|
||||||
} catch (NumberFormatException ex) {
|
} catch (NumberFormatException ex) {
|
||||||
writeUnauthorized(response, "invalid timestamp");
|
writeUnauthorized(response, "时间戳格式无效");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
|||||||
// 先完成签名校验,再占用 nonce,避免伪造请求提前消耗合法 nonce。
|
// 先完成签名校验,再占用 nonce,避免伪造请求提前消耗合法 nonce。
|
||||||
ReplayCheckResult result = replayProtectionService.check(buildReplayRequest(request, appId, nonce, requestEpoch));
|
ReplayCheckResult result = replayProtectionService.check(buildReplayRequest(request, appId, nonce, requestEpoch));
|
||||||
if (result.isReplayed()) {
|
if (result.isReplayed()) {
|
||||||
writeJson(response, HttpServletResponse.SC_CONFLICT, ErrorCode.CONFLICT.getCode(), "replayed nonce");
|
writeJson(response, HttpServletResponse.SC_CONFLICT, ErrorCode.CONFLICT.getCode(), "nonce已被重放");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (ReplayProtectionException ex) {
|
} catch (ReplayProtectionException ex) {
|
||||||
@ -159,6 +159,6 @@ public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE,
|
writeJson(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE,
|
||||||
ErrorCode.SERVICE_UNAVAILABLE.getCode(), "replay protection unavailable");
|
ErrorCode.SERVICE_UNAVAILABLE.getCode(), "防重放校验服务不可用");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,15 @@ class ChineseErrorMessageContractTest {
|
|||||||
private static final Pattern VALIDATION_MESSAGE = Pattern.compile(
|
private static final Pattern VALIDATION_MESSAGE = Pattern.compile(
|
||||||
"@(?:NotNull|NotBlank|NotEmpty|Size|Pattern|Min|Max|Positive|AssertTrue)\\([^\\n]*message\\s*=\\s*\"([^\"]+)\""
|
"@(?:NotNull|NotBlank|NotEmpty|Size|Pattern|Min|Max|Positive|AssertTrue)\\([^\\n]*message\\s*=\\s*\"([^\"]+)\""
|
||||||
);
|
);
|
||||||
|
private static final Pattern USER_FACING_STATEMENT = Pattern.compile(
|
||||||
|
"(?s)(?:new\\s+(?:BizException|IllegalArgumentException|IllegalStateException|RuntimeException|ReplayProtectionException)\\([^;]*;"
|
||||||
|
+ "|ApiResponse\\.fail\\([^;]*;"
|
||||||
|
+ "|write(?:Unauthorized|Forbidden|SessionInvalid|Json)\\([^;]*;"
|
||||||
|
+ "|InitStepExecutionResult\\.failure\\([^;]*;"
|
||||||
|
+ "|new\\s+PcieCryptoException\\([^;]*;"
|
||||||
|
+ "|new\\s+RequiredBackupResourceMissingException\\([^;]*;)"
|
||||||
|
);
|
||||||
|
private static final Pattern STRING_LITERAL_WITH_ENGLISH = Pattern.compile("\"([^\"]*[A-Za-z][^\"]*)\"");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void productionErrorMessagesShouldBeChinese() throws IOException {
|
void productionErrorMessagesShouldBeChinese() throws IOException {
|
||||||
@ -41,7 +50,9 @@ class ChineseErrorMessageContractTest {
|
|||||||
|
|
||||||
private static void collectViolations(Path path, List<String> violations) {
|
private static void collectViolations(Path path, List<String> violations) {
|
||||||
try {
|
try {
|
||||||
List<String> lines = Files.readAllLines(path);
|
String content = Files.readString(path);
|
||||||
|
collectStatements(content, path, violations);
|
||||||
|
List<String> lines = content.lines().toList();
|
||||||
for (int index = 0; index < lines.size(); index++) {
|
for (int index = 0; index < lines.size(); index++) {
|
||||||
String line = lines.get(index);
|
String line = lines.get(index);
|
||||||
if (line.trim().startsWith("//") || line.contains("log.")) {
|
if (line.trim().startsWith("//") || line.contains("log.")) {
|
||||||
@ -55,6 +66,48 @@ class ChineseErrorMessageContractTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void collectStatements(String content, Path path, List<String> violations) {
|
||||||
|
Matcher statementMatcher = USER_FACING_STATEMENT.matcher(content);
|
||||||
|
while (statementMatcher.find()) {
|
||||||
|
String statement = statementMatcher.group();
|
||||||
|
if (statement.trim().startsWith("//") || statement.contains("log.")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (CHINESE.matcher(statement).find()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Matcher literalMatcher = STRING_LITERAL_WITH_ENGLISH.matcher(statement);
|
||||||
|
int literalIndex = 0;
|
||||||
|
while (literalMatcher.find()) {
|
||||||
|
String message = literalMatcher.group(1);
|
||||||
|
if (isNonMessagePcieApiName(statement, message, literalIndex)) {
|
||||||
|
literalIndex++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!CHINESE.matcher(message).find()) {
|
||||||
|
violations.add(path + ":" + lineNumber(content, statementMatcher.start() + literalMatcher.start()) + " -> " + message);
|
||||||
|
}
|
||||||
|
literalIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNonMessagePcieApiName(String statement, String message, int literalIndex) {
|
||||||
|
return statement.contains("PcieCryptoException(")
|
||||||
|
&& literalIndex == 0
|
||||||
|
&& message.matches("[A-Za-z0-9_./-]+");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int lineNumber(String content, int offset) {
|
||||||
|
int line = 1;
|
||||||
|
for (int i = 0; i < offset; i++) {
|
||||||
|
if (content.charAt(i) == '\n') {
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
private static void collect(String line, Pattern pattern, Path path, int index, List<String> violations) {
|
private static void collect(String line, Pattern pattern, Path path, int index, List<String> violations) {
|
||||||
Matcher matcher = pattern.matcher(line);
|
Matcher matcher = pattern.matcher(line);
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class GlobalExceptionHandlerTest {
|
|||||||
Assertions.assertNotNull(response.getBody());
|
Assertions.assertNotNull(response.getBody());
|
||||||
Assertions.assertFalse(response.getBody().isSuccess());
|
Assertions.assertFalse(response.getBody().isSuccess());
|
||||||
Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getBody().getCode());
|
Assertions.assertEquals(ErrorCode.VALIDATE_FAILED.getCode(), response.getBody().getCode());
|
||||||
Assertions.assertEquals("failed to parse multipart request", response.getBody().getMsg());
|
Assertions.assertEquals("解析 multipart 上传请求失败", response.getBody().getMsg());
|
||||||
Assertions.assertEquals("/api/v1/files/upload", response.getBody().getPath());
|
Assertions.assertEquals("/api/v1/files/upload", response.getBody().getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ class GlobalExceptionHandlerTest {
|
|||||||
Assertions.assertNotNull(response.getBody());
|
Assertions.assertNotNull(response.getBody());
|
||||||
Assertions.assertFalse(response.getBody().isSuccess());
|
Assertions.assertFalse(response.getBody().isSuccess());
|
||||||
Assertions.assertEquals(ErrorCode.NOT_FOUND.getCode(), response.getBody().getCode());
|
Assertions.assertEquals(ErrorCode.NOT_FOUND.getCode(), response.getBody().getCode());
|
||||||
Assertions.assertEquals("resource not found", response.getBody().getMsg());
|
Assertions.assertEquals("资源不存在", response.getBody().getMsg());
|
||||||
Assertions.assertEquals("/missing.js", response.getBody().getPath());
|
Assertions.assertEquals("/missing.js", response.getBody().getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,7 +98,7 @@ class AuthAdminServiceTest {
|
|||||||
));
|
));
|
||||||
|
|
||||||
Assertions.assertEquals(ErrorCode.UNAUTHORIZED.getCode(), exception.getCode());
|
Assertions.assertEquals(ErrorCode.UNAUTHORIZED.getCode(), exception.getCode());
|
||||||
Assertions.assertEquals("old password is incorrect", exception.getMessage());
|
Assertions.assertEquals("旧密码错误", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -140,7 +140,7 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request));
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request));
|
||||||
|
|
||||||
Assertions.assertEquals("full account is locked", exception.getMessage());
|
Assertions.assertEquals("完整账号已锁定", exception.getMessage());
|
||||||
AuthFullAccountEntity locked = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
AuthFullAccountEntity locked = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 1).orElseThrow();
|
||||||
AuthFullAccountEntity untouched = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow();
|
AuthFullAccountEntity untouched = accounts.findByRoleCodeAndUid(RoleCode.SUPER_ADMIN.getCode(), 2).orElseThrow();
|
||||||
Assertions.assertEquals(RoleAccountStatus.LOCKED.name(), locked.getStatus());
|
Assertions.assertEquals(RoleAccountStatus.LOCKED.name(), locked.getStatus());
|
||||||
|
|||||||
@ -8,8 +8,8 @@ class RequiredBackupResourceMissingExceptionTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldExposeMessage() {
|
void shouldExposeMessage() {
|
||||||
RequiredBackupResourceMissingException exception =
|
RequiredBackupResourceMissingException exception =
|
||||||
new RequiredBackupResourceMissingException("required backup resource is unavailable");
|
new RequiredBackupResourceMissingException("必需备份资源不可用");
|
||||||
|
|
||||||
Assertions.assertEquals("required backup resource is unavailable", exception.getMessage());
|
Assertions.assertEquals("必需备份资源不可用", exception.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,7 +98,7 @@ class ResourceBackupServiceTest {
|
|||||||
Mockito.same(initTask),
|
Mockito.same(initTask),
|
||||||
Mockito.eq(discoveredItems),
|
Mockito.eq(discoveredItems),
|
||||||
Mockito.any(CreateResourceBackupRequest.class)
|
Mockito.any(CreateResourceBackupRequest.class)
|
||||||
)).thenThrow(new RequiredBackupResourceMissingException("required backup resource is unavailable: code=TMS_CONFIG"));
|
)).thenThrow(new RequiredBackupResourceMissingException("必需备份资源不可用:code=TMS_CONFIG"));
|
||||||
|
|
||||||
ResourceBackupService service = new ResourceBackupServiceImpl(
|
ResourceBackupService service = new ResourceBackupServiceImpl(
|
||||||
initTaskRepository,
|
initTaskRepository,
|
||||||
|
|||||||
@ -101,7 +101,7 @@ class UpgradePackageServiceTest {
|
|||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
||||||
Assertions.assertEquals(ErrorCode.BIZ_ERROR.getCode(), exception.getCode());
|
Assertions.assertEquals(ErrorCode.BIZ_ERROR.getCode(), exception.getCode());
|
||||||
Assertions.assertEquals("upgrade package product type does not match current device", exception.getMessage());
|
Assertions.assertEquals("升级包产品类型与当前设备不匹配", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -124,7 +124,7 @@ class UpgradePackageServiceTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
||||||
Assertions.assertEquals("target version is lower than current version", exception.getMessage());
|
Assertions.assertEquals("目标版本低于当前版本", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -147,7 +147,7 @@ class UpgradePackageServiceTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
||||||
Assertions.assertEquals("current version does not satisfy minimum compatible version", exception.getMessage());
|
Assertions.assertEquals("当前版本不满足最低兼容版本要求", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -210,7 +210,7 @@ class UpgradePackageServiceTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
||||||
Assertions.assertEquals("upgrade package payload sm3 mismatch", exception.getMessage());
|
Assertions.assertEquals("升级包payload SM3摘要不匹配", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -108,7 +108,7 @@ class UpgradeServiceTest {
|
|||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.createTask(request));
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.createTask(request));
|
||||||
|
|
||||||
Assertions.assertEquals("selected taskType does not match package taskType", exception.getMessage());
|
Assertions.assertEquals("选择的taskType与升级包taskType不匹配", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -129,7 +129,7 @@ class UpgradeServiceTest {
|
|||||||
|
|
||||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.executeTask("UPG-001"));
|
BizException exception = Assertions.assertThrows(BizException.class, () -> service.executeTask("UPG-001"));
|
||||||
|
|
||||||
Assertions.assertEquals("another upgrade task is running", exception.getMessage());
|
Assertions.assertEquals("已有升级任务正在执行", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -99,7 +99,7 @@ class InternalApiReplayInterceptorTest {
|
|||||||
|
|
||||||
Assertions.assertFalse(allowed);
|
Assertions.assertFalse(allowed);
|
||||||
Assertions.assertEquals(400, response.getStatus());
|
Assertions.assertEquals(400, response.getStatus());
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("missing replay protection headers"));
|
Assertions.assertTrue(response.getContentAsString().contains("防重放请求头缺失"));
|
||||||
Mockito.verifyNoInteractions(replayProtectionService);
|
Mockito.verifyNoInteractions(replayProtectionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ class InternalApiReplayInterceptorTest {
|
|||||||
|
|
||||||
Assertions.assertFalse(allowed);
|
Assertions.assertFalse(allowed);
|
||||||
Assertions.assertEquals(409, response.getStatus());
|
Assertions.assertEquals(409, response.getStatus());
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("replayed nonce"));
|
Assertions.assertTrue(response.getContentAsString().contains("nonce已被重放"));
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.CONFLICT.getCode()));
|
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.CONFLICT.getCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ class InternalApiReplayInterceptorTest {
|
|||||||
|
|
||||||
Assertions.assertFalse(allowed);
|
Assertions.assertFalse(allowed);
|
||||||
Assertions.assertEquals(503, response.getStatus());
|
Assertions.assertEquals(503, response.getStatus());
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("replay protection unavailable"));
|
Assertions.assertTrue(response.getContentAsString().contains("防重放校验服务不可用"));
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SERVICE_UNAVAILABLE.getCode()));
|
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SERVICE_UNAVAILABLE.getCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -117,7 +117,7 @@ class OpenApiSignAuthInterceptorTest {
|
|||||||
|
|
||||||
Assertions.assertFalse(allowed);
|
Assertions.assertFalse(allowed);
|
||||||
Assertions.assertEquals(409, response.getStatus());
|
Assertions.assertEquals(409, response.getStatus());
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("replayed nonce"));
|
Assertions.assertTrue(response.getContentAsString().contains("nonce已被重放"));
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.CONFLICT.getCode()));
|
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.CONFLICT.getCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ class OpenApiSignAuthInterceptorTest {
|
|||||||
|
|
||||||
Assertions.assertFalse(allowed);
|
Assertions.assertFalse(allowed);
|
||||||
Assertions.assertEquals(503, response.getStatus());
|
Assertions.assertEquals(503, response.getStatus());
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("replay protection unavailable"));
|
Assertions.assertTrue(response.getContentAsString().contains("防重放校验服务不可用"));
|
||||||
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SERVICE_UNAVAILABLE.getCode()));
|
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SERVICE_UNAVAILABLE.getCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user