Compare commits

...

10 Commits

56 changed files with 852 additions and 259 deletions
@@ -1,7 +1,6 @@
package com.onixbyte.helix.config; package com.onixbyte.helix.config;
import com.onixbyte.identitygenerator.IdentityGenerator; import com.onixbyte.identitygenerator.IdentityGenerator;
import com.onixbyte.identitygenerator.impl.SequentialUuidGenerator;
import com.onixbyte.identitygenerator.impl.SnowflakeIdentityGenerator; import com.onixbyte.identitygenerator.impl.SnowflakeIdentityGenerator;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -28,24 +27,6 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class GuidConfig { public class GuidConfig {
/**
* Creates a Snowflake-based identity generator for user IDs.
* <p>
* This method configures a {@link SnowflakeIdentityGenerator} with machine ID and data centre
* ID both set to 0. The generator produces unique 64-bit Long identifiers suitable for user
* entity primary keys in distributed environments.
* <p>
* The generated IDs are:
* <ul>
* <li>Globally unique across all instances</li>
* <li>Time-ordered (newer IDs have higher values)</li>
* <li>Highly performant with minimal coordination overhead</li>
* </ul>
*
* @return a configured {@link SnowflakeIdentityGenerator} instance for generating user IDs
* @see SnowflakeIdentityGenerator
* @see IdentityGenerator
*/
@Bean @Bean
public IdentityGenerator<Long> userIdentityGenerator() { public IdentityGenerator<Long> userIdentityGenerator() {
return new SnowflakeIdentityGenerator(0x0, 0x0); return new SnowflakeIdentityGenerator(0x0, 0x0);
@@ -0,0 +1,41 @@
/*
* 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.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import java.util.Locale;
@Configuration
public class I18nConfig {
@Bean
public LocaleResolver localeResolver() {
var slr = new AcceptHeaderLocaleResolver();
slr.setDefaultLocale(Locale.UK);
return slr;
}
}
@@ -1,7 +1,6 @@
package com.onixbyte.helix.config; package com.onixbyte.helix.config;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.onixbyte.helix.common.jackson.JacksonModules; import com.onixbyte.helix.common.jackson.JacksonModules;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -0,0 +1,47 @@
/*
* 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.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class ValidationConfig {
private final MessageSource messageSource;
@Autowired
public ValidationConfig(MessageSource messageSource) {
this.messageSource = messageSource;
}
@Bean
public LocalValidatorFactoryBean getValidator() {
var factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource);
return factoryBean;
}
}
@@ -4,6 +4,7 @@ import com.onixbyte.helix.shared.AssetPrefix;
import com.onixbyte.helix.domain.web.response.FileUploadResponse; import com.onixbyte.helix.domain.web.response.FileUploadResponse;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.service.AssetService; import com.onixbyte.helix.service.AssetService;
import com.onixbyte.helix.shared.MessageName;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -26,11 +27,6 @@ public class AssetController {
private final AssetService assetService; private final AssetService assetService;
/**
* Constructs a new FileController with the specified file service.
*
* @param assetService the file service to use for file operations
*/
@Autowired @Autowired
public AssetController(AssetService assetService) { public AssetController(AssetService assetService) {
this.assetService = assetService; this.assetService = assetService;
@@ -48,7 +44,7 @@ public class AssetController {
) { ) {
try { try {
if (file.isEmpty()) { if (file.isEmpty()) {
throw new BizException(HttpStatus.BAD_REQUEST, "File cannot be empty."); throw new BizException(HttpStatus.BAD_REQUEST, MessageName.ASSET_NOT_EMPTY);
} }
var fileUrl = assetService.uploadFile(AssetPrefix.UPLOADS, file); var fileUrl = assetService.uploadFile(AssetPrefix.UPLOADS, file);
@@ -61,10 +57,13 @@ public class AssetController {
file.getSize(), file.getSize(),
fileUrl fileUrl
)); ));
} catch (BizException ex) {
throw ex;
} catch (Exception e) { } catch (Exception e) {
log.error("File upload failed: {}", e.getMessage(), e); log.error("File upload failed: {}", e.getMessage(), e);
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
"Failed upload file: " + e.getMessage()); MessageName.ASSET_UPLOAD_FAILED,
e.getMessage());
} }
} }
@@ -6,8 +6,6 @@ import com.onixbyte.helix.service.AuthService;
import com.onixbyte.helix.service.TokenService; import com.onixbyte.helix.service.TokenService;
import com.onixbyte.helix.service.UserService; import com.onixbyte.helix.service.UserService;
import com.onixbyte.helix.shared.TokenConstant; import com.onixbyte.helix.shared.TokenConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -27,7 +25,6 @@ import java.time.Duration;
@RequestMapping("/auth") @RequestMapping("/auth")
public class AuthController { public class AuthController {
private static final Logger log = LoggerFactory.getLogger(AuthController.class);
private final AuthService authService; private final AuthService authService;
private final TokenService tokenService; private final TokenService tokenService;
private final UserService userService; private final UserService userService;
@@ -1,10 +1,12 @@
package com.onixbyte.helix.controller; package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Authority; import com.onixbyte.helix.domain.entity.Authority;
import com.onixbyte.helix.domain.web.request.AddAuthorityRequest; import com.onixbyte.helix.domain.web.request.AuthorityRequest;
import com.onixbyte.helix.domain.web.request.EditAuthorityRequest;
import com.onixbyte.helix.domain.web.request.QueryAuthorityRequest; 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.service.AuthorityService;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -21,9 +23,14 @@ import org.springframework.web.bind.annotation.*;
public class AuthorityController { public class AuthorityController {
private final AuthorityService authorityService; private final AuthorityService authorityService;
private final MessageUtil messageUtil;
public AuthorityController(AuthorityService authorityService) { public AuthorityController(
AuthorityService authorityService,
MessageUtil messageUtil
) {
this.authorityService = authorityService; this.authorityService = authorityService;
this.messageUtil = messageUtil;
} }
/** /**
@@ -51,7 +58,7 @@ public class AuthorityController {
* @return created authority * @return created authority
*/ */
@PostMapping @PostMapping
public Authority addAuthority(@Validated @RequestBody AddAuthorityRequest request) { public Authority addAuthority(@Validated @RequestBody AuthorityRequest request) {
return authorityService.addAuthority(request); return authorityService.addAuthority(request);
} }
@@ -61,8 +68,17 @@ public class AuthorityController {
* @param request authority specs * @param request authority specs
* @return edited authority * @return edited authority
*/ */
@PutMapping @PutMapping("/{id:\\d+}")
public Authority editAuthority(@Validated @RequestBody EditAuthorityRequest request) { public Authority editAuthority(
return authorityService.editAuthority(request); @PathVariable Long id,
@Validated @RequestBody AuthorityRequest request
) {
return authorityService.editAuthority(id, request);
}
@DeleteMapping("/{authorityId:\\d+}")
public ActionResponse deleteAuthority(@PathVariable Long authorityId) {
var name = authorityService.deleteAuthority(authorityId);
return ActionResponse.success(messageUtil.getMessage(MessageName.AUTHORITY_DELETED, name));
} }
} }
@@ -2,7 +2,7 @@ package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Department; import com.onixbyte.helix.domain.entity.Department;
import com.onixbyte.helix.domain.common.TreeNode; import com.onixbyte.helix.domain.common.TreeNode;
import com.onixbyte.helix.domain.web.request.AddDepartmentRequest; import com.onixbyte.helix.domain.web.request.DepartmentRequest;
import com.onixbyte.helix.service.DepartmentService; import com.onixbyte.helix.service.DepartmentService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -38,7 +38,15 @@ public class DepartmentController {
} }
@PostMapping @PostMapping
public Department addDepartment(@Validated @RequestBody AddDepartmentRequest request) { public Department addDepartment(@Validated @RequestBody DepartmentRequest request) {
return departmentService.addDepartment(request); return departmentService.addDepartment(request);
} }
@PutMapping("/{id:\\d+}")
public Department editDepartment(
@PathVariable Long id,
@Validated @RequestBody DepartmentRequest request
) {
return departmentService.editDepartment(id, request);
}
} }
@@ -2,6 +2,7 @@ package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.web.response.BizExceptionResponse; import com.onixbyte.helix.domain.web.response.BizExceptionResponse;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.utils.MessageUtil;
import jakarta.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -27,12 +28,22 @@ import java.util.stream.Collectors;
@RestControllerAdvice @RestControllerAdvice
public class ExceptionController { public class ExceptionController {
private final MessageUtil messageUtil;
public ExceptionController(MessageUtil messageUtil) {
this.messageUtil = messageUtil;
}
@ExceptionHandler(BizException.class) @ExceptionHandler(BizException.class)
public ResponseEntity<BizExceptionResponse> handleBizException(BizException ex) { public ResponseEntity<BizExceptionResponse> handleBizException(BizException ex) {
var message = ex.getMessageCode() == null
? ex.getMessage()
: messageUtil.getMessage(ex.getMessageCode(), ex.getMessageArgs());
return ResponseEntity.status(ex.getStatus()) return ResponseEntity.status(ex.getStatus())
.body(new BizExceptionResponse( .body(new BizExceptionResponse(
LocalDateTime.now(), LocalDateTime.now(),
ex.getMessage()) message)
); );
} }
@@ -1,15 +1,16 @@
package com.onixbyte.helix.controller; package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Role; 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.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.service.RoleService;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -24,10 +25,12 @@ import org.springframework.web.bind.annotation.*;
public class RoleController { public class RoleController {
private final RoleService roleService; private final RoleService roleService;
private final MessageUtil messageUtil;
@Autowired @Autowired
public RoleController(RoleService roleService) { public RoleController(RoleService roleService, MessageUtil messageUtil) {
this.roleService = roleService; this.roleService = roleService;
this.messageUtil = messageUtil;
} }
@GetMapping @GetMapping
@@ -41,20 +44,21 @@ public class RoleController {
} }
@PostMapping @PostMapping
public ResponseEntity<Void> addRole(@Validated @RequestBody AddRoleRequest request) { public Role addRole(@Validated @RequestBody RoleRequest request) {
roleService.addRole(request); return roleService.addRole(request);
return ResponseEntity.ok(null);
} }
@PutMapping @PutMapping("/{id:\\d+}")
public ResponseEntity<Void> editRole(@Validated @RequestBody EditRoleRequest request) { public Role editRole(
roleService.editRole(request); @PathVariable Long id,
return ResponseEntity.ok(null); @Validated @RequestBody RoleRequest request
) {
return roleService.editRole(id, request);
} }
@DeleteMapping("/{id:\\d+}") @DeleteMapping("/{id:\\d+}")
public ResponseEntity<Void> deleteRole(@PathVariable Long id) { public ActionResponse deleteRole(@PathVariable Long id) {
roleService.deleteRole(id); var name = roleService.deleteRole(id);
return ResponseEntity.ok(null); return ActionResponse.success(messageUtil.getMessage(MessageName.ROLE_DELETED, name));
} }
} }
@@ -7,11 +7,12 @@ import com.onixbyte.helix.domain.web.request.ResetPasswordRequest;
import com.onixbyte.helix.domain.web.response.ActionResponse; import com.onixbyte.helix.domain.web.response.ActionResponse;
import com.onixbyte.helix.domain.web.response.UserDetailResponse; import com.onixbyte.helix.domain.web.response.UserDetailResponse;
import com.onixbyte.helix.service.UserService; import com.onixbyte.helix.service.UserService;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -27,10 +28,12 @@ import org.springframework.web.bind.annotation.*;
public class UserController { public class UserController {
private final UserService userService; private final UserService userService;
private final MessageUtil messageUtil;
@Autowired @Autowired
public UserController(UserService userService) { public UserController(UserService userService, MessageUtil messageUtil) {
this.userService = userService; this.userService = userService;
this.messageUtil = messageUtil;
} }
/** /**
@@ -78,13 +81,16 @@ public class UserController {
/** /**
* Edit a user. * Edit a user.
* *
* @param id user ID
* @param request user to be edited * @param request user to be edited
* @return edited user * @return edited user
*/ */
@PutMapping @PutMapping("/{id:\\d+}")
public ResponseEntity<Void> editUser(@Validated @RequestBody EditUserRequest request) { public UserDetailResponse editUser(
userService.updateUser(request); @PathVariable Long id,
return ResponseEntity.ok(null); @Validated @RequestBody EditUserRequest request
) {
return userService.updateUser(id, request);
} }
/** /**
@@ -94,10 +100,13 @@ public class UserController {
* @return action response * @return action response
*/ */
@PreAuthorize("hasAnyAuthority('system:user:reset-password')") @PreAuthorize("hasAnyAuthority('system:user:reset-password')")
@PatchMapping("/reset-password") @PatchMapping("/reset-password/{id:\\d+}")
public ActionResponse resetPassword(@Validated @RequestBody ResetPasswordRequest request) { public ActionResponse resetPassword(
userService.resetPassword(request); @PathVariable Long id,
return ActionResponse.success("密码修改成功"); @Validated @RequestBody ResetPasswordRequest request
) {
userService.resetPassword(id, request);
return ActionResponse.success(messageUtil.getMessage(MessageName.USER_PASSWORD_RESET_SUCCESS));
} }
/** /**
@@ -110,6 +119,6 @@ public class UserController {
@DeleteMapping("/{userId:\\d+}") @DeleteMapping("/{userId:\\d+}")
public ActionResponse deleteUser(@PathVariable Long userId) { public ActionResponse deleteUser(@PathVariable Long userId) {
userService.deleteUser(userId); userService.deleteUser(userId);
return ActionResponse.success("删除成功"); return ActionResponse.success(messageUtil.getMessage(MessageName.USER_DELETED, userId));
} }
} }
@@ -1,15 +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 AddAuthorityRequest(
@NotNull @NotBlank(message = "权限编码不能为空") String code,
@NotNull @NotBlank(message = "权限名称不能为空") String name,
String description,
@Pattern(
regexp = "^(ACTIVE|INACTIVE)?$",
message = "状态错误") String status
) {
}
@@ -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; package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.enumeration.UserStatus; import com.onixbyte.helix.enumeration.UserStatus;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.util.List; import java.util.List;
public record AddUserRequest( public record AddUserRequest(
@NotBlank(message = "Username cannot be empty.") @NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_USERNAME_NOT_EMPTY + "}")
String username, String username,
@NotBlank(message = "Password cannot be empty.") @NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_PASSWORD_NOT_EMPTY + "}")
String password, String password,
@NotBlank(message = "Full name cannot be empty.") @NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_FULL_NAME_NOT_EMPTY + "}")
String fullName, String fullName,
String email, String email,
String regionAbbreviation, String regionAbbreviation,
@@ -0,0 +1,21 @@
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;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
public record AuthorityRequest(
@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 = "{" + 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; package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.enumeration.Status; import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
public record AddDepartmentRequest( public record DepartmentRequest(
@NotNull(message = "Name of the department should not be null") @NotNull(message = "{" + MessageName.REQUEST_DEPARTMENT_NAME_NOT_NULL + "}")
@NotBlank(message = "Name of the department should not be null") @NotBlank(message = "{" + MessageName.REQUEST_DEPARTMENT_NAME_NOT_NULL + "}")
String name, String name,
Long parentId, Long parentId,
Integer sort, Integer sort,
@@ -1,14 +0,0 @@
package com.onixbyte.helix.domain.web.request;
import jakarta.validation.constraints.Pattern;
public record EditAuthorityRequest(
Long id,
String name,
String description,
@Pattern(
regexp = "^(ACTIVE|INACTIVE)?$",
message = "状态参数错误")
String status
) {
}
@@ -1,11 +1,11 @@
package com.onixbyte.helix.domain.web.request; package com.onixbyte.helix.domain.web.request;
import jakarta.validation.constraints.NotBlank; import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
public record EditRoleRequest( public record EditRoleRequest(
@NotNull(message = "角色 ID 不能为空") @NotNull(message = "{" + MessageName.REQUEST_EDIT_ROLE_ID_NOT_NULL + "}")
Long id, Long id,
String name, String name,
String code, String code,
@@ -14,7 +14,7 @@ public record EditRoleRequest(
String description, String description,
@Pattern( @Pattern(
regexp = "^(ACTIVE|INACTIVE)?$", regexp = "^(ACTIVE|INACTIVE)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 其中之一") message = "{" + MessageName.REQUEST_EDIT_ROLE_STATUS_INVALID + "}")
String status String status
) { ) {
} }
@@ -1,12 +1,13 @@
package com.onixbyte.helix.domain.web.request; package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.enumeration.UserStatus; import com.onixbyte.helix.enumeration.UserStatus;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Positive;
public record EditUserRequest( public record EditUserRequest(
@NotNull(message = "User ID cannot be null") @NotNull(message = "{" + MessageName.REQUEST_EDIT_USER_ID_NOT_NULL + "}")
@Positive(message = "User ID must be positive") @Positive(message = "{" + MessageName.REQUEST_EDIT_USER_ID_POSITIVE + "}")
Long id, Long id,
String fullName, String fullName,
String email, String email,
@@ -1,5 +1,6 @@
package com.onixbyte.helix.domain.web.request; package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
public record QueryRoleRequest( public record QueryRoleRequest(
@@ -7,7 +8,7 @@ public record QueryRoleRequest(
String code, String code,
@Pattern( @Pattern(
regexp = "^(ACTIVE|INACTIVE)?$", regexp = "^(ACTIVE|INACTIVE)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 其中之一") message = "{" + MessageName.REQUEST_QUERY_ROLE_STATUS_INVALID + "}")
String status String status
) { ) {
} }
@@ -1,5 +1,6 @@
package com.onixbyte.helix.domain.web.request; package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -11,7 +12,7 @@ public record QueryUserRequest(
String phoneNumber, String phoneNumber,
@Pattern( @Pattern(
regexp = "^(ACTIVE|INACTIVE|LOCKED)?$", regexp = "^(ACTIVE|INACTIVE|LOCKED)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 或 LOCKED 其中之一") message = "{" + MessageName.REQUEST_QUERY_USER_STATUS_INVALID + "}")
String status, String status,
LocalDateTime createdAtStart, LocalDateTime createdAtStart,
LocalDateTime createdAtEnd LocalDateTime createdAtEnd
@@ -1,10 +1,10 @@
package com.onixbyte.helix.domain.web.request; package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.shared.MessageName;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
public record ResetPasswordRequest( public record ResetPasswordRequest(
@NotNull(message = "用户 ID 不能为空") Long id, @NotBlank(message = "{" + MessageName.REQUEST_RESET_PASSWORD_NOT_EMPTY + "}") String password
@NotBlank(message = "密码不能为空") 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,26 +41,19 @@ public class BizException extends RuntimeException {
* REST API endpoints. * REST API endpoints.
*/ */
private final HttpStatus status; private final HttpStatus status;
private final String messageCode;
private final Object[] messageArgs;
/** public BizException(String messageCode, Object... messageArgs) {
* Constructs a new business exception with the specified HTTP status and message.
*
* @param message the detailed error message explaining the business logic violation
*/
public BizException(String message) {
super(message);
this.status = HttpStatus.INTERNAL_SERVER_ERROR; 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) {
* Constructs a new business exception with the specified HTTP status and message.
*
* @param status the HTTP status code to associate with this exception
* @param message the detailed error message explaining the business logic violation
*/
public BizException(HttpStatus status, String message) {
super(message);
this.status = status; this.status = status;
this.messageCode = messageCode;
this.messageArgs = messageArgs == null ? new Object[0] : messageArgs;
} }
/** /**
@@ -71,4 +64,12 @@ public class BizException extends RuntimeException {
public HttpStatus getStatus() { public HttpStatus getStatus() {
return status; return status;
} }
public String getMessageCode() {
return messageCode;
}
public Object[] getMessageArgs() {
return messageArgs;
}
} }
@@ -2,7 +2,6 @@ package com.onixbyte.helix.manager;
import com.onixbyte.helix.shared.CacheName; import com.onixbyte.helix.shared.CacheName;
import com.onixbyte.helix.domain.entity.Asset; import com.onixbyte.helix.domain.entity.Asset;
import com.onixbyte.helix.mapper.AssetMapper;
import com.onixbyte.helix.repository.AssetRepository; import com.onixbyte.helix.repository.AssetRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
@@ -13,12 +12,10 @@ import org.springframework.stereotype.Component;
@Component @Component
public class AssetManager { public class AssetManager {
private final AssetMapper assetMapper;
private final AssetRepository assetRepository; private final AssetRepository assetRepository;
@Autowired @Autowired
public AssetManager(AssetMapper assetMapper, AssetRepository assetRepository) { public AssetManager(AssetRepository assetRepository) {
this.assetMapper = assetMapper;
this.assetRepository = assetRepository; this.assetRepository = assetRepository;
} }
@@ -2,14 +2,13 @@ package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.database.query.wrapper.QueryAuthorityWrapper; import com.onixbyte.helix.domain.database.query.wrapper.QueryAuthorityWrapper;
import com.onixbyte.helix.domain.entity.Authority; import com.onixbyte.helix.domain.entity.Authority;
import com.onixbyte.helix.domain.web.request.EditAuthorityRequest; import com.onixbyte.helix.domain.web.request.AuthorityRequest;
import com.onixbyte.helix.enumeration.Status; import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.AuthorityMapper; import com.onixbyte.helix.mapper.AuthorityMapper;
import com.onixbyte.helix.repository.AuthorityRepository; import com.onixbyte.helix.repository.AuthorityRepository;
import com.onixbyte.helix.shared.CacheName; import com.onixbyte.helix.shared.CacheName;
import org.slf4j.Logger; import com.onixbyte.helix.shared.MessageName;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Example; import org.springframework.data.domain.Example;
@@ -19,13 +18,13 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@Component @Component
public class AuthorityManager { public class AuthorityManager {
private static final Logger log = LoggerFactory.getLogger(AuthorityManager.class);
private final AuthorityMapper authorityMapper; private final AuthorityMapper authorityMapper;
private final AuthorityRepository authorityRepository; private final AuthorityRepository authorityRepository;
@@ -56,20 +55,38 @@ public class AuthorityManager {
return authorityRepository.save(authority); return authorityRepository.save(authority);
} }
/**
* Fully updates an existing authority by ID.
* <p>
* The method loads the target authority, replaces mutable fields ({@code name},
* {@code description}, {@code status}), and refreshes {@code updatedAt} to the current time.
* The update runs within a transactional context.
*
* @param id the ID of the authority to update
* @param authority the source data carrying new field values
* @return the supplied {@link Authority} object
* @throws BizException if the target authority does not exist
*/
@Transactional @Transactional
public Authority update(EditAuthorityRequest request) { public Authority fullUpdateById(Long id, Authority authority) {
var authority = authorityRepository.findById(request.id()) var updatedAt = LocalDateTime.now();
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "找不到指定的权限信息"));
Optional.ofNullable(request.name()) var authorityToUpdate = authorityRepository.findById(id)
.ifPresent(authority::setName); .orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, MessageName.AUTHORITY_NOT_FOUND, id));
authority.setDescription(request.description()); authorityToUpdate.setName(authority.getName());
authorityToUpdate.setDescription(authority.getDescription());
Optional.ofNullable(request.status()) authorityToUpdate.setStatus(authority.getStatus());
.map(Status::valueOf) authorityToUpdate.setUpdatedAt(updatedAt);
.ifPresent(authority::setStatus);
return authority; return authority;
} }
public String findAuthorityNameById(Long authorityId) {
return authorityRepository.findAuthorityNameById(authorityId);
}
public void deleteById(Long authorityId) {
authorityRepository.deleteById(authorityId);
}
} }
@@ -1,24 +1,27 @@
package com.onixbyte.helix.manager; package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.Department; import com.onixbyte.helix.domain.entity.Department;
import com.onixbyte.helix.mapper.DepartmentMapper; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.repository.DepartmentRepository; import com.onixbyte.helix.repository.DepartmentRepository;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Optional; import java.util.Optional;
@Component @Component
public class DepartmentManager { public class DepartmentManager {
private final DepartmentMapper departmentMapper;
private final DepartmentRepository departmentRepository; private final DepartmentRepository departmentRepository;
@Autowired @Autowired
public DepartmentManager(DepartmentMapper departmentMapper, DepartmentRepository departmentRepository) { public DepartmentManager(DepartmentRepository departmentRepository) {
this.departmentMapper = departmentMapper;
this.departmentRepository = departmentRepository; this.departmentRepository = departmentRepository;
} }
@@ -37,4 +40,38 @@ public class DepartmentManager {
public Department save(Department department) { public Department save(Department department) {
return departmentRepository.save(department); return departmentRepository.save(department);
} }
/**
* Fully updates an existing department by ID.
* <p>
* The method loads the target department, replaces mutable fields ({@code name},
* {@code parentId}, {@code sort}, {@code status}), and refreshes {@code updatedAt} to the
* current time.
*
* @param id the ID of the department to update
* @param department the source data carrying new field values
* @return the managed and updated {@link Department} entity
* @throws BizException if the target department does not exist
*/
@Transactional
public Department fullUpdateById(Long id, Department department) {
var updatedAt = LocalDateTime.now();
var departmentToEdit = departmentRepository.findById(id)
.orElseThrow(() -> new BizException(
HttpStatus.NOT_FOUND,
"Department (ID: %d) to be edited not found.".formatted(department.getId()))
);
departmentToEdit.setName(department.getName());
departmentToEdit.setParentId(department.getParentId());
departmentToEdit.setSort(department.getSort());
departmentToEdit.setStatus(department.getStatus());
departmentToEdit.setUpdatedAt(updatedAt);
return departmentToEdit;
}
public boolean existsByName(String name) {
return departmentRepository.existsByName(name);
}
} }
@@ -2,8 +2,6 @@ package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.Menu; import com.onixbyte.helix.domain.entity.Menu;
import com.onixbyte.helix.mapper.MenuMapper; import com.onixbyte.helix.mapper.MenuMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -11,7 +9,7 @@ import java.util.List;
@Component @Component
public class MenuManager { public class MenuManager {
private static final Logger log = LoggerFactory.getLogger(MenuManager.class);
private final MenuMapper menuMapper; private final MenuMapper menuMapper;
@Autowired @Autowired
@@ -1,7 +1,6 @@
package com.onixbyte.helix.manager; package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.Position; import com.onixbyte.helix.domain.entity.Position;
import com.onixbyte.helix.mapper.PositionMapper;
import com.onixbyte.helix.repository.PositionRepository; import com.onixbyte.helix.repository.PositionRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -11,12 +10,10 @@ import org.springframework.stereotype.Component;
@Component @Component
public class PositionManager { public class PositionManager {
private final PositionMapper positionMapper;
private final PositionRepository positionRepository; private final PositionRepository positionRepository;
@Autowired @Autowired
public PositionManager(PositionMapper positionMapper, PositionRepository positionRepository) { public PositionManager(PositionRepository positionRepository) {
this.positionMapper = positionMapper;
this.positionRepository = positionRepository; this.positionRepository = positionRepository;
} }
@@ -1,6 +1,6 @@
package com.onixbyte.helix.manager; package com.onixbyte.helix.manager;
import com.onixbyte.helix.repository.RoleAuthorityRepository; import com.onixbyte.helix.mapper.RoleAuthorityMapper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -9,14 +9,20 @@ import org.springframework.stereotype.Component;
public class RoleAuthorityManager { public class RoleAuthorityManager {
private static final Logger log = LoggerFactory.getLogger(RoleAuthorityManager.class); private static final Logger log = LoggerFactory.getLogger(RoleAuthorityManager.class);
private final RoleAuthorityRepository roleAuthorityRepository; private final RoleAuthorityMapper roleAuthorityMapper;
public RoleAuthorityManager(RoleAuthorityRepository roleAuthorityRepository) { public RoleAuthorityManager(RoleAuthorityMapper roleAuthorityMapper) {
this.roleAuthorityRepository = roleAuthorityRepository; this.roleAuthorityMapper = roleAuthorityMapper;
} }
public void deleteByRoleId(Long roleId) { public void deleteByRoleId(Long roleId) {
var affectedRows = roleAuthorityRepository.deleteByRoleId(roleId); var affectedRows = roleAuthorityMapper.deleteByRoleId(roleId);
log.info("角色 {} 关联的权限绑定已全部移除(共 {} 条)", roleId, affectedRows); log.info("A total of {} authorities linked to Role ID: {} have been successfully cleared.",
affectedRows, roleId);
}
public void deleteByAuthorityId(Long authorityId) {
var affectedRows = roleAuthorityMapper.deleteByAuthorityId(authorityId);
log.info("The binding between {} authorities and the role has been cleared.", affectedRows);
} }
} }
@@ -3,15 +3,17 @@ package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.database.query.wrapper.QueryRoleWrapper; import com.onixbyte.helix.domain.database.query.wrapper.QueryRoleWrapper;
import com.onixbyte.helix.domain.entity.Role; import com.onixbyte.helix.domain.entity.Role;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.RoleAuthorityMapper;
import com.onixbyte.helix.mapper.RoleMapper; import com.onixbyte.helix.mapper.RoleMapper;
import com.onixbyte.helix.repository.RoleRepository; 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.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*; import org.springframework.data.domain.*;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -20,18 +22,18 @@ public class RoleManager {
private final RoleMapper roleMapper; private final RoleMapper roleMapper;
private final RoleRepository roleRepository; private final RoleRepository roleRepository;
private final RoleAuthorityMapper roleAuthorityMapper; private final MessageUtil messageUtil;
@Autowired @Autowired
public RoleManager(RoleMapper roleMapper, RoleRepository roleRepository, RoleAuthorityMapper roleAuthorityMapper) { public RoleManager(RoleMapper roleMapper, RoleRepository roleRepository, MessageUtil messageUtil) {
this.roleMapper = roleMapper; this.roleMapper = roleMapper;
this.roleRepository = roleRepository; this.roleRepository = roleRepository;
this.roleAuthorityMapper = roleAuthorityMapper; this.messageUtil = messageUtil;
} }
public void validateRoles(List<Long> roleIds) { public void validateRoles(List<Long> roleIds) {
if (!roleMapper.areRolesExisted(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);
} }
} }
@@ -55,23 +57,24 @@ public class RoleManager {
} }
@Transactional @Transactional
public void updateRole(Role role) { public Role fullUpdateById(Long id, Role role) {
var roleToUpdate = roleRepository.findById(role.getId()) var updatedAt = LocalDateTime.now();
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "找不到指定的角色信息。"));
Optional.ofNullable(role.getName()) var roleToUpdate = roleRepository.findById(id)
.ifPresent(roleToUpdate::setName); .orElseThrow(() -> new BizException(
HttpStatus.NOT_FOUND,
Optional.ofNullable(role.getCode()) messageUtil.getMessage(MessageName.ROLE_NOT_FOUND, id))
.ifPresent(roleToUpdate::setCode); );
Optional.ofNullable(role.getSort())
.ifPresent(roleToUpdate::setSort);
roleToUpdate.setName(role.getName());
roleToUpdate.setCode(role.getCode());
roleToUpdate.setSort(role.getSort());
roleToUpdate.setDefaultValue(role.getDefaultValue());
roleToUpdate.setDescription(role.getDescription()); roleToUpdate.setDescription(role.getDescription());
roleToUpdate.setStatus(role.getStatus());
roleToUpdate.setUpdatedAt(updatedAt);
Optional.ofNullable(role.getStatus()) return role;
.ifPresent(roleToUpdate::setStatus);
} }
public void deleteRole(Long id) { 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.mapper.UserMapper;
import com.onixbyte.helix.repository.UserRepository; import com.onixbyte.helix.repository.UserRepository;
import com.onixbyte.region.Region; import com.onixbyte.region.Region;
import com.onixbyte.helix.shared.MessageName;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.CachePut;
@@ -112,7 +113,7 @@ public class UserManager {
@Transactional(rollbackFor = Throwable.class) @Transactional(rollbackFor = Throwable.class)
public User updateUser(User user) { public User updateUser(User user) {
var userToUpdate = userRepository.findById(user.getId()) 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()) Optional.ofNullable(user.getFullName())
.filter(StringUtils::isNotBlank) .filter(StringUtils::isNotBlank)
@@ -150,9 +151,9 @@ public class UserManager {
} }
@Transactional(rollbackFor = Throwable.class) @Transactional(rollbackFor = Throwable.class)
public void updateUserPassword(ResetPasswordRequest request) { public void updatePasswordById(Long id, ResetPasswordRequest request) {
userCredentialMapper.updateUserCredential( userCredentialMapper.updateUserCredential(
request.id(), id,
passwordEncoder.encode(request.password()) passwordEncoder.encode(request.password())
); );
} }
@@ -1,8 +1,6 @@
package com.onixbyte.helix.manager; package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.UserRole; import com.onixbyte.helix.domain.entity.UserRole;
import com.onixbyte.helix.domain.entity.embeddable.UserRoleId;
import com.onixbyte.helix.mapper.UserRoleMapper;
import com.onixbyte.helix.repository.UserRoleRepository; import com.onixbyte.helix.repository.UserRoleRepository;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -15,12 +13,10 @@ import java.util.List;
public class UserRoleManager { public class UserRoleManager {
private static final Logger log = LoggerFactory.getLogger(UserRoleManager.class); private static final Logger log = LoggerFactory.getLogger(UserRoleManager.class);
private final UserRoleMapper userRoleMapper;
private final UserRoleRepository userRoleRepository; private final UserRoleRepository userRoleRepository;
@Autowired @Autowired
public UserRoleManager(UserRoleMapper userRoleMapper, UserRoleRepository userRoleRepository) { public UserRoleManager(UserRoleRepository userRoleRepository) {
this.userRoleMapper = userRoleMapper;
this.userRoleRepository = userRoleRepository; this.userRoleRepository = userRoleRepository;
} }
@@ -7,4 +7,6 @@ import org.apache.ibatis.annotations.Param;
public interface RoleAuthorityMapper { public interface RoleAuthorityMapper {
int deleteByRoleId(@Param("roleId") Long roleId); int deleteByRoleId(@Param("roleId") Long roleId);
int deleteByAuthorityId(@Param("authorityId") Long authorityId);
} }
@@ -2,8 +2,16 @@ package com.onixbyte.helix.repository;
import com.onixbyte.helix.domain.entity.Authority; import com.onixbyte.helix.domain.entity.Authority;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
public interface AuthorityRepository extends JpaRepository<Authority, Long> { public interface AuthorityRepository extends JpaRepository<Authority, Long> {
@Query("""
select a.name
from Authority a
where a.id = :authorityId
""")
String findAuthorityNameById(Long authorityId);
} }
@@ -15,4 +15,6 @@ public interface DepartmentRepository extends JpaRepository<Department, Long> {
or d.parentId = :parentId or d.parentId = :parentId
""") """)
Integer findMaxSort(Long parentId); Integer findMaxSort(Long parentId);
boolean existsByName(String name);
} }
@@ -8,6 +8,7 @@ import com.onixbyte.helix.manager.AuthorityManager;
import com.onixbyte.helix.manager.UserManager; import com.onixbyte.helix.manager.UserManager;
import com.onixbyte.helix.repository.UserCredentialRepository; import com.onixbyte.helix.repository.UserCredentialRepository;
import com.onixbyte.helix.security.authentication.UsernamePasswordAuthentication; import com.onixbyte.helix.security.authentication.UsernamePasswordAuthentication;
import com.onixbyte.helix.shared.MessageName;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -46,14 +47,14 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof UsernamePasswordAuthentication usernamePasswordAuthentication)) { 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 // get user from database
var user = userManager.selectByUsername(usernamePasswordAuthentication.getPrincipal()); var user = userManager.selectByUsername(usernamePasswordAuthentication.getPrincipal());
if (Objects.isNull(user)) { if (Objects.isNull(user)) {
log.error("User {} is trying to authenticate but no user found.", usernamePasswordAuthentication.getPrincipal()); 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 // get user credentials from database
@@ -61,12 +62,12 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
.provider(CredentialProvider.LOCAL) .provider(CredentialProvider.LOCAL)
.userId(user.getId()) .userId(user.getId())
.build())) .build()))
.orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, "您还没有配置密码,请使用第三方账号登录")); .orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, MessageName.AUTH_PROVIDER_PASSWORD_NOT_CONFIGURED));
// validate password // validate password
if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), userCredentials.getCredential())) { if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), userCredentials.getCredential())) {
log.error("User {} is trying to authenticate but password is incorrect.", usernamePasswordAuthentication.getPrincipal()); 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 // erase credentials
@@ -4,6 +4,7 @@ import com.onixbyte.helix.domain.entity.Asset;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.manager.AssetManager; import com.onixbyte.helix.manager.AssetManager;
import com.onixbyte.helix.properties.AssetProperties; import com.onixbyte.helix.properties.AssetProperties;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.SecurityUtil; import com.onixbyte.helix.utils.SecurityUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -62,7 +63,7 @@ public class AssetService {
if (Objects.isNull(prefix) || prefix.isBlank() || prefix.startsWith("/") || prefix.startsWith("..")) { if (Objects.isNull(prefix) || prefix.isBlank() || prefix.startsWith("/") || prefix.startsWith("..")) {
throw new BizException( throw new BizException(
HttpStatus.INTERNAL_SERVER_ERROR, 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); var asset = assetManager.queryByAssetId(assetId);
if (!Objects.equals(currentUser.getId(), asset.getUploadBy())) { 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); assetManager.deleteById(assetId);
@@ -9,6 +9,7 @@ import com.onixbyte.helix.manager.CaptchaManager;
import com.onixbyte.helix.manager.SecurityManager; import com.onixbyte.helix.manager.SecurityManager;
import com.onixbyte.helix.manager.SettingManager; import com.onixbyte.helix.manager.SettingManager;
import com.onixbyte.helix.security.authentication.UsernamePasswordAuthentication; import com.onixbyte.helix.security.authentication.UsernamePasswordAuthentication;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.shared.SettingName; import com.onixbyte.helix.shared.SettingName;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -63,10 +64,10 @@ public class AuthService {
var rawCaptcha = captchaManager.getCaptcha(uuid); var rawCaptcha = captchaManager.getCaptcha(uuid);
if (Objects.isNull(rawCaptcha) || rawCaptcha.isBlank()) { 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())) { 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() _authentication.getClass()
); );
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR, throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
"Cannot perform login due to server crashes."); MessageName.AUTH_LOGIN_FAILED);
} }
return authentication.getDetails(); return authentication.getDetails();
@@ -2,26 +2,28 @@ package com.onixbyte.helix.service;
import com.onixbyte.helix.domain.database.query.wrapper.QueryAuthorityWrapper; import com.onixbyte.helix.domain.database.query.wrapper.QueryAuthorityWrapper;
import com.onixbyte.helix.domain.entity.Authority; import com.onixbyte.helix.domain.entity.Authority;
import com.onixbyte.helix.domain.web.request.AddAuthorityRequest; import com.onixbyte.helix.domain.web.request.AuthorityRequest;
import com.onixbyte.helix.domain.web.request.EditAuthorityRequest;
import com.onixbyte.helix.domain.web.request.QueryAuthorityRequest; import com.onixbyte.helix.domain.web.request.QueryAuthorityRequest;
import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.manager.AuthorityManager; 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.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service @Service
public class AuthorityService { public class AuthorityService {
private final AuthorityManager authorityManager; private final AuthorityManager authorityManager;
private final RoleAuthorityManager roleAuthorityManager;
public AuthorityService(AuthorityManager authorityManager) { public AuthorityService(AuthorityManager authorityManager, RoleAuthorityManager roleAuthorityManager) {
this.authorityManager = authorityManager; this.authorityManager = authorityManager;
this.roleAuthorityManager = roleAuthorityManager;
} }
public Page<Authority> getAuthorities(Pageable pageable, QueryAuthorityRequest request) { public Page<Authority> getAuthorities(Pageable pageable, QueryAuthorityRequest request) {
@@ -30,24 +32,40 @@ public class AuthorityService {
return authorityManager.selectAll(pageable, wrapper); return authorityManager.selectAll(pageable, wrapper);
} }
public Authority addAuthority(AddAuthorityRequest request) { public Authority addAuthority(AuthorityRequest request) {
var authority = Authority.builder() var authority = Authority.builder()
.code(request.code()) .code(request.code())
.name(request.name()) .name(request.name())
.description(request.description()) .description(request.description())
.status(Optional.ofNullable(request.status()) .status(request.status())
.map(Status::valueOf)
.orElse(Status.ACTIVE))
.build(); .build();
if (authorityManager.existsByCode(authority)) { 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); return authorityManager.save(authority);
} }
public Authority editAuthority(EditAuthorityRequest request) { public Authority editAuthority(Long id, AuthorityRequest request) {
return authorityManager.update(request); return authorityManager.fullUpdateById(id, Authority.builder()
.name(request.name())
.description(request.description())
.status(request.status())
.build());
}
@Transactional(rollbackFor = Throwable.class)
public String deleteAuthority(Long authorityId) {
var authorityName = authorityManager.findAuthorityNameById(authorityId);
if (StringUtils.isBlank(authorityName)) {
throw new BizException(HttpStatus.NOT_FOUND, MessageName.AUTHORITY_NOT_FOUND, authorityId);
}
roleAuthorityManager.deleteByAuthorityId(authorityId);
authorityManager.deleteById(authorityId);
return authorityName;
} }
} }
@@ -1,13 +1,14 @@
package com.onixbyte.helix.service; package com.onixbyte.helix.service;
import com.onixbyte.captcha.Producer; 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.entity.Setting;
import com.onixbyte.helix.domain.web.response.CaptchaResponse; import com.onixbyte.helix.domain.web.response.CaptchaResponse;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.manager.CaptchaManager; import com.onixbyte.helix.manager.CaptchaManager;
import com.onixbyte.helix.manager.SettingManager; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream; import org.springframework.util.FastByteArrayOutputStream;
@@ -66,7 +67,7 @@ public class CaptchaService {
Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return new CaptchaResponse(captchaDataUrl, uuid); return new CaptchaResponse(captchaDataUrl, uuid);
} catch (IOException e) { } catch (IOException e) {
throw new BizException("无法生成验证码图片。"); throw new BizException(MessageName.CAPTCHA_GENERATE_FAILED);
} }
} }
} }
@@ -2,12 +2,15 @@ package com.onixbyte.helix.service;
import com.onixbyte.helix.domain.common.TreeNode; import com.onixbyte.helix.domain.common.TreeNode;
import com.onixbyte.helix.domain.entity.Department; import com.onixbyte.helix.domain.entity.Department;
import com.onixbyte.helix.domain.web.request.AddDepartmentRequest; import com.onixbyte.helix.domain.web.request.DepartmentRequest;
import com.onixbyte.helix.enumeration.Status; import com.onixbyte.helix.enumeration.Status;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.manager.DepartmentManager; import com.onixbyte.helix.manager.DepartmentManager;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.TreeUtil; import com.onixbyte.helix.utils.TreeUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -35,9 +38,13 @@ public class DepartmentService {
} }
@Transactional(rollbackFor = Throwable.class) @Transactional(rollbackFor = Throwable.class)
public Department addDepartment(AddDepartmentRequest request) { public Department addDepartment(DepartmentRequest request) {
var createdAt = LocalDateTime.now(); var createdAt = LocalDateTime.now();
if (departmentManager.existsByName(request.name())) {
throw new BizException(HttpStatus.CONFLICT, MessageName.REQUEST_CREATE_DEPARTMENT_NAME_DUPLICATED);
}
var parentId = request.parentId(); var parentId = request.parentId();
var sort = Optional.ofNullable(request.sort()) var sort = Optional.ofNullable(request.sort())
.orElseGet(() -> departmentManager.getNextSort(parentId)); .orElseGet(() -> departmentManager.getNextSort(parentId));
@@ -51,4 +58,17 @@ public class DepartmentService {
.updatedAt(createdAt) .updatedAt(createdAt)
.build()); .build());
} }
public Department editDepartment(Long id, DepartmentRequest request) {
if (departmentManager.existsByName(request.name())) {
throw new BizException(HttpStatus.CONFLICT, MessageName.REQUEST_CREATE_DEPARTMENT_NAME_DUPLICATED);
}
return departmentManager.fullUpdateById(id, Department.builder()
.name(request.name())
.parentId(request.parentId())
.sort(request.sort())
.status(request.status())
.build());
}
} }
@@ -5,8 +5,6 @@ import com.onixbyte.helix.domain.common.TreeNode;
import com.onixbyte.helix.manager.MenuManager; import com.onixbyte.helix.manager.MenuManager;
import com.onixbyte.helix.utils.SecurityUtil; import com.onixbyte.helix.utils.SecurityUtil;
import com.onixbyte.helix.utils.TreeUtil; import com.onixbyte.helix.utils.TreeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -14,7 +12,7 @@ import java.util.List;
@Service @Service
public class MenuService { public class MenuService {
private static final Logger log = LoggerFactory.getLogger(MenuService.class);
private final MenuManager menuManager; private final MenuManager menuManager;
@Autowired @Autowired
@@ -1,19 +1,22 @@
package com.onixbyte.helix.service; 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.database.query.wrapper.QueryRoleWrapper;
import com.onixbyte.helix.domain.entity.Role; 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.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.RoleAuthorityManager;
import com.onixbyte.helix.manager.RoleManager; import com.onixbyte.helix.manager.RoleManager;
import com.onixbyte.helix.manager.UserRoleManager; import com.onixbyte.helix.manager.UserRoleManager;
import com.onixbyte.helix.shared.MessageName;
import com.onixbyte.helix.utils.MessageUtil;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Optional; import java.util.Optional;
@@ -24,16 +27,19 @@ public class RoleService {
private final RoleManager roleManager; private final RoleManager roleManager;
private final RoleAuthorityManager roleAuthorityManager; private final RoleAuthorityManager roleAuthorityManager;
private final UserRoleManager userRoleManager; private final UserRoleManager userRoleManager;
private final MessageUtil messageUtil;
@Autowired @Autowired
public RoleService( public RoleService(
RoleManager roleManager, RoleManager roleManager,
RoleAuthorityManager roleAuthorityManager, RoleAuthorityManager roleAuthorityManager,
UserRoleManager userRoleManager UserRoleManager userRoleManager,
MessageUtil messageUtil
) { ) {
this.roleManager = roleManager; this.roleManager = roleManager;
this.roleAuthorityManager = roleAuthorityManager; this.roleAuthorityManager = roleAuthorityManager;
this.userRoleManager = userRoleManager; this.userRoleManager = userRoleManager;
this.messageUtil = messageUtil;
} }
public Page<Role> getRoles(Pageable pageable, QueryRoleRequest request) { public Page<Role> getRoles(Pageable pageable, QueryRoleRequest request) {
@@ -55,11 +61,12 @@ public class RoleService {
return roleManager.selectAll(pageable, wrapper); return roleManager.selectAll(pageable, wrapper);
} }
public Role addRole(AddRoleRequest request) { public Role addRole(RoleRequest request) {
var isDefaultRole = Optional.ofNullable(request.defaultValue()) var isDefaultRole = Optional.of(request)
.map(RoleRequest::defaultValue)
.orElse(false); .orElse(false);
var status = Optional.ofNullable(request.status()) var status = Optional.of(request)
.map(Status::valueOf) .map(RoleRequest::status)
.orElse(Status.ACTIVE); .orElse(Status.ACTIVE);
var role = Role.builder() var role = Role.builder()
@@ -75,24 +82,29 @@ public class RoleService {
} }
@Transactional @Transactional
public void editRole(EditRoleRequest request) { public Role editRole(Long id, RoleRequest request) {
roleManager.updateRole(Role.builder() return roleManager.fullUpdateById(id, Role.builder()
.id(request.id())
.name(request.name()) .name(request.name())
.code(request.code()) .code(request.code())
.sort(request.sort()) .sort(request.sort())
.defaultValue(request.defaultValue()) .defaultValue(request.defaultValue())
.description(request.description()) .description(request.description())
.status(Optional.ofNullable(request.status()) .status(request.status())
.map(Status::valueOf)
.orElse(null))
.build()); .build());
} }
@Transactional @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); roleAuthorityManager.deleteByRoleId(id);
userRoleManager.deleteByRoleId(id); userRoleManager.deleteByRoleId(id);
roleManager.deleteRole(id); roleManager.deleteRole(id);
return role.getName();
} }
} }
@@ -143,9 +143,9 @@ public class UserService {
} }
@Transactional(rollbackFor = Throwable.class) @Transactional(rollbackFor = Throwable.class)
public void updateUser(EditUserRequest request) { public UserDetailResponse updateUser(Long id, EditUserRequest request) {
userManager.updateUser(User.builder() userManager.updateUser(User.builder()
.id(request.id()) .id(id)
.fullName(request.fullName()) .fullName(request.fullName())
.email(request.email()) .email(request.email())
.regionAbbreviation(request.regionAbbreviation()) .regionAbbreviation(request.regionAbbreviation())
@@ -155,6 +155,8 @@ public class UserService {
.departmentId(request.departmentId()) .departmentId(request.departmentId())
.positionId(request.positionId()) .positionId(request.positionId())
.build()); .build());
return getUserDetailByUserId(id);
} }
public UserDetailResponse getUserDetailByUserId(Long userId) { public UserDetailResponse getUserDetailByUserId(Long userId) {
@@ -162,8 +164,8 @@ public class UserService {
} }
@Transactional(rollbackFor = Throwable.class) @Transactional(rollbackFor = Throwable.class)
public void resetPassword(ResetPasswordRequest request) { public void resetPassword(Long id, ResetPasswordRequest request) {
userManager.updateUserPassword(request); userManager.updatePasswordById(id, request);
} }
@Transactional(rollbackFor = Throwable.class) @Transactional(rollbackFor = Throwable.class)
@@ -0,0 +1,84 @@
/*
* 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";
public static final String REQUEST_CREATE_DEPARTMENT_NAME_DUPLICATED = "request.create-department.name-duplicated";
}
@@ -0,0 +1,42 @@
/*
* 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.utils;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
@Component
public class MessageUtil {
private final MessageSource messageSource;
public MessageUtil(MessageSource messageSource) {
this.messageSource = messageSource;
}
public String getMessage(String code, Object... args) {
var locale = LocaleContextHolder.getLocale();
return messageSource.getMessage(code, args, locale);
}
}
@@ -2,6 +2,7 @@ package com.onixbyte.helix.utils;
import com.onixbyte.helix.domain.entity.User; import com.onixbyte.helix.domain.entity.User;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.shared.MessageName;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -32,7 +33,7 @@ public class SecurityUtil {
if (!(_details instanceof User user)) { if (!(_details instanceof User user)) {
log.error("Authentication details is {}", _details); 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; return user;
@@ -3,6 +3,7 @@ package com.onixbyte.helix.utils;
import com.onixbyte.helix.domain.common.Treeable; import com.onixbyte.helix.domain.common.Treeable;
import com.onixbyte.helix.domain.common.TreeNode; import com.onixbyte.helix.domain.common.TreeNode;
import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.shared.MessageName;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -43,7 +44,7 @@ public class TreeUtil {
// Ensure only 1 root node is included // Ensure only 1 root node is included
if (rootItems.size() > 1) { 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 // Get root item and build root node
+2
View File
@@ -27,6 +27,8 @@ spring:
hikari: hikari:
minimum-idle: 1 minimum-idle: 1
maximum-pool-size: 10 maximum-pool-size: 10
messages:
basename: i18n/messages
flyway: flyway:
enabled: true enabled: true
baseline-on-migrate: true baseline-on-migrate: true
@@ -0,0 +1,76 @@
#
# 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.
#
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=Username or password is incorrect.
captcha.generate-failed = Unable to generate captcha image.
role.not-exists=Name of the authority cannot be null.
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.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.
request.create-department.name-duplicated=Name of the department already exists.
@@ -0,0 +1,77 @@
#
# 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.
#
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.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.
request.create-department.name-duplicated=Name of the department already exists.
@@ -0,0 +1,77 @@
#
# 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.
#
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.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。
request.create-department.name-duplicated=部门名称已存在。
@@ -2,7 +2,7 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.onixbyte.helix.mapper.AuthorityMapper"> <mapper namespace="com.onixbyte.helix.mapper.AuthorityMapper">
<select id="selectByUserId" parameterType="long"> <select id="selectByUserId" parameterType="long" resultType="com.onixbyte.helix.domain.entity.Authority">
SELECT DISTINCT a.id, a.code, a.name, a.description, a.status, a.created_at, a.updated_at SELECT DISTINCT a.id, a.code, a.name, a.description, a.status, a.created_at, a.updated_at
FROM authority a FROM authority a
JOIN role_authority ra ON a.id = ra.authority_id JOIN role_authority ra ON a.id = ra.authority_id
@@ -7,4 +7,10 @@
FROM role_authority FROM role_authority
WHERE role_id = #{roleId} WHERE role_id = #{roleId}
</delete> </delete>
<delete id="deleteByAuthorityId" parameterType="long">
DELETE
FROM role_authority
WHERE authority_id = #{authorityId}
</delete>
</mapper> </mapper>
+1 -1
View File
@@ -52,7 +52,7 @@
</if> </if>
</select> </select>
<select id="count" parameterType="com.onixbyte.helix.domain.database.query.wrapper.QueryRoleWrapper"> <select id="count" parameterType="com.onixbyte.helix.domain.database.query.wrapper.QueryRoleWrapper" resultType="int">
SELECT COUNT(*) SELECT COUNT(*)
FROM role FROM role
<where> <where>