feat : thêm swagger
This commit is contained in:
parent
744a2fa40b
commit
abd31bf956
|
|
@ -0,0 +1,74 @@
|
|||
package com.vega.hrm.controller;
|
||||
|
||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||
import com.vega.hrm.request.function.CreateFunctionRequest;
|
||||
import com.vega.hrm.request.function.UpdateFunctionRequest;
|
||||
import com.vega.hrm.response.FunctionDto;
|
||||
import com.vega.hrm.service.FunctionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("api/auth/function")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Auth - Function/Menu", description = "Quản lý menu và chức năng hệ thống")
|
||||
public class FunctionController {
|
||||
private final FunctionService functionService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "Tạo menu mới", description = "Tạo menu/chức năng mới trong hệ thống.")
|
||||
public ResponseEntity<BaseResponse<FunctionDto>> createFunction(
|
||||
@Valid @RequestBody CreateFunctionRequest request,
|
||||
@Parameter(description = "Người tạo (từ token)") @RequestParam(defaultValue = "admin") String currentUser
|
||||
) {
|
||||
return ResponseEntity.ok(functionService.createFunction(request, currentUser));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "Cập nhật menu", description = "Cập nhật thông tin menu/chức năng.")
|
||||
public ResponseEntity<BaseResponse<FunctionDto>> updateFunction(@Valid @RequestBody UpdateFunctionRequest request) {
|
||||
return ResponseEntity.ok(functionService.updateFunction(request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "Xóa menu", description = "Xóa menu/chức năng (không thể xóa nếu có menu con).")
|
||||
public ResponseEntity<BaseResponse<Boolean>> deleteFunction(
|
||||
@Parameter(description = "ID của menu") @RequestParam String functionId
|
||||
) {
|
||||
return ResponseEntity.ok(functionService.deleteFunction(functionId));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "Lấy danh sách menu", description = "Lấy tất cả menu dạng flat list.")
|
||||
public ResponseEntity<BaseResponse<List<FunctionDto>>> getAllFunctions() {
|
||||
return ResponseEntity.ok(functionService.getAllFunctions());
|
||||
}
|
||||
|
||||
@GetMapping("/tree")
|
||||
@Operation(summary = "Lấy cây menu", description = "Lấy menu dạng cây phân cấp (parent-children).")
|
||||
public ResponseEntity<BaseResponse<List<FunctionDto>>> getMenuTree() {
|
||||
return ResponseEntity.ok(functionService.getMenuTree());
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@Operation(summary = "Lấy chi tiết menu", description = "Lấy thông tin chi tiết của menu/chức năng.")
|
||||
public ResponseEntity<BaseResponse<FunctionDto>> getFunctionDetail(
|
||||
@Parameter(description = "ID của menu") @RequestParam String functionId
|
||||
) {
|
||||
return ResponseEntity.ok(functionService.getFunctionDetail(functionId));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.vega.hrm.controller;
|
||||
|
||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||
import com.vega.hrm.request.role.AssignPermissionsRequest;
|
||||
import com.vega.hrm.request.role.CreateRoleRequest;
|
||||
import com.vega.hrm.request.role.UpdateRoleRequest;
|
||||
import com.vega.hrm.response.RoleDto;
|
||||
import com.vega.hrm.service.RoleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("api/auth/role")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Auth - Role", description = "Quản lý vai trò và phân quyền")
|
||||
public class RoleController {
|
||||
private final RoleService roleService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "Tạo role mới", description = "Tạo vai trò mới và gán quyền menu.")
|
||||
public ResponseEntity<BaseResponse<RoleDto>> createRole(
|
||||
@Valid @RequestBody CreateRoleRequest request,
|
||||
@Parameter(description = "Người tạo (từ token)") @RequestParam(defaultValue = "admin") String currentUser
|
||||
) {
|
||||
return ResponseEntity.ok(roleService.createRole(request, currentUser));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "Cập nhật role", description = "Cập nhật thông tin vai trò.")
|
||||
public ResponseEntity<BaseResponse<RoleDto>> updateRole(@Valid @RequestBody UpdateRoleRequest request) {
|
||||
return ResponseEntity.ok(roleService.updateRole(request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "Xóa role", description = "Xóa vai trò (soft delete).")
|
||||
public ResponseEntity<BaseResponse<Boolean>> deleteRole(
|
||||
@Parameter(description = "ID của role") @RequestParam String roleId
|
||||
) {
|
||||
return ResponseEntity.ok(roleService.deleteRole(roleId));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "Lấy danh sách role", description = "Lấy tất cả vai trò trong hệ thống.")
|
||||
public ResponseEntity<BaseResponse<List<RoleDto>>> getAllRoles() {
|
||||
return ResponseEntity.ok(roleService.getAllRoles());
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@Operation(summary = "Lấy chi tiết role", description = "Lấy thông tin chi tiết vai trò và quyền.")
|
||||
public ResponseEntity<BaseResponse<RoleDto>> getRoleDetail(
|
||||
@Parameter(description = "ID của role") @RequestParam String roleId
|
||||
) {
|
||||
return ResponseEntity.ok(roleService.getRoleDetail(roleId));
|
||||
}
|
||||
|
||||
@PostMapping("/assign-permissions")
|
||||
@Operation(summary = "Gán quyền cho role", description = "Gán danh sách menu/function cho vai trò.")
|
||||
public ResponseEntity<BaseResponse<Boolean>> assignPermissions(
|
||||
@Valid @RequestBody AssignPermissionsRequest request,
|
||||
@Parameter(description = "Người thực hiện (từ token)") @RequestParam(defaultValue = "admin") String currentUser
|
||||
) {
|
||||
return ResponseEntity.ok(roleService.assignPermissions(request, currentUser));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,22 +1,33 @@
|
|||
package com.vega.hrm.controller;
|
||||
|
||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||
import com.vega.hrm.request.user.ChangePasswordRequest;
|
||||
import com.vega.hrm.request.user.CreateUserRequest;
|
||||
import com.vega.hrm.request.user.ForgotPasswordRequest;
|
||||
import com.vega.hrm.request.user.LoginRequest;
|
||||
import com.vega.hrm.request.user.RegisterRequest;
|
||||
import com.vega.hrm.request.user.UpdateProfileRequest;
|
||||
import com.vega.hrm.response.UserInfoResponse;
|
||||
import com.vega.hrm.service.UserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("api/auth/user")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Auth - User", description = "Các API liên quan đến xác thực và quản lý người dùng nội bộ")
|
||||
@Tag(name = "Auth - User", description = "Các API liên quan đến xác thực và quản lý người dùng")
|
||||
public class UserController {
|
||||
private final UserService userService;
|
||||
|
||||
|
|
@ -27,8 +38,52 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PostMapping("/insert")
|
||||
@Operation(summary = "Tạo người dùng nội bộ", description = "Tạo tài khoản quản trị viên/nhân sự mới.")
|
||||
public ResponseEntity<BaseResponse<Boolean>> insert(@RequestBody CreateUserRequest request) {
|
||||
@Operation(summary = "Tạo người dùng nội bộ (Admin)", description = "Tạo tài khoản quản trị viên/nhân sự mới bởi admin và gán role.")
|
||||
public ResponseEntity<BaseResponse<Boolean>> insert(@Valid @RequestBody CreateUserRequest request) {
|
||||
return ResponseEntity.ok(userService.insert(request));
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
@Operation(summary = "Đăng ký tài khoản", description = "Người dùng tự đăng ký tài khoản mới.")
|
||||
public ResponseEntity<BaseResponse<Boolean>> register(@Valid @RequestBody RegisterRequest request) {
|
||||
return ResponseEntity.ok(userService.register(request));
|
||||
}
|
||||
|
||||
@GetMapping("/info")
|
||||
@Operation(summary = "Lấy thông tin tài khoản", description = "Lấy thông tin chi tiết của người dùng theo User ID.")
|
||||
public ResponseEntity<BaseResponse<UserInfoResponse>> getUserInfo(
|
||||
@Parameter(description = "ID của người dùng")
|
||||
@RequestParam String userId
|
||||
) {
|
||||
return ResponseEntity.ok(userService.getUserInfo(userId));
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
@Operation(summary = "Cập nhật thông tin cá nhân", description = "Cập nhật họ tên, email của người dùng.")
|
||||
public ResponseEntity<BaseResponse<Boolean>> updateProfile(@Valid @RequestBody UpdateProfileRequest request) {
|
||||
return ResponseEntity.ok(userService.updateProfile(request));
|
||||
}
|
||||
|
||||
@PutMapping("/change-password")
|
||||
@Operation(summary = "Đổi mật khẩu", description = "Người dùng đổi mật khẩu khi biết mật khẩu cũ.")
|
||||
public ResponseEntity<BaseResponse<Boolean>> changePassword(@Valid @RequestBody ChangePasswordRequest request) {
|
||||
return ResponseEntity.ok(userService.changePassword(request));
|
||||
}
|
||||
|
||||
@PostMapping("/forgot-password")
|
||||
@Operation(summary = "Quên mật khẩu", description = "Tạo mật khẩu mới và gửi qua email khi người dùng quên mật khẩu.")
|
||||
public ResponseEntity<BaseResponse<String>> forgotPassword(@Valid @RequestBody ForgotPasswordRequest request) {
|
||||
return ResponseEntity.ok(userService.forgotPassword(request));
|
||||
}
|
||||
|
||||
@PostMapping("/upload-avatar")
|
||||
@Operation(summary = "Upload ảnh đại diện", description = "Upload ảnh đại diện cho người dùng (jpg, jpeg, png, gif).")
|
||||
public ResponseEntity<BaseResponse<String>> uploadAvatar(
|
||||
@Parameter(description = "ID của người dùng")
|
||||
@RequestParam String userId,
|
||||
@Parameter(description = "File ảnh đại diện")
|
||||
@RequestParam("file") MultipartFile file
|
||||
) {
|
||||
return ResponseEntity.ok(userService.uploadAvatar(userId, file));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.vega.hrm.request.function;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CreateFunctionRequest {
|
||||
@NotBlank(message = "Tên menu không được để trống")
|
||||
@Size(max = 200, message = "Tên menu không được vượt quá 200 ký tự")
|
||||
private String functionName;
|
||||
|
||||
@NotNull(message = "Cấp độ menu không được để trống")
|
||||
private Long functionLevel; // 1: menu cha, 2: menu con, 3: sub menu...
|
||||
|
||||
@Size(max = 100, message = "URL không được vượt quá 100 ký tự")
|
||||
private String functionUrl; // Đường dẫn route
|
||||
|
||||
@NotNull(message = "Thứ tự hiển thị không được để trống")
|
||||
private Long functionOrder; // Thứ tự sắp xếp
|
||||
|
||||
private String parentId; // ID menu cha (null nếu là menu top level)
|
||||
|
||||
@NotBlank(message = "Trạng thái hiển thị không được để trống")
|
||||
@Size(max = 20, message = "Trạng thái hiển thị không được vượt quá 20 ký tự")
|
||||
private String functionDisplay; // "show" / "hide"
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.vega.hrm.request.function;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class UpdateFunctionRequest {
|
||||
@NotBlank(message = "Function ID không được để trống")
|
||||
private String functionId;
|
||||
|
||||
@NotBlank(message = "Tên menu không được để trống")
|
||||
@Size(max = 200, message = "Tên menu không được vượt quá 200 ký tự")
|
||||
private String functionName;
|
||||
|
||||
@NotNull(message = "Cấp độ menu không được để trống")
|
||||
private Long functionLevel;
|
||||
|
||||
@Size(max = 100, message = "URL không được vượt quá 100 ký tự")
|
||||
private String functionUrl;
|
||||
|
||||
@NotNull(message = "Thứ tự hiển thị không được để trống")
|
||||
private Long functionOrder;
|
||||
|
||||
@NotBlank(message = "Trạng thái hiển thị không được để trống")
|
||||
@Size(max = 20, message = "Trạng thái hiển thị không được vượt quá 20 ký tự")
|
||||
private String functionDisplay;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.vega.hrm.request.role;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AssignPermissionsRequest {
|
||||
@NotBlank(message = "Role ID không được để trống")
|
||||
private String roleId;
|
||||
|
||||
@NotEmpty(message = "Danh sách quyền không được để trống")
|
||||
private List<UUID> functionIds;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.vega.hrm.request.role;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CreateRoleRequest {
|
||||
@NotBlank(message = "Tên role không được để trống")
|
||||
@Size(max = 200, message = "Tên role không được vượt quá 200 ký tự")
|
||||
private String roleName;
|
||||
|
||||
@Size(max = 1000, message = "Mô tả không được vượt quá 1000 ký tự")
|
||||
private String description;
|
||||
|
||||
private List<UUID> functionIds; // Danh sách function/menu IDs được gán cho role
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.vega.hrm.request.role;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class UpdateRoleRequest {
|
||||
@NotBlank(message = "Role ID không được để trống")
|
||||
private String roleId;
|
||||
|
||||
@NotBlank(message = "Tên role không được để trống")
|
||||
@Size(max = 200, message = "Tên role không được vượt quá 200 ký tự")
|
||||
private String roleName;
|
||||
|
||||
@Size(max = 1000, message = "Mô tả không được vượt quá 1000 ký tự")
|
||||
private String description;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.vega.hrm.request.user;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ChangePasswordRequest {
|
||||
@NotBlank(message = "User ID không được để trống")
|
||||
private String userId;
|
||||
|
||||
@NotBlank(message = "Mật khẩu cũ không được để trống")
|
||||
private String oldPassword;
|
||||
|
||||
@NotBlank(message = "Mật khẩu mới không được để trống")
|
||||
@Size(min = 6, message = "Mật khẩu mới phải có ít nhất 6 ký tự")
|
||||
private String newPassword;
|
||||
}
|
||||
|
||||
|
|
@ -1,14 +1,30 @@
|
|||
package com.vega.hrm.request.user;
|
||||
|
||||
import java.util.UUID;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CreateUserRequest {
|
||||
@NotBlank(message = "Tên đăng nhập không được để trống")
|
||||
@Size(max = 200, message = "Tên đăng nhập không được vượt quá 200 ký tự")
|
||||
private String userName;
|
||||
|
||||
@NotBlank(message = "Mật khẩu không được để trống")
|
||||
@Size(min = 6, message = "Mật khẩu phải có ít nhất 6 ký tự")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "Họ tên không được để trống")
|
||||
@Size(max = 200, message = "Họ tên không được vượt quá 200 ký tự")
|
||||
private String fullName;
|
||||
private String roleId;
|
||||
|
||||
@Email(message = "Email không đúng định dạng")
|
||||
@Size(max = 255, message = "Email không được vượt quá 255 ký tự")
|
||||
private String email;
|
||||
|
||||
@NotBlank(message = "Role ID không được để trống")
|
||||
private String roleId; // UUID của role
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.vega.hrm.request.user;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ForgotPasswordRequest {
|
||||
@NotBlank(message = "Email không được để trống")
|
||||
@Email(message = "Email không đúng định dạng")
|
||||
private String email;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.vega.hrm.request.user;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RegisterRequest {
|
||||
@NotBlank(message = "Tên đăng nhập không được để trống")
|
||||
@Size(min = 3, max = 200, message = "Tên đăng nhập phải từ 3-200 ký tự")
|
||||
private String userName;
|
||||
|
||||
@NotBlank(message = "Mật khẩu không được để trống")
|
||||
@Size(min = 6, message = "Mật khẩu phải có ít nhất 6 ký tự")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "Họ tên không được để trống")
|
||||
@Size(max = 200, message = "Họ tên không được vượt quá 200 ký tự")
|
||||
private String fullName;
|
||||
|
||||
@Email(message = "Email không đúng định dạng")
|
||||
@Size(max = 255, message = "Email không được vượt quá 255 ký tự")
|
||||
private String email;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.vega.hrm.request.user;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class UpdateProfileRequest {
|
||||
@NotBlank(message = "User ID không được để trống")
|
||||
private String userId;
|
||||
|
||||
@NotBlank(message = "Họ tên không được để trống")
|
||||
@Size(max = 200, message = "Họ tên không được vượt quá 200 ký tự")
|
||||
private String fullName;
|
||||
|
||||
@Email(message = "Email không đúng định dạng")
|
||||
@Size(max = 255, message = "Email không được vượt quá 255 ký tự")
|
||||
private String email;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.vega.hrm.response;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class FunctionDto {
|
||||
private UUID functionId;
|
||||
private String functionName;
|
||||
private Long functionLevel;
|
||||
private String functionUrl;
|
||||
private Long functionOrder;
|
||||
private UUID parentId;
|
||||
private String functionDisplay;
|
||||
private String createdBy;
|
||||
private Instant createTime;
|
||||
private List<FunctionDto> children; // Menu con (nếu có)
|
||||
}
|
||||
|
||||
|
|
@ -1,17 +1,55 @@
|
|||
package com.vega.hrm.response;
|
||||
|
||||
import com.vega.hrm.core.entities.BoUser;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoginResponse {
|
||||
private BoUser user;
|
||||
private UserLoginInfo user;
|
||||
private String token;
|
||||
private RoleInfo role;
|
||||
private List<FunctionInfo> functions;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class UserLoginInfo {
|
||||
private UUID userId;
|
||||
private String userName;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private String avatarUrl;
|
||||
private String status;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class RoleInfo {
|
||||
private UUID roleId;
|
||||
private String roleName;
|
||||
private String description;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class FunctionInfo {
|
||||
private UUID functionId;
|
||||
private String functionName;
|
||||
private String functionUrl;
|
||||
private Long functionLevel;
|
||||
private Long functionOrder;
|
||||
private UUID parentId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.vega.hrm.response;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleDto {
|
||||
private UUID roleId;
|
||||
private String roleName;
|
||||
private String description;
|
||||
private String status;
|
||||
private String createdBy;
|
||||
private Instant createTime;
|
||||
private List<FunctionDto> functions; // Danh sách menu/quyền của role
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.vega.hrm.response;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserInfoResponse {
|
||||
private UUID userId;
|
||||
private String userName;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private String avatarUrl;
|
||||
private String status;
|
||||
private UUID roleId;
|
||||
private Instant lastLoginTime;
|
||||
private Instant createdDate;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
package com.vega.hrm.service;
|
||||
|
||||
import com.vega.hrm.core.entities.BoFunction;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||
import com.vega.hrm.core.repositories.CoreFunctionRepository;
|
||||
import com.vega.hrm.request.function.CreateFunctionRequest;
|
||||
import com.vega.hrm.request.function.UpdateFunctionRequest;
|
||||
import com.vega.hrm.response.FunctionDto;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FunctionService {
|
||||
private final CoreFunctionRepository functionRepository;
|
||||
|
||||
/**
|
||||
* Tạo menu/function mới
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<FunctionDto> createFunction(CreateFunctionRequest request, String currentUser) {
|
||||
var function = new BoFunction();
|
||||
function.setId(UUID.randomUUID());
|
||||
function.setFunctionName(request.getFunctionName());
|
||||
function.setFunctionLevel(request.getFunctionLevel());
|
||||
function.setFunctionUrl(request.getFunctionUrl());
|
||||
function.setFunctionOrder(request.getFunctionOrder());
|
||||
function.setFunctionDisplay(request.getFunctionDisplay());
|
||||
function.setCreatedBy(currentUser);
|
||||
function.setCreateTime(Instant.now());
|
||||
|
||||
// Xử lý parent
|
||||
if (request.getParentId() != null && !request.getParentId().isEmpty()) {
|
||||
try {
|
||||
function.setParentId(UUID.fromString(request.getParentId()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Parent ID không hợp lệ");
|
||||
}
|
||||
} else {
|
||||
// Menu top level - dùng UUID zero
|
||||
function.setParentId(UUID.fromString("00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
|
||||
functionRepository.save(function);
|
||||
LogHelper.info("Tạo menu thành công: " + function.getFunctionName());
|
||||
|
||||
return BaseResponse.success("Tạo menu thành công", mapToFunctionDto(function));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cập nhật menu/function
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<FunctionDto> updateFunction(UpdateFunctionRequest request) {
|
||||
try {
|
||||
UUID functionId = UUID.fromString(request.getFunctionId());
|
||||
var function = functionRepository.findById(functionId).orElse(null);
|
||||
|
||||
if (function == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy menu");
|
||||
}
|
||||
|
||||
function.setFunctionName(request.getFunctionName());
|
||||
function.setFunctionLevel(request.getFunctionLevel());
|
||||
function.setFunctionUrl(request.getFunctionUrl());
|
||||
function.setFunctionOrder(request.getFunctionOrder());
|
||||
function.setFunctionDisplay(request.getFunctionDisplay());
|
||||
|
||||
functionRepository.save(function);
|
||||
LogHelper.info("Cập nhật menu thành công: " + function.getFunctionName());
|
||||
|
||||
return BaseResponse.success("Cập nhật menu thành công", mapToFunctionDto(function));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Function ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Xóa menu/function
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<Boolean> deleteFunction(String functionId) {
|
||||
try {
|
||||
UUID id = UUID.fromString(functionId);
|
||||
var function = functionRepository.findById(id).orElse(null);
|
||||
|
||||
if (function == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy menu");
|
||||
}
|
||||
|
||||
// Kiểm tra có menu con không
|
||||
List<BoFunction> children = functionRepository.findByParentId(id);
|
||||
if (!children.isEmpty()) {
|
||||
return BaseResponse.invalid("Không thể xóa menu có menu con. Vui lòng xóa menu con trước.");
|
||||
}
|
||||
|
||||
functionRepository.delete(function);
|
||||
LogHelper.info("Xóa menu: " + function.getFunctionName());
|
||||
|
||||
return BaseResponse.success("Xóa menu thành công", true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Function ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy tất cả menu dạng flat list
|
||||
*/
|
||||
public BaseResponse<List<FunctionDto>> getAllFunctions() {
|
||||
List<BoFunction> functions = functionRepository.findAllOrderByLevelAndOrder();
|
||||
List<FunctionDto> functionDtos = functions.stream()
|
||||
.map(this::mapToFunctionDto)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return BaseResponse.success("Lấy danh sách menu thành công", functionDtos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy menu dạng tree (phân cấp)
|
||||
*/
|
||||
public BaseResponse<List<FunctionDto>> getMenuTree() {
|
||||
List<BoFunction> allFunctions = functionRepository.findAllOrderByLevelAndOrder();
|
||||
|
||||
// Group theo parent
|
||||
Map<UUID, List<BoFunction>> functionsByParent = new HashMap<>();
|
||||
for (BoFunction function : allFunctions) {
|
||||
functionsByParent
|
||||
.computeIfAbsent(function.getParentId(), k -> new ArrayList<>())
|
||||
.add(function);
|
||||
}
|
||||
|
||||
// Build tree từ root (parentId = 00000000-0000-0000-0000-000000000000)
|
||||
UUID rootId = UUID.fromString("00000000-0000-0000-0000-000000000000");
|
||||
List<FunctionDto> tree = buildTree(rootId, functionsByParent);
|
||||
|
||||
return BaseResponse.success("Lấy cây menu thành công", tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy chi tiết menu
|
||||
*/
|
||||
public BaseResponse<FunctionDto> getFunctionDetail(String functionId) {
|
||||
try {
|
||||
UUID id = UUID.fromString(functionId);
|
||||
var function = functionRepository.findById(id).orElse(null);
|
||||
|
||||
if (function == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy menu");
|
||||
}
|
||||
|
||||
return BaseResponse.success("Lấy thông tin menu thành công", mapToFunctionDto(function));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Function ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Build tree từ flat list
|
||||
*/
|
||||
private List<FunctionDto> buildTree(UUID parentId, Map<UUID, List<BoFunction>> functionsByParent) {
|
||||
List<BoFunction> children = functionsByParent.getOrDefault(parentId, new ArrayList<>());
|
||||
|
||||
return children.stream()
|
||||
.map(function -> {
|
||||
FunctionDto dto = mapToFunctionDto(function);
|
||||
// Đệ quy build children
|
||||
dto.setChildren(buildTree(function.getId(), functionsByParent));
|
||||
return dto;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Map entity sang DTO
|
||||
*/
|
||||
private FunctionDto mapToFunctionDto(BoFunction function) {
|
||||
return FunctionDto.builder()
|
||||
.functionId(function.getId())
|
||||
.functionName(function.getFunctionName())
|
||||
.functionLevel(function.getFunctionLevel())
|
||||
.functionUrl(function.getFunctionUrl())
|
||||
.functionOrder(function.getFunctionOrder())
|
||||
.parentId(function.getParentId())
|
||||
.functionDisplay(function.getFunctionDisplay())
|
||||
.createdBy(function.getCreatedBy())
|
||||
.createTime(function.getCreateTime())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
package com.vega.hrm.service;
|
||||
|
||||
import com.vega.hrm.core.entities.BoFunction;
|
||||
import com.vega.hrm.core.entities.BoRole;
|
||||
import com.vega.hrm.core.entities.BoRoleFunc;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||
import com.vega.hrm.core.repositories.BoRoleFuncRepository;
|
||||
import com.vega.hrm.core.repositories.CoreFunctionRepository;
|
||||
import com.vega.hrm.core.repositories.CoreRoleRepository;
|
||||
import com.vega.hrm.request.role.AssignPermissionsRequest;
|
||||
import com.vega.hrm.request.role.CreateRoleRequest;
|
||||
import com.vega.hrm.request.role.UpdateRoleRequest;
|
||||
import com.vega.hrm.response.FunctionDto;
|
||||
import com.vega.hrm.response.RoleDto;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RoleService {
|
||||
private final CoreRoleRepository roleRepository;
|
||||
private final CoreFunctionRepository functionRepository;
|
||||
private final BoRoleFuncRepository roleFuncRepository;
|
||||
|
||||
/**
|
||||
* Tạo role mới và gán quyền
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<RoleDto> createRole(CreateRoleRequest request, String currentUser) {
|
||||
// Kiểm tra tên role đã tồn tại
|
||||
var existingRole = roleRepository.findByRoleName(request.getRoleName());
|
||||
if (existingRole != null) {
|
||||
return BaseResponse.invalid("Tên role đã tồn tại");
|
||||
}
|
||||
|
||||
// Tạo role mới
|
||||
var role = new BoRole();
|
||||
role.setId(UUID.randomUUID());
|
||||
role.setRoleName(request.getRoleName());
|
||||
role.setDescription(request.getDescription());
|
||||
role.setStatus("1"); // Active
|
||||
role.setCreatedBy(currentUser);
|
||||
role.setCreateTime(Instant.now());
|
||||
|
||||
roleRepository.save(role);
|
||||
|
||||
// Gán quyền nếu có
|
||||
if (request.getFunctionIds() != null && !request.getFunctionIds().isEmpty()) {
|
||||
assignFunctionsToRole(role.getId(), request.getFunctionIds(), currentUser);
|
||||
}
|
||||
|
||||
LogHelper.info("Tạo role thành công: " + role.getRoleName());
|
||||
return BaseResponse.success("Tạo role thành công", mapToRoleDto(role));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cập nhật thông tin role
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<RoleDto> updateRole(UpdateRoleRequest request) {
|
||||
try {
|
||||
UUID roleId = UUID.fromString(request.getRoleId());
|
||||
var role = roleRepository.findById(roleId).orElse(null);
|
||||
|
||||
if (role == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy role");
|
||||
}
|
||||
|
||||
// Kiểm tra tên role trùng (nếu thay đổi)
|
||||
if (!role.getRoleName().equals(request.getRoleName())) {
|
||||
var existingRole = roleRepository.findByRoleName(request.getRoleName());
|
||||
if (existingRole != null) {
|
||||
return BaseResponse.invalid("Tên role đã tồn tại");
|
||||
}
|
||||
}
|
||||
|
||||
role.setRoleName(request.getRoleName());
|
||||
role.setDescription(request.getDescription());
|
||||
|
||||
roleRepository.save(role);
|
||||
LogHelper.info("Cập nhật role thành công: " + role.getRoleName());
|
||||
|
||||
return BaseResponse.success("Cập nhật role thành công", mapToRoleDto(role));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Role ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Xóa role (soft delete - chuyển status)
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<Boolean> deleteRole(String roleId) {
|
||||
try {
|
||||
UUID id = UUID.fromString(roleId);
|
||||
var role = roleRepository.findById(id).orElse(null);
|
||||
|
||||
if (role == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy role");
|
||||
}
|
||||
|
||||
role.setStatus("0"); // Inactive
|
||||
roleRepository.save(role);
|
||||
|
||||
LogHelper.info("Xóa role: " + role.getRoleName());
|
||||
return BaseResponse.success("Xóa role thành công", true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Role ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy danh sách tất cả role
|
||||
*/
|
||||
public BaseResponse<List<RoleDto>> getAllRoles() {
|
||||
List<BoRole> roles = roleRepository.findAll();
|
||||
List<RoleDto> roleDtos = roles.stream()
|
||||
.map(this::mapToRoleDto)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return BaseResponse.success("Lấy danh sách role thành công", roleDtos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy chi tiết role và quyền
|
||||
*/
|
||||
public BaseResponse<RoleDto> getRoleDetail(String roleId) {
|
||||
try {
|
||||
UUID id = UUID.fromString(roleId);
|
||||
var role = roleRepository.findById(id).orElse(null);
|
||||
|
||||
if (role == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy role");
|
||||
}
|
||||
|
||||
// Lấy danh sách function của role
|
||||
List<UUID> functionIds = roleFuncRepository.findFunctionIdsByRoleId(id);
|
||||
List<BoFunction> functions = functionRepository.findByIdIn(functionIds);
|
||||
|
||||
RoleDto roleDto = mapToRoleDto(role);
|
||||
roleDto.setFunctions(functions.stream()
|
||||
.map(this::mapToFunctionDto)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
return BaseResponse.success("Lấy thông tin role thành công", roleDto);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Role ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gán quyền cho role
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<Boolean> assignPermissions(AssignPermissionsRequest request, String currentUser) {
|
||||
try {
|
||||
UUID roleId = UUID.fromString(request.getRoleId());
|
||||
var role = roleRepository.findById(roleId).orElse(null);
|
||||
|
||||
if (role == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy role");
|
||||
}
|
||||
|
||||
// Xóa tất cả quyền cũ
|
||||
roleFuncRepository.deleteByRoleId(roleId);
|
||||
|
||||
// Gán quyền mới
|
||||
assignFunctionsToRole(roleId, request.getFunctionIds(), currentUser);
|
||||
|
||||
LogHelper.info("Gán quyền cho role: " + role.getRoleName()
|
||||
+ " (" + request.getFunctionIds().size() + " quyền)");
|
||||
|
||||
return BaseResponse.success("Gán quyền thành công", true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Role ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Gán functions cho role
|
||||
*/
|
||||
private void assignFunctionsToRole(UUID roleId, List<UUID> functionIds, String currentUser) {
|
||||
var role = roleRepository.findById(roleId).orElse(null);
|
||||
if (role == null) return;
|
||||
|
||||
for (UUID functionId : functionIds) {
|
||||
var function = functionRepository.findById(functionId).orElse(null);
|
||||
if (function == null) continue;
|
||||
|
||||
var roleFunc = new BoRoleFunc();
|
||||
roleFunc.setId(UUID.randomUUID());
|
||||
roleFunc.setRole(role);
|
||||
roleFunc.setFunction(function);
|
||||
roleFunc.setCreateTime(Instant.now());
|
||||
roleFunc.setCreateUser(currentUser);
|
||||
|
||||
roleFuncRepository.save(roleFunc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map entity sang DTO
|
||||
*/
|
||||
private RoleDto mapToRoleDto(BoRole role) {
|
||||
return RoleDto.builder()
|
||||
.roleId(role.getId())
|
||||
.roleName(role.getRoleName())
|
||||
.description(role.getDescription())
|
||||
.status(role.getStatus())
|
||||
.createdBy(role.getCreatedBy())
|
||||
.createTime(role.getCreateTime())
|
||||
.build();
|
||||
}
|
||||
|
||||
private FunctionDto mapToFunctionDto(BoFunction function) {
|
||||
return FunctionDto.builder()
|
||||
.functionId(function.getId())
|
||||
.functionName(function.getFunctionName())
|
||||
.functionLevel(function.getFunctionLevel())
|
||||
.functionUrl(function.getFunctionUrl())
|
||||
.functionOrder(function.getFunctionOrder())
|
||||
.parentId(function.getParentId())
|
||||
.functionDisplay(function.getFunctionDisplay())
|
||||
.createdBy(function.getCreatedBy())
|
||||
.createTime(function.getCreateTime())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,17 +2,32 @@ package com.vega.hrm.service;
|
|||
|
||||
import com.vega.hrm.AuthHelper;
|
||||
import com.vega.hrm.core.constants.CommonConst;
|
||||
import com.vega.hrm.core.entities.BoFunction;
|
||||
import com.vega.hrm.core.entities.BoRole;
|
||||
import com.vega.hrm.core.entities.BoUser;
|
||||
import com.vega.hrm.core.helpers.JwtHelper;
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
||||
import com.vega.hrm.core.repositories.BoRoleFuncRepository;
|
||||
import com.vega.hrm.core.repositories.CoreFunctionRepository;
|
||||
import com.vega.hrm.core.repositories.CoreRoleRepository;
|
||||
import com.vega.hrm.core.repositories.CoreUserRepository;
|
||||
import com.vega.hrm.core.service.FileStorageService;
|
||||
import com.vega.hrm.core.service.RedisService;
|
||||
import com.vega.hrm.request.user.ChangePasswordRequest;
|
||||
import com.vega.hrm.request.user.CreateUserRequest;
|
||||
import com.vega.hrm.request.user.ForgotPasswordRequest;
|
||||
import com.vega.hrm.request.user.LoginRequest;
|
||||
import com.vega.hrm.request.user.RegisterRequest;
|
||||
import com.vega.hrm.request.user.UpdateProfileRequest;
|
||||
import com.vega.hrm.response.LoginResponse;
|
||||
import com.vega.hrm.response.UserInfoResponse;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -22,7 +37,11 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final CoreUserRepository userRepository;
|
||||
private final CoreRoleRepository roleRepository;
|
||||
private final CoreFunctionRepository functionRepository;
|
||||
private final BoRoleFuncRepository roleFuncRepository;
|
||||
private final RedisService redisService;
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
public BaseResponse<Object> login(LoginRequest request) {
|
||||
var user = userRepository.findByUserName(request.getUserName());
|
||||
|
|
@ -35,22 +54,70 @@ public class UserService {
|
|||
user.setNumberOfFailedLogins(0L);
|
||||
user.setLastLoginTime(Instant.now());
|
||||
userRepository.save(user);
|
||||
var claims = new HashMap<String, Object>();
|
||||
claims.put("UserId", user.getId());
|
||||
claims.put("UserName", user.getUserName());
|
||||
|
||||
var token = JwtHelper.generateToken(user.getUserName(), user.getId(), UUID.randomUUID());
|
||||
if (Strings.isBlank(token)) {
|
||||
return BaseResponse.internalSystemError();
|
||||
}
|
||||
redisService.hSet(CommonConst.TOKEN,user.getId().toString(),token);
|
||||
redisService.hSet(CommonConst.TOKEN, user.getId().toString(), token);
|
||||
|
||||
var baseResponseLogin = BaseResponse.builder().code("00");
|
||||
baseResponseLogin.data(
|
||||
LoginResponse.builder()
|
||||
.user(user)
|
||||
.token(token)
|
||||
.build());
|
||||
return baseResponseLogin.build();
|
||||
// Lấy thông tin role
|
||||
LoginResponse.RoleInfo roleInfo = null;
|
||||
if (user.getRoleId() != null) {
|
||||
BoRole role = roleRepository.findById(user.getRoleId()).orElse(null);
|
||||
if (role != null) {
|
||||
roleInfo = LoginResponse.RoleInfo.builder()
|
||||
.roleId(role.getId())
|
||||
.roleName(role.getRoleName())
|
||||
.description(role.getDescription())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// Lấy danh sách function/menu được phép truy cập
|
||||
List<LoginResponse.FunctionInfo> functions = new ArrayList<>();
|
||||
if (user.getRoleId() != null) {
|
||||
List<UUID> functionIds = roleFuncRepository.findFunctionIdsByRoleId(user.getRoleId());
|
||||
if (!functionIds.isEmpty()) {
|
||||
List<BoFunction> functionList = functionRepository.findByIdIn(functionIds);
|
||||
functions = functionList.stream()
|
||||
.map(f -> LoginResponse.FunctionInfo.builder()
|
||||
.functionId(f.getId())
|
||||
.functionName(f.getFunctionName())
|
||||
.functionUrl(f.getFunctionUrl())
|
||||
.functionLevel(f.getFunctionLevel())
|
||||
.functionOrder(f.getFunctionOrder())
|
||||
.parentId(f.getParentId())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
// Build user info
|
||||
LoginResponse.UserLoginInfo userInfo = LoginResponse.UserLoginInfo.builder()
|
||||
.userId(user.getId())
|
||||
.userName(user.getUserName())
|
||||
.fullName(user.getFullName())
|
||||
.email(user.getEmail())
|
||||
.avatarUrl(user.getAvatarUrl())
|
||||
.status(user.getStatus())
|
||||
.build();
|
||||
|
||||
var loginResponse = LoginResponse.builder()
|
||||
.user(userInfo)
|
||||
.token(token)
|
||||
.role(roleInfo)
|
||||
.functions(functions)
|
||||
.build();
|
||||
|
||||
LogHelper.info("Đăng nhập thành công: " + user.getUserName()
|
||||
+ " với " + functions.size() + " quyền");
|
||||
|
||||
return BaseResponse.builder()
|
||||
.code("00")
|
||||
.message("Đăng nhập thành công")
|
||||
.data(loginResponse)
|
||||
.build();
|
||||
}
|
||||
if (user.getStatus().equals("1")) {
|
||||
user.setNumberOfFailedLogins(user.getNumberOfFailedLogins() + 1);
|
||||
|
|
@ -58,29 +125,270 @@ public class UserService {
|
|||
}
|
||||
}
|
||||
|
||||
return BaseResponse.invalid(
|
||||
"UserName or Password is valid. Please check.");
|
||||
return BaseResponse.invalid("Tên đăng nhập hoặc mật khẩu không đúng");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public BaseResponse<Boolean> insert(CreateUserRequest request) {
|
||||
var user = userRepository.findByUserName(request.getUserName());
|
||||
if (user != null) {
|
||||
return BaseResponse.success("User created unsuccessful");
|
||||
// Kiểm tra username đã tồn tại
|
||||
var existingUser = userRepository.findByUserName(request.getUserName());
|
||||
if (existingUser != null) {
|
||||
return BaseResponse.invalid("Tên đăng nhập đã tồn tại");
|
||||
}
|
||||
|
||||
// Kiểm tra email đã tồn tại
|
||||
if (request.getEmail() != null) {
|
||||
var existingEmail = userRepository.findByEmail(request.getEmail());
|
||||
if (existingEmail != null) {
|
||||
return BaseResponse.invalid("Email đã được sử dụng");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate roleId
|
||||
UUID roleId;
|
||||
try {
|
||||
roleId = UUID.fromString(request.getRoleId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("Role ID không hợp lệ");
|
||||
}
|
||||
|
||||
var newUser = new BoUser();
|
||||
newUser.setId(UUID.randomUUID());
|
||||
newUser.setUserName(request.getUserName());
|
||||
newUser.setFullName(request.getFullName());
|
||||
newUser.setRoleId(UUID.randomUUID());
|
||||
newUser.setEmail(request.getEmail());
|
||||
newUser.setRoleId(roleId);
|
||||
newUser.setPassword(AuthHelper.hashPassword(request.getUserName(), request.getPassword()));
|
||||
newUser.setStatus("1");
|
||||
newUser.setPwdExpireDate(Instant.now().plusSeconds(90L * 24 * 60 * 60));
|
||||
newUser.setCreatedDate(Instant.now());
|
||||
newUser.setCreatedUser("system");
|
||||
newUser.setCreatedUser("admin"); // TODO: Lấy từ token
|
||||
newUser.setNumberOfFailedLogins(0L);
|
||||
newUser.setIsPasswordChanged(0L);
|
||||
|
||||
userRepository.save(newUser);
|
||||
return BaseResponse.success("User created successfully");
|
||||
LogHelper.info("Tạo user thành công: " + request.getUserName() + " với role: " + roleId);
|
||||
|
||||
return BaseResponse.success("Tạo user thành công", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Đăng ký tài khoản mới (cho người dùng tự đăng ký)
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<Boolean> register(RegisterRequest request) {
|
||||
// Kiểm tra username đã tồn tại
|
||||
var existingUser = userRepository.findByUserName(request.getUserName());
|
||||
if (existingUser != null) {
|
||||
return BaseResponse.invalid("Tên đăng nhập đã tồn tại");
|
||||
}
|
||||
|
||||
// Kiểm tra email đã tồn tại
|
||||
if (request.getEmail() != null) {
|
||||
var existingEmail = userRepository.findByEmail(request.getEmail());
|
||||
if (existingEmail != null) {
|
||||
return BaseResponse.invalid("Email đã được sử dụng");
|
||||
}
|
||||
}
|
||||
|
||||
var newUser = new BoUser();
|
||||
newUser.setId(UUID.randomUUID());
|
||||
newUser.setUserName(request.getUserName());
|
||||
newUser.setFullName(request.getFullName());
|
||||
newUser.setEmail(request.getEmail());
|
||||
newUser.setPassword(AuthHelper.hashPassword(request.getUserName(), request.getPassword()));
|
||||
newUser.setStatus("1");
|
||||
newUser.setPwdExpireDate(Instant.now().plusSeconds(90L * 24 * 60 * 60));
|
||||
newUser.setCreatedDate(Instant.now());
|
||||
newUser.setCreatedUser("self-register");
|
||||
newUser.setNumberOfFailedLogins(0L);
|
||||
newUser.setIsPasswordChanged(0L);
|
||||
|
||||
userRepository.save(newUser);
|
||||
LogHelper.info("Đăng ký tài khoản thành công: " + request.getUserName());
|
||||
return BaseResponse.success("Đăng ký tài khoản thành công", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy thông tin tài khoản
|
||||
*/
|
||||
public BaseResponse<UserInfoResponse> getUserInfo(String userId) {
|
||||
try {
|
||||
UUID id = UUID.fromString(userId);
|
||||
var user = userRepository.findById(id).orElse(null);
|
||||
|
||||
if (user == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy người dùng");
|
||||
}
|
||||
|
||||
UserInfoResponse response = UserInfoResponse.builder()
|
||||
.userId(user.getId())
|
||||
.userName(user.getUserName())
|
||||
.fullName(user.getFullName())
|
||||
.email(user.getEmail())
|
||||
.avatarUrl(user.getAvatarUrl())
|
||||
.status(user.getStatus())
|
||||
.roleId(user.getRoleId())
|
||||
.lastLoginTime(user.getLastLoginTime())
|
||||
.createdDate(user.getCreatedDate())
|
||||
.build();
|
||||
|
||||
return BaseResponse.success("Lấy thông tin thành công", response);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("User ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cập nhật thông tin cá nhân
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<Boolean> updateProfile(UpdateProfileRequest request) {
|
||||
try {
|
||||
UUID id = UUID.fromString(request.getUserId());
|
||||
var user = userRepository.findById(id).orElse(null);
|
||||
|
||||
if (user == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy người dùng");
|
||||
}
|
||||
|
||||
// Kiểm tra email trùng (nếu thay đổi)
|
||||
if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
|
||||
var existingEmail = userRepository.findByEmail(request.getEmail());
|
||||
if (existingEmail != null && !existingEmail.getId().equals(id)) {
|
||||
return BaseResponse.invalid("Email đã được sử dụng bởi tài khoản khác");
|
||||
}
|
||||
}
|
||||
|
||||
user.setFullName(request.getFullName());
|
||||
user.setEmail(request.getEmail());
|
||||
user.setUpdatedDate(Instant.now());
|
||||
user.setUpdatedUser(user.getUserName());
|
||||
|
||||
userRepository.save(user);
|
||||
LogHelper.info("Cập nhật thông tin thành công cho user: " + user.getUserName());
|
||||
return BaseResponse.success("Cập nhật thông tin thành công", true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("User ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Đổi mật khẩu
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<Boolean> changePassword(ChangePasswordRequest request) {
|
||||
try {
|
||||
UUID id = UUID.fromString(request.getUserId());
|
||||
var user = userRepository.findById(id).orElse(null);
|
||||
|
||||
if (user == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy người dùng");
|
||||
}
|
||||
|
||||
// Kiểm tra mật khẩu cũ
|
||||
String hashedOldPassword = AuthHelper.hashPassword(user.getUserName(), request.getOldPassword());
|
||||
if (!user.getPassword().equals(hashedOldPassword)) {
|
||||
return BaseResponse.invalid("Mật khẩu cũ không đúng");
|
||||
}
|
||||
|
||||
// Cập nhật mật khẩu mới
|
||||
String hashedNewPassword = AuthHelper.hashPassword(user.getUserName(), request.getNewPassword());
|
||||
user.setPassword(hashedNewPassword);
|
||||
user.setIsPasswordChanged(1L);
|
||||
user.setPwdExpireDate(Instant.now().plusSeconds(90L * 24 * 60 * 60));
|
||||
user.setUpdatedDate(Instant.now());
|
||||
user.setUpdatedUser(user.getUserName());
|
||||
|
||||
userRepository.save(user);
|
||||
LogHelper.info("Đổi mật khẩu thành công cho user: " + user.getUserName());
|
||||
return BaseResponse.success("Đổi mật khẩu thành công", true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid("User ID không hợp lệ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quên mật khẩu - Tạo mật khẩu mới và gửi qua email (giả lập)
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<String> forgotPassword(ForgotPasswordRequest request) {
|
||||
var user = userRepository.findByEmail(request.getEmail());
|
||||
|
||||
if (user == null) {
|
||||
// Không tiết lộ email có tồn tại hay không (bảo mật)
|
||||
return BaseResponse.success("Nếu email tồn tại, mật khẩu mới đã được gửi", null);
|
||||
}
|
||||
|
||||
// Tạo mật khẩu ngẫu nhiên
|
||||
String newPassword = generateRandomPassword(8);
|
||||
|
||||
// Hash và lưu mật khẩu mới
|
||||
String hashedPassword = AuthHelper.hashPassword(user.getUserName(), newPassword);
|
||||
user.setPassword(hashedPassword);
|
||||
user.setIsPasswordChanged(0L); // Bắt buộc đổi mật khẩu lần đầu đăng nhập
|
||||
user.setPwdExpireDate(Instant.now().plusSeconds(7L * 24 * 60 * 60)); // 7 ngày
|
||||
user.setUpdatedDate(Instant.now());
|
||||
user.setUpdatedUser("system-forgot-password");
|
||||
|
||||
userRepository.save(user);
|
||||
|
||||
// TODO: Tích hợp email service để gửi mật khẩu mới
|
||||
LogHelper.info("Tạo mật khẩu mới cho user: " + user.getUserName());
|
||||
LogHelper.info("Mật khẩu tạm thời (chỉ hiển thị trong môi trường dev): " + newPassword);
|
||||
|
||||
// Trong production, không trả về mật khẩu qua API
|
||||
return BaseResponse.success("Mật khẩu mới đã được gửi qua email", newPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tạo mật khẩu ngẫu nhiên
|
||||
*/
|
||||
private String generateRandomPassword(int length) {
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%";
|
||||
SecureRandom random = new SecureRandom();
|
||||
StringBuilder password = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
password.append(chars.charAt(random.nextInt(chars.length())));
|
||||
}
|
||||
|
||||
return password.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload avatar cho người dùng
|
||||
*/
|
||||
@Transactional
|
||||
public BaseResponse<String> uploadAvatar(String userId, org.springframework.web.multipart.MultipartFile file) {
|
||||
try {
|
||||
UUID id = UUID.fromString(userId);
|
||||
var user = userRepository.findById(id).orElse(null);
|
||||
|
||||
if (user == null) {
|
||||
return BaseResponse.notFound("Không tìm thấy người dùng");
|
||||
}
|
||||
|
||||
// Xóa avatar cũ nếu có
|
||||
if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) {
|
||||
fileStorageService.deleteAvatarFile(user.getAvatarUrl());
|
||||
}
|
||||
|
||||
// Lưu avatar mới
|
||||
String avatarUrl = fileStorageService.storeAvatarFile(file, userId);
|
||||
user.setAvatarUrl(avatarUrl);
|
||||
user.setUpdatedDate(Instant.now());
|
||||
user.setUpdatedUser(user.getUserName());
|
||||
|
||||
userRepository.save(user);
|
||||
LogHelper.info("Upload avatar thành công cho user: " + user.getUserName());
|
||||
|
||||
return BaseResponse.success("Upload avatar thành công", avatarUrl);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return BaseResponse.invalid(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
LogHelper.error("Lỗi khi upload avatar: " + e.getMessage());
|
||||
return BaseResponse.internalSystemError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ vega.hrm.postgre.enabled=true
|
|||
vega.jpa.repository.basePackage=com.vega.hrm.core.repositories
|
||||
vega.jpa.entity.basePackage=com.vega.hrm.core.entities
|
||||
spring.config.import=file:config/shared.properties
|
||||
logging.config=file:config/log4j2.properties
|
||||
springdoc.api-docs.path=/api-docs/auth
|
||||
springdoc.swagger-ui.path=/swagger-ui/auth
|
||||
logging.config=file:./config/log4j2.properties
|
||||
|
||||
springdoc.swagger-ui.operations-sorter=method
|
||||
springdoc.swagger-ui.tags-sorter=alpha
|
||||
|
||||
springdoc.swagger-ui.path=/auth/swagger-ui/index.html
|
||||
springdoc.api-docs.path=/auth/api-docs
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.vega.hrm.core.configs;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class StaticResourceConfig implements WebMvcConfigurer {
|
||||
|
||||
@Value("${file.upload.avatar-dir:uploads/avatars}")
|
||||
private String avatarDir;
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// Map URL /uploads/avatars/** to file system directory
|
||||
registry.addResourceHandler("/uploads/avatars/**")
|
||||
.addResourceLocations("file:" + avatarDir + "/");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,4 +86,8 @@ public class BoUser {
|
|||
@Column(name = "email")
|
||||
private String email;
|
||||
|
||||
@Size(max = 500)
|
||||
@Column(name = "avatar_url", length = 500)
|
||||
private String avatarUrl;
|
||||
|
||||
}
|
||||
|
|
@ -41,7 +41,26 @@ public class AuthorizationFilter extends OncePerRequestFilter {
|
|||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) {
|
||||
try {
|
||||
var uri = request.getRequestURI();
|
||||
String uri = request.getRequestURI(); // /auth/api/v1/test
|
||||
String[] parts = uri.split("/");
|
||||
|
||||
if(!uri.contains("swagger-ui")) {
|
||||
if(!uri.contains("api-docs")) {
|
||||
if (parts.length > 1) {
|
||||
String first = parts[1];
|
||||
|
||||
if ("auth".equals(first) || "report".equals(first)) {
|
||||
|
||||
String newUri = uri.replaceFirst("/" + first, "");
|
||||
request.getRequestDispatcher(newUri).forward(request, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (EXCLUDE_URIS.contains(uri) || uri.contains("actuator") || uri.contains("swagger") || uri.contains("api-docs")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package com.vega.hrm.core.filters;
|
|||
|
||||
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -20,11 +22,27 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
|||
|
||||
@Component
|
||||
@Slf4j
|
||||
@Order(1)
|
||||
@Order(0)
|
||||
public class CorsFilter extends OncePerRequestFilter {
|
||||
|
||||
/**
|
||||
* Ví dụ cấu hình:
|
||||
* cors.allowed-origins=*
|
||||
* cors.allowed-origins=http://localhost:3000,https://vega-frontend.onestop.vn
|
||||
*/
|
||||
@Value("${cors.allowed-origins:*}")
|
||||
private String[] allowedOrigins;
|
||||
private String allowedOriginsConfig;
|
||||
|
||||
private List<String> allowedOrigins;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.allowedOrigins = Arrays.stream(allowedOriginsConfig.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
|
|
@ -33,20 +51,30 @@ public class CorsFilter extends OncePerRequestFilter {
|
|||
) {
|
||||
try {
|
||||
var startTime = Instant.now();
|
||||
|
||||
// Xử lý CORS
|
||||
var origin = request.getHeader("Origin");
|
||||
if (Strings.isNotBlank(origin) && Arrays.stream(allowedOrigins).toList()
|
||||
.contains(origin)) {
|
||||
response.setHeader("Access-Control-Allow-Origin", origin);
|
||||
if (Strings.isNotBlank(origin)) {
|
||||
// Nếu cấu hình chứa "*" thì cho phép mọi origin,
|
||||
// còn không thì chỉ cho phép những origin nằm trong danh sách.
|
||||
if (allowedOrigins.contains("*") || allowedOrigins.contains(origin)) {
|
||||
response.setHeader("Access-Control-Allow-Origin", origin);
|
||||
response.setHeader("Vary", "Origin");
|
||||
}
|
||||
}
|
||||
response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
||||
response.setHeader("Access-Control-Allow-Methods", "GET, PUT, DELETE, POST, OPTIONS");
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
|
||||
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, b");
|
||||
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
|
||||
// Preflight request
|
||||
if (request.getMethod().equalsIgnoreCase("OPTIONS")) {
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
// Logging + trace
|
||||
var clientIp = request.getHeader("X-Forwarded-For");
|
||||
if (Strings.isBlank(clientIp)) {
|
||||
clientIp = request.getRemoteAddr();
|
||||
|
|
@ -58,7 +86,9 @@ public class CorsFilter extends OncePerRequestFilter {
|
|||
var traceId = UUID.randomUUID().toString();
|
||||
ThreadContext.put("traceId", traceId);
|
||||
LogHelper.info("Request start");
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
|
||||
var duration = Instant.now().toEpochMilli() - startTime.toEpochMilli();
|
||||
LogHelper.info("Request end - Duration: " + duration + "ms");
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package com.vega.hrm.core.repositories;
|
||||
|
||||
import com.vega.hrm.core.entities.BoRoleFunc;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface BoRoleFuncRepository extends JpaRepository<BoRoleFunc, UUID> {
|
||||
|
||||
@Query("SELECT rf FROM BoRoleFunc rf WHERE rf.role.id = :roleId")
|
||||
List<BoRoleFunc> findByRoleId(@Param("roleId") UUID roleId);
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM BoRoleFunc rf WHERE rf.role.id = :roleId")
|
||||
void deleteByRoleId(@Param("roleId") UUID roleId);
|
||||
|
||||
@Query("SELECT rf.function.id FROM BoRoleFunc rf WHERE rf.role.id = :roleId")
|
||||
List<UUID> findFunctionIdsByRoleId(@Param("roleId") UUID roleId);
|
||||
}
|
||||
|
||||
|
|
@ -10,6 +10,14 @@ import org.springframework.stereotype.Repository;
|
|||
|
||||
@Repository
|
||||
public interface CoreFunctionRepository extends JpaRepository<BoFunction, UUID> {
|
||||
|
||||
|
||||
@Query("SELECT f FROM BoFunction f ORDER BY f.functionLevel, f.functionOrder")
|
||||
List<BoFunction> findAllOrderByLevelAndOrder();
|
||||
|
||||
@Query("SELECT f FROM BoFunction f WHERE f.parentId = :parentId ORDER BY f.functionOrder")
|
||||
List<BoFunction> findByParentId(@Param("parentId") UUID parentId);
|
||||
|
||||
@Query("SELECT f FROM BoFunction f WHERE f.id IN :ids ORDER BY f.functionLevel, f.functionOrder")
|
||||
List<BoFunction> findByIdIn(@Param("ids") List<UUID> ids);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package com.vega.hrm.core.repositories;
|
||||
|
||||
import com.vega.hrm.core.entities.BoRole;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CoreRoleRepository extends JpaRepository<BoRole, UUID> {
|
||||
BoRole findBoRoleById(UUID id);
|
||||
BoRole findBoRoleById(UUID id);
|
||||
BoRole findByRoleName(String roleName);
|
||||
List<BoRole> findByStatus(String status);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ import org.springframework.stereotype.Repository;
|
|||
@Repository
|
||||
public interface CoreUserRepository extends JpaRepository<BoUser, UUID> {
|
||||
BoUser findByUserName(String username);
|
||||
BoUser findByEmail(String email);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
package com.vega.hrm.core.service;
|
||||
|
||||
import com.vega.hrm.core.helpers.LogHelper;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Service
|
||||
public class FileStorageService {
|
||||
|
||||
private final Path avatarStorageLocation;
|
||||
|
||||
public FileStorageService(@Value("${file.upload.avatar-dir:uploads/avatars}") String avatarDir) {
|
||||
this.avatarStorageLocation = Paths.get(avatarDir).toAbsolutePath().normalize();
|
||||
|
||||
try {
|
||||
Files.createDirectories(this.avatarStorageLocation);
|
||||
LogHelper.info("Thư mục lưu avatar đã được tạo: " + this.avatarStorageLocation);
|
||||
} catch (IOException ex) {
|
||||
LogHelper.error("Không thể tạo thư mục lưu trữ avatar: " + ex.getMessage());
|
||||
throw new RuntimeException("Không thể tạo thư mục lưu trữ avatar", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lưu file avatar và trả về đường dẫn tương đối
|
||||
*/
|
||||
public String storeAvatarFile(MultipartFile file, String userId) {
|
||||
if (file.isEmpty()) {
|
||||
throw new IllegalArgumentException("File rỗng, không thể upload");
|
||||
}
|
||||
|
||||
// Lấy extension gốc
|
||||
String originalFilename = StringUtils.cleanPath(file.getOriginalFilename());
|
||||
String fileExtension = "";
|
||||
|
||||
if (originalFilename.contains(".")) {
|
||||
fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||
}
|
||||
|
||||
// Validate extension
|
||||
if (!isValidImageExtension(fileExtension)) {
|
||||
throw new IllegalArgumentException("Chỉ chấp nhận file ảnh: jpg, jpeg, png, gif");
|
||||
}
|
||||
|
||||
// Tạo tên file unique: userId_timestamp_uuid.ext
|
||||
String newFilename = userId + "_" + System.currentTimeMillis() + "_"
|
||||
+ UUID.randomUUID().toString().substring(0, 8) + fileExtension;
|
||||
|
||||
try {
|
||||
// Kiểm tra path traversal attack
|
||||
if (newFilename.contains("..")) {
|
||||
throw new IllegalArgumentException("Tên file không hợp lệ: " + newFilename);
|
||||
}
|
||||
|
||||
Path targetLocation = this.avatarStorageLocation.resolve(newFilename);
|
||||
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
LogHelper.info("Đã lưu avatar: " + newFilename + " (size: " + file.getSize() + " bytes)");
|
||||
|
||||
// Trả về đường dẫn tương đối để lưu vào DB
|
||||
return "/uploads/avatars/" + newFilename;
|
||||
|
||||
} catch (IOException ex) {
|
||||
LogHelper.error("Lỗi khi lưu file avatar: " + ex.getMessage());
|
||||
throw new RuntimeException("Lỗi khi lưu file avatar", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Xóa file avatar cũ
|
||||
*/
|
||||
public void deleteAvatarFile(String avatarUrl) {
|
||||
if (avatarUrl == null || avatarUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Lấy tên file từ URL
|
||||
String filename = avatarUrl.substring(avatarUrl.lastIndexOf("/") + 1);
|
||||
Path filePath = this.avatarStorageLocation.resolve(filename).normalize();
|
||||
|
||||
Files.deleteIfExists(filePath);
|
||||
LogHelper.info("Đã xóa avatar cũ: " + filename);
|
||||
} catch (IOException ex) {
|
||||
LogHelper.error("Lỗi khi xóa avatar cũ: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kiểm tra extension hợp lệ
|
||||
*/
|
||||
private boolean isValidImageExtension(String extension) {
|
||||
String lowerExt = extension.toLowerCase();
|
||||
return lowerExt.equals(".jpg") || lowerExt.equals(".jpeg")
|
||||
|| lowerExt.equals(".png") || lowerExt.equals(".gif");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy đường dẫn đầy đủ của file
|
||||
*/
|
||||
public Path getAvatarPath(String filename) {
|
||||
return this.avatarStorageLocation.resolve(filename).normalize();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
google.client.clientId = 719251949807-0jqbsmlh0a116cm8vm47oknmc5vpi19q.apps.googleusercontent.com
|
||||
google.client.clientSecret = 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
|
||||
google.client.redirect.uri=http://localhost:8089/api/google/user/callback
|
||||
|
||||
# File Upload Configuration
|
||||
file.upload.avatar-dir=uploads/avatars
|
||||
spring.servlet.multipart.max-file-size=5MB
|
||||
spring.servlet.multipart.max-request-size=5MB
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ vega.jpa.repository.basePackage=com.vega.hrm.core.repositories
|
|||
vega.jpa.entity.basePackage=com.vega.hrm.core.entities
|
||||
spring.config.import=file:config/shared.properties
|
||||
logging.config=file:config/log4j2.properties
|
||||
springdoc.api-docs.path=/api-docs/report
|
||||
springdoc.swagger-ui.path=/swagger-ui/report
|
||||
springdoc.swagger-ui.path=/report/swagger-ui/index.html
|
||||
springdoc.api-docs.path=/report/api-docs
|
||||
springdoc.swagger-ui.operations-sorter=method
|
||||
springdoc.swagger-ui.tags-sorter=alpha
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user