feat: refactor message handling to use MessageName constants for improved internationalisation support

This commit is contained in:
siujamo
2026-03-24 12:23:13 +08:00
parent 776ddd28c1
commit 08c18fea90
33 changed files with 391 additions and 152 deletions
@@ -4,8 +4,7 @@ import com.onixbyte.helix.shared.AssetPrefix;
import com.onixbyte.helix.domain.web.response.FileUploadResponse;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.service.AssetService;
import com.onixbyte.helix.shared.Message;
import com.onixbyte.helix.utils.MessageUtil;
import com.onixbyte.helix.shared.MessageName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,15 +26,10 @@ public class AssetController {
private static final Logger log = LoggerFactory.getLogger(AssetController.class);
private final AssetService assetService;
private final MessageUtil messageUtil;
@Autowired
public AssetController(
AssetService assetService,
MessageUtil messageUtil
) {
public AssetController(AssetService assetService) {
this.assetService = assetService;
this.messageUtil = messageUtil;
}
/**
@@ -50,7 +44,7 @@ public class AssetController {
) {
try {
if (file.isEmpty()) {
throw new BizException(HttpStatus.BAD_REQUEST, messageUtil.getMessage(Message.ASSET_NOT_EMPTY));
throw new BizException(HttpStatus.BAD_REQUEST, MessageName.ASSET_NOT_EMPTY);
}
var fileUrl = assetService.uploadFile(AssetPrefix.UPLOADS, file);
@@ -63,10 +57,13 @@ public class AssetController {
file.getSize(),
fileUrl
));
} catch (BizException ex) {
throw ex;
} catch (Exception e) {
log.error("File upload failed: {}", e.getMessage(), e);
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
"Failed upload file: " + e.getMessage());
MessageName.ASSET_UPLOAD_FAILED,
e.getMessage());
}
}
@@ -5,7 +5,7 @@ import com.onixbyte.helix.domain.web.request.AuthorityRequest;
import com.onixbyte.helix.domain.web.request.QueryAuthorityRequest;
import com.onixbyte.helix.domain.web.response.ActionResponse;
import com.onixbyte.helix.service.AuthorityService;
import com.onixbyte.helix.shared.Message;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -79,6 +79,6 @@ public class AuthorityController {
@DeleteMapping("/{authorityId:\\d+}")
public ActionResponse deleteAuthority(@PathVariable Long authorityId) {
var name = authorityService.deleteAuthority(authorityId);
return ActionResponse.success(messageUtil.getMessage(Message.AUTHORITY_DELETED, name));
return ActionResponse.success(messageUtil.getMessage(MessageName.AUTHORITY_DELETED, name));
}
}
@@ -2,6 +2,7 @@ package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.web.response.BizExceptionResponse;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.utils.MessageUtil;
import jakarta.validation.ConstraintViolationException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
@@ -27,12 +28,22 @@ import java.util.stream.Collectors;
@RestControllerAdvice
public class ExceptionController {
private final MessageUtil messageUtil;
public ExceptionController(MessageUtil messageUtil) {
this.messageUtil = messageUtil;
}
@ExceptionHandler(BizException.class)
public ResponseEntity<BizExceptionResponse> handleBizException(BizException ex) {
var message = ex.getMessageCode() == null
? ex.getMessage()
: messageUtil.getMessage(ex.getMessageCode(), ex.getMessageArgs());
return ResponseEntity.status(ex.getStatus())
.body(new BizExceptionResponse(
LocalDateTime.now(),
ex.getMessage())
message)
);
}
@@ -1,15 +1,16 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Role;
import com.onixbyte.helix.domain.web.request.AddRoleRequest;
import com.onixbyte.helix.domain.web.request.EditRoleRequest;
import com.onixbyte.helix.domain.web.request.QueryRoleRequest;
import com.onixbyte.helix.domain.web.request.RoleRequest;
import com.onixbyte.helix.domain.web.response.ActionResponse;
import com.onixbyte.helix.service.RoleService;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -24,10 +25,12 @@ import org.springframework.web.bind.annotation.*;
public class RoleController {
private final RoleService roleService;
private final MessageUtil messageUtil;
@Autowired
public RoleController(RoleService roleService) {
public RoleController(RoleService roleService, MessageUtil messageUtil) {
this.roleService = roleService;
this.messageUtil = messageUtil;
}
@GetMapping
@@ -41,20 +44,21 @@ public class RoleController {
}
@PostMapping
public ResponseEntity<Void> addRole(@Validated @RequestBody AddRoleRequest request) {
roleService.addRole(request);
return ResponseEntity.ok(null);
public Role addRole(@Validated @RequestBody RoleRequest request) {
return roleService.addRole(request);
}
@PutMapping
public ResponseEntity<Void> editRole(@Validated @RequestBody EditRoleRequest request) {
roleService.editRole(request);
return ResponseEntity.ok(null);
@PutMapping("/{id:\\d+}")
public Role editRole(
@PathVariable Long id,
@Validated @RequestBody RoleRequest request
) {
return roleService.editRole(id, request);
}
@DeleteMapping("/{id:\\d+}")
public ResponseEntity<Void> deleteRole(@PathVariable Long id) {
roleService.deleteRole(id);
return ResponseEntity.ok(null);
public ActionResponse deleteRole(@PathVariable Long id) {
var name = roleService.deleteRole(id);
return ActionResponse.success(messageUtil.getMessage(MessageName.ROLE_DELETED, name));
}
}
@@ -7,7 +7,7 @@ import com.onixbyte.helix.domain.web.request.ResetPasswordRequest;
import com.onixbyte.helix.domain.web.response.ActionResponse;
import com.onixbyte.helix.domain.web.response.UserDetailResponse;
import com.onixbyte.helix.service.UserService;
import com.onixbyte.helix.shared.Message;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@@ -104,7 +104,7 @@ public class UserController {
@Validated @RequestBody ResetPasswordRequest request
) {
userService.resetPassword(id, request);
return ActionResponse.success(messageUtil.getMessage(Message.USER_PASSWORD_RESET_SUCCESS));
return ActionResponse.success(messageUtil.getMessage(MessageName.USER_PASSWORD_RESET_SUCCESS));
}
/**
@@ -117,6 +117,6 @@ public class UserController {
@DeleteMapping("/{userId:\\d+}")
public ActionResponse deleteUser(@PathVariable Long userId) {
userService.deleteUser(userId);
return ActionResponse.success(messageUtil.getMessage(Message.USER_DELETED));
return ActionResponse.success(messageUtil.getMessage(MessageName.USER_DELETED, userId));
}
}
@@ -1,21 +0,0 @@
package com.onixbyte.helix.domain.web.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
public record AddRoleRequest(
@NotBlank(message = "角色名称不能为空")
String name,
@NotBlank(message = "角色编码不能为空")
String code,
@NotNull(message = "排序编号不能为空")
Integer sort,
Boolean defaultValue,
String description,
@Pattern(
regexp = "^(ACTIVE|INACTIVE)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 其中之一")
String status
) {
}
@@ -1,16 +1,17 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.enumeration.UserStatus;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
public record AddUserRequest(
@NotBlank(message = "Username cannot be empty.")
@NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_USERNAME_NOT_EMPTY + "}")
String username,
@NotBlank(message = "Password cannot be empty.")
@NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_PASSWORD_NOT_EMPTY + "}")
String password,
@NotBlank(message = "Full name cannot be empty.")
@NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_FULL_NAME_NOT_EMPTY + "}")
String fullName,
String email,
String regionAbbreviation,
@@ -1,6 +1,7 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.validation.group.OnCreate;
import com.onixbyte.helix.validation.group.OnUpdate;
import jakarta.validation.constraints.NotBlank;
@@ -8,11 +9,11 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
public record AuthorityRequest(
@Null(groups = {OnUpdate.class}, message = "Code cannot be edited.")
@NotNull(groups = {OnCreate.class}, message = "Code cannot be null.")
@Null(groups = {OnUpdate.class}, message = "{" + MessageName.REQUEST_AUTHORITY_CODE_NOT_EDITABLE + "}")
@NotNull(groups = {OnCreate.class}, message = "{" + MessageName.REQUEST_AUTHORITY_CODE_NOT_NULL + "}")
String code,
@NotNull(message = "Name of the authority cannot be null")
@NotBlank(message = "Name of the authority cannot be null")
@NotNull(message = "{" + MessageName.REQUEST_AUTHORITY_NAME_NOT_NULL + "}")
@NotBlank(message = "{" + MessageName.REQUEST_AUTHORITY_NAME_NOT_NULL + "}")
String name,
String description,
Status status
@@ -23,12 +23,13 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record DepartmentRequest(
@NotNull(message = "Name of the department should not be null")
@NotBlank(message = "Name of the department should not be null")
@NotNull(message = "{" + MessageName.REQUEST_DEPARTMENT_NAME_NOT_NULL + "}")
@NotBlank(message = "{" + MessageName.REQUEST_DEPARTMENT_NAME_NOT_NULL + "}")
String name,
Long parentId,
Integer sort,
@@ -1,10 +1,11 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
public record EditRoleRequest(
@NotNull(message = "角色 ID 不能为空")
@NotNull(message = "{" + MessageName.REQUEST_EDIT_ROLE_ID_NOT_NULL + "}")
Long id,
String name,
String code,
@@ -13,7 +14,7 @@ public record EditRoleRequest(
String description,
@Pattern(
regexp = "^(ACTIVE|INACTIVE)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 其中之一")
message = "{" + MessageName.REQUEST_EDIT_ROLE_STATUS_INVALID + "}")
String status
) {
}
@@ -1,12 +1,13 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.enumeration.UserStatus;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
public record EditUserRequest(
@NotNull(message = "User ID cannot be null")
@Positive(message = "User ID must be positive")
@NotNull(message = "{" + MessageName.REQUEST_EDIT_USER_ID_NOT_NULL + "}")
@Positive(message = "{" + MessageName.REQUEST_EDIT_USER_ID_POSITIVE + "}")
Long id,
String fullName,
String email,
@@ -1,5 +1,6 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.Pattern;
public record QueryRoleRequest(
@@ -7,7 +8,7 @@ public record QueryRoleRequest(
String code,
@Pattern(
regexp = "^(ACTIVE|INACTIVE)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 其中之一")
message = "{" + MessageName.REQUEST_QUERY_ROLE_STATUS_INVALID + "}")
String status
) {
}
@@ -1,5 +1,6 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.Pattern;
import java.time.LocalDateTime;
@@ -11,7 +12,7 @@ public record QueryUserRequest(
String phoneNumber,
@Pattern(
regexp = "^(ACTIVE|INACTIVE|LOCKED)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 或 LOCKED 其中之一")
message = "{" + MessageName.REQUEST_QUERY_USER_STATUS_INVALID + "}")
String status,
LocalDateTime createdAtStart,
LocalDateTime createdAtEnd
@@ -1,9 +1,10 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record ResetPasswordRequest(
@NotBlank(message = "密码不能为空") String password
@NotBlank(message = "{" + MessageName.REQUEST_RESET_PASSWORD_NOT_EMPTY + "}") String password
) {
}
@@ -0,0 +1,20 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
public record RoleRequest(
@NotBlank(message = "{" + MessageName.REQUEST_ROLE_NAME_NOT_EMPTY + "}")
String name,
@NotBlank(message = "{" + MessageName.REQUEST_ROLE_CODE_NOT_EMPTY + "}")
String code,
@NotNull(message = "{" + MessageName.REQUEST_ROLE_SORT_NOT_NULL + "}")
Integer sort,
Boolean defaultValue,
String description,
Status status
) {
}
@@ -41,6 +41,8 @@ public class BizException extends RuntimeException {
* REST API endpoints.
*/
private final HttpStatus status;
private final String messageCode;
private final Object[] messageArgs;
/**
* Constructs a new business exception with the specified HTTP status and message.
@@ -50,6 +52,8 @@ public class BizException extends RuntimeException {
public BizException(String message) {
super(message);
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
this.messageCode = null;
this.messageArgs = new Object[0];
}
/**
@@ -61,6 +65,22 @@ public class BizException extends RuntimeException {
public BizException(HttpStatus status, String message) {
super(message);
this.status = status;
this.messageCode = null;
this.messageArgs = new Object[0];
}
public BizException(String messageCode, Object... messageArgs) {
super(messageCode);
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
this.messageCode = messageCode;
this.messageArgs = messageArgs == null ? new Object[0] : messageArgs;
}
public BizException(HttpStatus status, String messageCode, Object... messageArgs) {
super(messageCode);
this.status = status;
this.messageCode = messageCode;
this.messageArgs = messageArgs == null ? new Object[0] : messageArgs;
}
/**
@@ -71,4 +91,12 @@ public class BizException extends RuntimeException {
public HttpStatus getStatus() {
return status;
}
public String getMessageCode() {
return messageCode;
}
public Object[] getMessageArgs() {
return messageArgs;
}
}
@@ -8,6 +8,7 @@ import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.AuthorityMapper;
import com.onixbyte.helix.repository.AuthorityRepository;
import com.onixbyte.helix.shared.CacheName;
import com.onixbyte.helix.shared.MessageName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Example;
@@ -71,7 +72,7 @@ public class AuthorityManager {
var updatedAt = LocalDateTime.now();
var authorityToUpdate = authorityRepository.findById(id)
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "找不到指定的权限信息"));
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, MessageName.AUTHORITY_NOT_FOUND, id));
authorityToUpdate.setName(authority.getName());
authorityToUpdate.setDescription(authority.getDescription());
@@ -5,12 +5,15 @@ import com.onixbyte.helix.domain.entity.Role;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.RoleMapper;
import com.onixbyte.helix.repository.RoleRepository;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@@ -19,16 +22,18 @@ public class RoleManager {
private final RoleMapper roleMapper;
private final RoleRepository roleRepository;
private final MessageUtil messageUtil;
@Autowired
public RoleManager(RoleMapper roleMapper, RoleRepository roleRepository) {
public RoleManager(RoleMapper roleMapper, RoleRepository roleRepository, MessageUtil messageUtil) {
this.roleMapper = roleMapper;
this.roleRepository = roleRepository;
this.messageUtil = messageUtil;
}
public void validateRoles(List<Long> roleIds) {
if (!roleMapper.areRolesExisted(roleIds)) {
throw new BizException(HttpStatus.BAD_REQUEST, "Role does not exist in database.");
throw new BizException(HttpStatus.BAD_REQUEST, MessageName.ROLE_NOT_EXISTS);
}
}
@@ -52,23 +57,24 @@ public class RoleManager {
}
@Transactional
public void updateRole(Role role) {
var roleToUpdate = roleRepository.findById(role.getId())
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "找不到指定的角色信息。"));
public Role fullUpdateById(Long id, Role role) {
var updatedAt = LocalDateTime.now();
Optional.ofNullable(role.getName())
.ifPresent(roleToUpdate::setName);
Optional.ofNullable(role.getCode())
.ifPresent(roleToUpdate::setCode);
Optional.ofNullable(role.getSort())
.ifPresent(roleToUpdate::setSort);
var roleToUpdate = roleRepository.findById(id)
.orElseThrow(() -> new BizException(
HttpStatus.NOT_FOUND,
messageUtil.getMessage(MessageName.ROLE_NOT_FOUND, id))
);
roleToUpdate.setName(role.getName());
roleToUpdate.setCode(role.getCode());
roleToUpdate.setSort(role.getSort());
roleToUpdate.setDefaultValue(role.getDefaultValue());
roleToUpdate.setDescription(role.getDescription());
roleToUpdate.setStatus(role.getStatus());
roleToUpdate.setUpdatedAt(updatedAt);
Optional.ofNullable(role.getStatus())
.ifPresent(roleToUpdate::setStatus);
return role;
}
public void deleteRole(Long id) {
@@ -33,6 +33,7 @@ import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.UserMapper;
import com.onixbyte.helix.repository.UserRepository;
import com.onixbyte.region.Region;
import com.onixbyte.helix.shared.MessageName;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
@@ -112,7 +113,7 @@ public class UserManager {
@Transactional(rollbackFor = Throwable.class)
public User updateUser(User user) {
var userToUpdate = userRepository.findById(user.getId())
.orElseThrow(() -> new BizException(HttpStatus.BAD_REQUEST, "找不到 ID 为" + user.getId() + "的用户信息"));
.orElseThrow(() -> new BizException(HttpStatus.BAD_REQUEST, MessageName.USER_NOT_FOUND, user.getId()));
Optional.ofNullable(user.getFullName())
.filter(StringUtils::isNotBlank)
@@ -8,6 +8,7 @@ import com.onixbyte.helix.manager.AuthorityManager;
import com.onixbyte.helix.manager.UserManager;
import com.onixbyte.helix.repository.UserCredentialRepository;
import com.onixbyte.helix.security.authentication.UsernamePasswordAuthentication;
import com.onixbyte.helix.shared.MessageName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -46,14 +47,14 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof UsernamePasswordAuthentication usernamePasswordAuthentication)) {
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "用户认证失败,请稍后再试。");
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, MessageName.AUTH_PROVIDER_FAILED);
}
// get user from database
var user = userManager.selectByUsername(usernamePasswordAuthentication.getPrincipal());
if (Objects.isNull(user)) {
log.error("User {} is trying to authenticate but no user found.", usernamePasswordAuthentication.getPrincipal());
throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。");
throw new BizException(HttpStatus.UNAUTHORIZED, MessageName.AUTH_PROVIDER_BAD_CREDENTIALS);
}
// get user credentials from database
@@ -61,12 +62,12 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
.provider(CredentialProvider.LOCAL)
.userId(user.getId())
.build()))
.orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, "您还没有配置密码,请使用第三方账号登录"));
.orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, MessageName.AUTH_PROVIDER_PASSWORD_NOT_CONFIGURED));
// validate password
if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), userCredentials.getCredential())) {
log.error("User {} is trying to authenticate but password is incorrect.", usernamePasswordAuthentication.getPrincipal());
throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。");
throw new BizException(HttpStatus.UNAUTHORIZED, MessageName.AUTH_PROVIDER_BAD_CREDENTIALS);
}
// erase credentials
@@ -4,6 +4,7 @@ import com.onixbyte.helix.domain.entity.Asset;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.manager.AssetManager;
import com.onixbyte.helix.properties.AssetProperties;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.SecurityUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
@@ -62,7 +63,7 @@ public class AssetService {
if (Objects.isNull(prefix) || prefix.isBlank() || prefix.startsWith("/") || prefix.startsWith("..")) {
throw new BizException(
HttpStatus.INTERNAL_SERVER_ERROR,
"Prefix must not be empty, and should not start with '/' or '..'."
MessageName.ASSET_INVALID_PREFIX
);
}
@@ -108,7 +109,7 @@ public class AssetService {
var asset = assetManager.queryByAssetId(assetId);
if (!Objects.equals(currentUser.getId(), asset.getUploadBy())) {
throw new BizException(HttpStatus.FORBIDDEN, "You are not able to delete an asset that is not uploaded by you.");
throw new BizException(HttpStatus.FORBIDDEN, MessageName.ASSET_DELETE_FORBIDDEN);
}
assetManager.deleteById(assetId);
@@ -9,6 +9,7 @@ import com.onixbyte.helix.manager.CaptchaManager;
import com.onixbyte.helix.manager.SecurityManager;
import com.onixbyte.helix.manager.SettingManager;
import com.onixbyte.helix.security.authentication.UsernamePasswordAuthentication;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.shared.SettingName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -63,10 +64,10 @@ public class AuthService {
var rawCaptcha = captchaManager.getCaptcha(uuid);
if (Objects.isNull(rawCaptcha) || rawCaptcha.isBlank()) {
throw new BizException(HttpStatus.BAD_REQUEST, "未找到验证码");
throw new BizException(HttpStatus.BAD_REQUEST, MessageName.AUTH_LOGIN_CAPTCHA_NOT_FOUND);
}
if (!rawCaptcha.equalsIgnoreCase(request.captcha())) {
throw new BizException(HttpStatus.BAD_REQUEST, "验证码错误");
throw new BizException(HttpStatus.BAD_REQUEST, MessageName.AUTH_LOGIN_CAPTCHA_INCORRECT);
}
}
@@ -78,7 +79,7 @@ public class AuthService {
_authentication.getClass()
);
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
"Cannot perform login due to server crashes.");
MessageName.AUTH_LOGIN_FAILED);
}
return authentication.getDetails();
@@ -7,6 +7,7 @@ import com.onixbyte.helix.domain.web.request.QueryAuthorityRequest;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.manager.AuthorityManager;
import com.onixbyte.helix.manager.RoleAuthorityManager;
import com.onixbyte.helix.shared.MessageName;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -40,7 +41,7 @@ public class AuthorityService {
.build();
if (authorityManager.existsByCode(authority)) {
throw new BizException(HttpStatus.CONFLICT, "权限编码 `" + authority.getCode() + "` 已被使用");
throw new BizException(HttpStatus.CONFLICT, MessageName.AUTHORITY_CODE_USED, authority.getCode());
}
return authorityManager.save(authority);
@@ -59,7 +60,7 @@ public class AuthorityService {
var authorityName = authorityManager.findAuthorityNameById(authorityId);
if (StringUtils.isBlank(authorityName)) {
throw new BizException(HttpStatus.NOT_FOUND, "Authority with ID '%d' not found.".formatted(authorityId));
throw new BizException(HttpStatus.NOT_FOUND, MessageName.AUTHORITY_NOT_FOUND, authorityId);
}
roleAuthorityManager.deleteByAuthorityId(authorityId);
@@ -1,13 +1,14 @@
package com.onixbyte.helix.service;
import com.onixbyte.captcha.Producer;
import com.onixbyte.helix.shared.FileType;
import com.onixbyte.helix.shared.SettingName;
import com.onixbyte.helix.domain.entity.Setting;
import com.onixbyte.helix.domain.web.response.CaptchaResponse;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.manager.CaptchaManager;
import com.onixbyte.helix.manager.SettingManager;
import com.onixbyte.helix.shared.FileType;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.shared.SettingName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;
@@ -66,7 +67,7 @@ public class CaptchaService {
Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return new CaptchaResponse(captchaDataUrl, uuid);
} catch (IOException e) {
throw new BizException("无法生成验证码图片。");
throw new BizException(MessageName.CAPTCHA_GENERATE_FAILED);
}
}
}
@@ -1,19 +1,22 @@
package com.onixbyte.helix.service;
import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.domain.database.query.wrapper.QueryRoleWrapper;
import com.onixbyte.helix.domain.entity.Role;
import com.onixbyte.helix.domain.web.request.AddRoleRequest;
import com.onixbyte.helix.domain.web.request.EditRoleRequest;
import com.onixbyte.helix.domain.web.request.QueryRoleRequest;
import com.onixbyte.helix.domain.web.request.RoleRequest;
import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.manager.RoleAuthorityManager;
import com.onixbyte.helix.manager.RoleManager;
import com.onixbyte.helix.manager.UserRoleManager;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import jakarta.transaction.Transactional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.util.Optional;
@@ -24,16 +27,19 @@ public class RoleService {
private final RoleManager roleManager;
private final RoleAuthorityManager roleAuthorityManager;
private final UserRoleManager userRoleManager;
private final MessageUtil messageUtil;
@Autowired
public RoleService(
RoleManager roleManager,
RoleAuthorityManager roleAuthorityManager,
UserRoleManager userRoleManager
UserRoleManager userRoleManager,
MessageUtil messageUtil
) {
this.roleManager = roleManager;
this.roleAuthorityManager = roleAuthorityManager;
this.userRoleManager = userRoleManager;
this.messageUtil = messageUtil;
}
public Page<Role> getRoles(Pageable pageable, QueryRoleRequest request) {
@@ -55,11 +61,12 @@ public class RoleService {
return roleManager.selectAll(pageable, wrapper);
}
public Role addRole(AddRoleRequest request) {
var isDefaultRole = Optional.ofNullable(request.defaultValue())
public Role addRole(RoleRequest request) {
var isDefaultRole = Optional.of(request)
.map(RoleRequest::defaultValue)
.orElse(false);
var status = Optional.ofNullable(request.status())
.map(Status::valueOf)
var status = Optional.of(request)
.map(RoleRequest::status)
.orElse(Status.ACTIVE);
var role = Role.builder()
@@ -75,24 +82,29 @@ public class RoleService {
}
@Transactional
public void editRole(EditRoleRequest request) {
roleManager.updateRole(Role.builder()
.id(request.id())
public Role editRole(Long id, RoleRequest request) {
return roleManager.fullUpdateById(id, Role.builder()
.name(request.name())
.code(request.code())
.sort(request.sort())
.defaultValue(request.defaultValue())
.description(request.description())
.status(Optional.ofNullable(request.status())
.map(Status::valueOf)
.orElse(null))
.status(request.status())
.build());
}
@Transactional
public void deleteRole(Long id) {
public String deleteRole(Long id) {
var role = roleManager.getRoleById(id)
.orElseThrow(() -> new BizException(
HttpStatus.NOT_FOUND,
messageUtil.getMessage(MessageName.ROLE_NOT_FOUND, id))
);
roleAuthorityManager.deleteByRoleId(id);
userRoleManager.deleteByRoleId(id);
roleManager.deleteRole(id);
return role.getName();
}
}
@@ -1,33 +0,0 @@
/*
* Copyright (c) 2024-2026 OnixByte
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.onixbyte.helix.shared;
public class Message {
public static final String ASSET_NOT_EMPTY = "asset.not-empty";
public static final String AUTHORITY_DELETED = "authority.deleted";
public static final String USER_PASSWORD_RESET_SUCCESS = "user.password-reset-success";
public static final String USER_DELETED = "user.deleted";
}
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2024-2026 OnixByte
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.onixbyte.helix.shared;
public class MessageName {
public static final String ASSET_NOT_EMPTY = "asset.not-empty";
public static final String ASSET_INVALID_PREFIX = "asset.invalid-prefix";
public static final String ASSET_DELETE_FORBIDDEN = "asset.delete-forbidden";
public static final String ASSET_UPLOAD_FAILED = "asset.upload-failed";
public static final String AUTHORITY_DELETED = "authority.deleted";
public static final String AUTHORITY_CODE_USED = "authority.code-used";
public static final String AUTHORITY_NOT_FOUND = "authority.not-found";
public static final String AUTH_LOGIN_CAPTCHA_NOT_FOUND = "auth.login.captcha-not-found";
public static final String AUTH_LOGIN_CAPTCHA_INCORRECT = "auth.login.captcha-incorrect";
public static final String AUTH_LOGIN_FAILED = "auth.login.failed";
public static final String AUTH_PROVIDER_FAILED = "auth.provider.failed";
public static final String AUTH_PROVIDER_BAD_CREDENTIALS = "auth.provider.bad-credentials";
public static final String AUTH_PROVIDER_PASSWORD_NOT_CONFIGURED = "auth.provider.password-not-configured";
public static final String CAPTCHA_GENERATE_FAILED = "captcha.generate-failed";
public static final String ROLE_NOT_EXISTS = "role.not-exists";
public static final String USER_PASSWORD_RESET_SUCCESS = "user.password-reset-success";
public static final String USER_DELETED = "user.deleted";
public static final String USER_NOT_FOUND = "user.not-found";
public static final String SECURITY_CONTEXT_USER_NOT_FOUND = "security.context-user-not-found";
public static final String TREE_MULTIPLE_ROOTS = "tree.multiple-roots";
public static final String ROLE_NOT_FOUND = "role.not-found";
public static final String ROLE_DELETED = "role.deleted";
public static final String REQUEST_ADD_USER_USERNAME_NOT_EMPTY = "request.add-user.username.not-empty";
public static final String REQUEST_ADD_USER_PASSWORD_NOT_EMPTY = "request.add-user.password.not-empty";
public static final String REQUEST_ADD_USER_FULL_NAME_NOT_EMPTY = "request.add-user.full-name.not-empty";
public static final String REQUEST_AUTHORITY_CODE_NOT_EDITABLE = "request.authority.code.not-editable";
public static final String REQUEST_AUTHORITY_CODE_NOT_NULL = "request.authority.code.not-null";
public static final String REQUEST_AUTHORITY_NAME_NOT_NULL = "request.authority.name.not-null";
public static final String REQUEST_DEPARTMENT_NAME_NOT_NULL = "request.department.name.not-null";
public static final String REQUEST_EDIT_ROLE_ID_NOT_NULL = "request.edit-role.id.not-null";
public static final String REQUEST_EDIT_ROLE_STATUS_INVALID = "request.edit-role.status.invalid";
public static final String REQUEST_EDIT_USER_ID_NOT_NULL = "request.edit-user.id.not-null";
public static final String REQUEST_EDIT_USER_ID_POSITIVE = "request.edit-user.id.positive";
public static final String REQUEST_RESET_PASSWORD_NOT_EMPTY = "request.reset-password.password.not-empty";
public static final String REQUEST_ROLE_NAME_NOT_EMPTY = "request.role.name.not-empty";
public static final String REQUEST_ROLE_CODE_NOT_EMPTY = "request.role.code.not-empty";
public static final String REQUEST_ROLE_SORT_NOT_NULL = "request.role.sort.not-null";
public static final String REQUEST_QUERY_ROLE_STATUS_INVALID = "request.query-role.status.invalid";
public static final String REQUEST_QUERY_USER_STATUS_INVALID = "request.query-user.status.invalid";
}
@@ -2,6 +2,7 @@ package com.onixbyte.helix.utils;
import com.onixbyte.helix.domain.entity.User;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.shared.MessageName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@@ -32,7 +33,7 @@ public class SecurityUtil {
if (!(_details instanceof User user)) {
log.error("Authentication details is {}", _details);
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "Cannot retrieve user information from context.");
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, MessageName.SECURITY_CONTEXT_USER_NOT_FOUND);
}
return user;
@@ -3,6 +3,7 @@ package com.onixbyte.helix.utils;
import com.onixbyte.helix.domain.common.Treeable;
import com.onixbyte.helix.domain.common.TreeNode;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.shared.MessageName;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.http.HttpStatus;
@@ -43,7 +44,7 @@ public class TreeUtil {
// Ensure only 1 root node is included
if (rootItems.size() > 1) {
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, "Multiple root items found in given values.");
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, MessageName.TREE_MULTIPLE_ROOTS);
}
// Get root item and build root node
+2
View File
@@ -27,6 +27,8 @@ spring:
hikari:
minimum-idle: 1
maximum-pool-size: 10
messages:
basename: i18n/messages
flyway:
enabled: true
baseline-on-migrate: true
+20 -2
View File
@@ -19,10 +19,28 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
tree.multiple-roots=
user.not-found=User with ID [{0}] not found.
security.context-user-not-found=Cannot retrieve user information from security context.
role.not-exists=Role does not exist in database.
captcha.generate-failed=
auth.provider.password-not-configured=Username or password is incorrect.
auth.provider.bad-credentials=Username or password is incorrect.
auth.provider.failed=Authentication failed, please try again later.
auth.login.failed=Cannot perform login due to server error.
auth.login.captcha-incorrect=Captcha is incorrect.
auth.login.captcha-not-found=Captcha not found.
authority.not-found=Authority with ID [{0}] not found.
authority.code-used=Authority code [{0}] is already in use.
asset.upload-failed=Failed to upload file: {0}
asset.delete-forbidden=You are not able to delete an asset that is not uploaded by you.
asset.invalid-prefix=Prefix must not be empty and must not start with '/' or '..'.
asset.not-empty = File cannot be empty.
authority.deleted = Authority [{0}] deleted.
user.password-reset-success = Password has been reset.
user.deleted = User [{0}] deleted.
user.deleted = User [{0}] deleted.
role.not-found=Role with ID {0} not found.
role.deleted=Role [{0}] deleted.
request.role.name.not-empty=Name of the role cannot be empty.
@@ -21,8 +21,56 @@
#
asset.not-empty = File cannot be empty.
asset.invalid-prefix = Prefix must not be empty and must not start with '/' or '..'.
asset.delete-forbidden = You are not able to delete an asset that is not uploaded by you.
asset.upload-failed = Failed to upload file: {0}
authority.deleted = Authority [{0}] deleted.
authority.code-used = Authority code [{0}] is already in use.
authority.not-found = Authority with ID [{0}] not found.
auth.login.captcha-not-found = Captcha not found.
auth.login.captcha-incorrect = Captcha is incorrect.
auth.login.failed = Cannot perform login due to server error.
auth.provider.failed = Authentication failed, please try again later.
auth.provider.bad-credentials = Username or password is incorrect.
auth.provider.password-not-configured = Password is not configured. Please sign in with a third-party account.
captcha.generate-failed = Unable to generate captcha image.
role.not-exists = Role does not exist in database.
role.not-found = Role with ID {0} not found.
role.deleted = Role [{0}] deleted.
user.password-reset-success = Password has been reset.
user.deleted = User [{0}] deleted.
user.deleted = User [{0}] deleted.
user.not-found = User with ID [{0}] not found.
security.context-user-not-found = Cannot retrieve user information from security context.
tree.multiple-roots = Multiple root items found in given values.
request.add-user.username.not-empty = Username cannot be empty.
request.add-user.password.not-empty = Password cannot be empty.
request.add-user.full-name.not-empty = Full name cannot be empty.
request.authority.code.not-editable = Code cannot be edited.
request.authority.code.not-null = Code cannot be null.
request.authority.name.not-null = Name of the authority cannot be null.
request.department.name.not-null = Name of the department should not be null.
request.edit-role.id.not-null = Role ID cannot be null.
request.edit-role.status.invalid = Status can only be ACTIVE or INACTIVE.
request.edit-user.id.not-null = User ID cannot be null.
request.edit-user.id.positive = User ID must be positive.
request.reset-password.password.not-empty = Password cannot be empty.
request.role.name.not-empty = Name of the role cannot be empty.
request.role.code.not-empty = Code of the role cannot be empty.
request.role.sort.not-null = Sort number cannot be null.
request.query-role.status.invalid = Status can only be ACTIVE or INACTIVE.
request.query-user.status.invalid = Status can only be ACTIVE, INACTIVE, or LOCKED.
@@ -21,8 +21,56 @@
#
asset.not-empty = 文件不能为空。
asset.invalid-prefix = 前缀不能为空,且不能以 '/' 或 '..' 开头。
asset.delete-forbidden = 你不能删除非自己上传的文件。
asset.upload-failed = 文件上传失败:{0}
authority.deleted = 权限【{0}】删除成功。
authority.code-used = 权限编码【{0}】已被使用。
authority.not-found = 找不到 ID 为【{0}】的权限。
auth.login.captcha-not-found = 未找到验证码。
auth.login.captcha-incorrect = 验证码错误。
auth.login.failed = 登录失败,服务器出现错误。
auth.provider.failed = 用户认证失败,请稍后再试。
auth.provider.bad-credentials = 用户名或密码错误。
auth.provider.password-not-configured = 你还没有配置密码,请使用第三方账号登录。
captcha.generate-failed = 无法生成验证码图片。
role.not-exists = 角色不存在。
role.not-found = 找不到 ID 为 {0} 的角色。
role.deleted = 角色【{0}】删除成功。
user.password-reset-success = 密码修改成功。
user.deleted = 用户【{0}】删除成功。
user.deleted = 用户【{0}】删除成功。
user.not-found = 找不到 ID 为【{0}】的用户信息。
security.context-user-not-found = 无法从安全上下文中获取用户信息。
tree.multiple-roots = 数据中包含多个根节点。
request.add-user.username.not-empty = 用户名不能为空。
request.add-user.password.not-empty = 密码不能为空。
request.add-user.full-name.not-empty = 姓名不能为空。
request.authority.code.not-editable = 权限编码不可修改。
request.authority.code.not-null = 权限编码不能为空。
request.authority.name.not-null = 权限名称不能为空。
request.department.name.not-null = 部门名称不能为空。
request.edit-role.id.not-null = 角色 ID 不能为空。
request.edit-role.status.invalid = 状态只能是 ACTIVE 或 INACTIVE。
request.edit-user.id.not-null = 用户 ID 不能为空。
request.edit-user.id.positive = 用户 ID 必须为正数。
request.reset-password.password.not-empty = 密码不能为空。
request.role.name.not-empty = 角色名称不能为空。
request.role.code.not-empty = 角色编码不能为空。
request.role.sort.not-null = 排序编号不能为空。
request.query-role.status.invalid = 状态只能是 ACTIVE 或 INACTIVE。
request.query-user.status.invalid = 状态只能是 ACTIVE、INACTIVE 或 LOCKED。