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;
|
package com.vega.hrm.controller;
|
||||||
|
|
||||||
import com.vega.hrm.core.models.responses.BaseResponse;
|
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.CreateUserRequest;
|
||||||
|
import com.vega.hrm.request.user.ForgotPasswordRequest;
|
||||||
import com.vega.hrm.request.user.LoginRequest;
|
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 com.vega.hrm.service.UserService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
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.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/auth/user")
|
@RequestMapping("api/auth/user")
|
||||||
@RequiredArgsConstructor
|
@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 {
|
public class UserController {
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
|
|
@ -27,8 +38,52 @@ public class UserController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/insert")
|
@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.")
|
@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(@RequestBody CreateUserRequest request) {
|
public ResponseEntity<BaseResponse<Boolean>> insert(@Valid @RequestBody CreateUserRequest request) {
|
||||||
return ResponseEntity.ok(userService.insert(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;
|
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.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class CreateUserRequest {
|
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;
|
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;
|
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 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;
|
package com.vega.hrm.response;
|
||||||
|
|
||||||
import com.vega.hrm.core.entities.BoUser;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class LoginResponse {
|
public class LoginResponse {
|
||||||
private BoUser user;
|
private UserLoginInfo user;
|
||||||
private String token;
|
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.AuthHelper;
|
||||||
import com.vega.hrm.core.constants.CommonConst;
|
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.entities.BoUser;
|
||||||
import com.vega.hrm.core.helpers.JwtHelper;
|
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.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.repositories.CoreUserRepository;
|
||||||
|
import com.vega.hrm.core.service.FileStorageService;
|
||||||
import com.vega.hrm.core.service.RedisService;
|
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.CreateUserRequest;
|
||||||
|
import com.vega.hrm.request.user.ForgotPasswordRequest;
|
||||||
import com.vega.hrm.request.user.LoginRequest;
|
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.LoginResponse;
|
||||||
|
import com.vega.hrm.response.UserInfoResponse;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.logging.log4j.util.Strings;
|
import org.apache.logging.log4j.util.Strings;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
@ -22,7 +37,11 @@ import org.springframework.transaction.annotation.Transactional;
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class UserService {
|
public class UserService {
|
||||||
private final CoreUserRepository userRepository;
|
private final CoreUserRepository userRepository;
|
||||||
|
private final CoreRoleRepository roleRepository;
|
||||||
|
private final CoreFunctionRepository functionRepository;
|
||||||
|
private final BoRoleFuncRepository roleFuncRepository;
|
||||||
private final RedisService redisService;
|
private final RedisService redisService;
|
||||||
|
private final FileStorageService fileStorageService;
|
||||||
|
|
||||||
public BaseResponse<Object> login(LoginRequest request) {
|
public BaseResponse<Object> login(LoginRequest request) {
|
||||||
var user = userRepository.findByUserName(request.getUserName());
|
var user = userRepository.findByUserName(request.getUserName());
|
||||||
|
|
@ -35,22 +54,70 @@ public class UserService {
|
||||||
user.setNumberOfFailedLogins(0L);
|
user.setNumberOfFailedLogins(0L);
|
||||||
user.setLastLoginTime(Instant.now());
|
user.setLastLoginTime(Instant.now());
|
||||||
userRepository.save(user);
|
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());
|
var token = JwtHelper.generateToken(user.getUserName(), user.getId(), UUID.randomUUID());
|
||||||
if (Strings.isBlank(token)) {
|
if (Strings.isBlank(token)) {
|
||||||
return BaseResponse.internalSystemError();
|
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");
|
// Lấy thông tin role
|
||||||
baseResponseLogin.data(
|
LoginResponse.RoleInfo roleInfo = null;
|
||||||
LoginResponse.builder()
|
if (user.getRoleId() != null) {
|
||||||
.user(user)
|
BoRole role = roleRepository.findById(user.getRoleId()).orElse(null);
|
||||||
.token(token)
|
if (role != null) {
|
||||||
.build());
|
roleInfo = LoginResponse.RoleInfo.builder()
|
||||||
return baseResponseLogin.build();
|
.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")) {
|
if (user.getStatus().equals("1")) {
|
||||||
user.setNumberOfFailedLogins(user.getNumberOfFailedLogins() + 1);
|
user.setNumberOfFailedLogins(user.getNumberOfFailedLogins() + 1);
|
||||||
|
|
@ -58,29 +125,270 @@ public class UserService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return BaseResponse.invalid(
|
return BaseResponse.invalid("Tên đăng nhập hoặc mật khẩu không đúng");
|
||||||
"UserName or Password is valid. Please check.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public BaseResponse<Boolean> insert(CreateUserRequest request) {
|
public BaseResponse<Boolean> insert(CreateUserRequest request) {
|
||||||
var user = userRepository.findByUserName(request.getUserName());
|
// Kiểm tra username đã tồn tại
|
||||||
if (user != null) {
|
var existingUser = userRepository.findByUserName(request.getUserName());
|
||||||
return BaseResponse.success("User created unsuccessful");
|
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();
|
var newUser = new BoUser();
|
||||||
newUser.setId(UUID.randomUUID());
|
newUser.setId(UUID.randomUUID());
|
||||||
newUser.setUserName(request.getUserName());
|
newUser.setUserName(request.getUserName());
|
||||||
newUser.setFullName(request.getFullName());
|
newUser.setFullName(request.getFullName());
|
||||||
newUser.setRoleId(UUID.randomUUID());
|
newUser.setEmail(request.getEmail());
|
||||||
|
newUser.setRoleId(roleId);
|
||||||
newUser.setPassword(AuthHelper.hashPassword(request.getUserName(), request.getPassword()));
|
newUser.setPassword(AuthHelper.hashPassword(request.getUserName(), request.getPassword()));
|
||||||
newUser.setStatus("1");
|
newUser.setStatus("1");
|
||||||
newUser.setPwdExpireDate(Instant.now().plusSeconds(90L * 24 * 60 * 60));
|
newUser.setPwdExpireDate(Instant.now().plusSeconds(90L * 24 * 60 * 60));
|
||||||
newUser.setCreatedDate(Instant.now());
|
newUser.setCreatedDate(Instant.now());
|
||||||
newUser.setCreatedUser("system");
|
newUser.setCreatedUser("admin"); // TODO: Lấy từ token
|
||||||
newUser.setNumberOfFailedLogins(0L);
|
newUser.setNumberOfFailedLogins(0L);
|
||||||
newUser.setIsPasswordChanged(0L);
|
newUser.setIsPasswordChanged(0L);
|
||||||
|
|
||||||
userRepository.save(newUser);
|
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.repository.basePackage=com.vega.hrm.core.repositories
|
||||||
vega.jpa.entity.basePackage=com.vega.hrm.core.entities
|
vega.jpa.entity.basePackage=com.vega.hrm.core.entities
|
||||||
spring.config.import=file:config/shared.properties
|
spring.config.import=file:config/shared.properties
|
||||||
logging.config=file:config/log4j2.properties
|
logging.config=file:./config/log4j2.properties
|
||||||
springdoc.api-docs.path=/api-docs/auth
|
|
||||||
springdoc.swagger-ui.path=/swagger-ui/auth
|
|
||||||
springdoc.swagger-ui.operations-sorter=method
|
springdoc.swagger-ui.operations-sorter=method
|
||||||
springdoc.swagger-ui.tags-sorter=alpha
|
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")
|
@Column(name = "email")
|
||||||
private String 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
|
@Override
|
||||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) {
|
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) {
|
||||||
try {
|
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")) {
|
if (EXCLUDE_URIS.contains(uri) || uri.contains("actuator") || uri.contains("swagger") || uri.contains("api-docs")) {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ package com.vega.hrm.core.filters;
|
||||||
|
|
||||||
|
|
||||||
import com.vega.hrm.core.helpers.LogHelper;
|
import com.vega.hrm.core.helpers.LogHelper;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
@ -20,11 +22,27 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Order(1)
|
@Order(0)
|
||||||
public class CorsFilter extends OncePerRequestFilter {
|
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:*}")
|
@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
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(
|
||||||
@NonNull HttpServletRequest request,
|
@NonNull HttpServletRequest request,
|
||||||
|
|
@ -33,20 +51,30 @@ public class CorsFilter extends OncePerRequestFilter {
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
var startTime = Instant.now();
|
var startTime = Instant.now();
|
||||||
|
|
||||||
|
// Xử lý CORS
|
||||||
var origin = request.getHeader("Origin");
|
var origin = request.getHeader("Origin");
|
||||||
if (Strings.isNotBlank(origin) && Arrays.stream(allowedOrigins).toList()
|
if (Strings.isNotBlank(origin)) {
|
||||||
.contains(origin)) {
|
// Nếu cấu hình chứa "*" thì cho phép mọi origin,
|
||||||
response.setHeader("Access-Control-Allow-Origin", 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-Expose-Headers", "Content-Disposition");
|
||||||
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, b");
|
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, b");
|
||||||
response.setHeader("Access-Control-Allow-Credentials", "true");
|
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||||
response.setCharacterEncoding("UTF-8");
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
|
// Preflight request
|
||||||
if (request.getMethod().equalsIgnoreCase("OPTIONS")) {
|
if (request.getMethod().equalsIgnoreCase("OPTIONS")) {
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logging + trace
|
||||||
var clientIp = request.getHeader("X-Forwarded-For");
|
var clientIp = request.getHeader("X-Forwarded-For");
|
||||||
if (Strings.isBlank(clientIp)) {
|
if (Strings.isBlank(clientIp)) {
|
||||||
clientIp = request.getRemoteAddr();
|
clientIp = request.getRemoteAddr();
|
||||||
|
|
@ -58,7 +86,9 @@ public class CorsFilter extends OncePerRequestFilter {
|
||||||
var traceId = UUID.randomUUID().toString();
|
var traceId = UUID.randomUUID().toString();
|
||||||
ThreadContext.put("traceId", traceId);
|
ThreadContext.put("traceId", traceId);
|
||||||
LogHelper.info("Request start");
|
LogHelper.info("Request start");
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
|
|
||||||
var duration = Instant.now().toEpochMilli() - startTime.toEpochMilli();
|
var duration = Instant.now().toEpochMilli() - startTime.toEpochMilli();
|
||||||
LogHelper.info("Request end - Duration: " + duration + "ms");
|
LogHelper.info("Request end - Duration: " + duration + "ms");
|
||||||
} catch (Exception e) {
|
} 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -11,5 +11,13 @@ import org.springframework.stereotype.Repository;
|
||||||
@Repository
|
@Repository
|
||||||
public interface CoreFunctionRepository extends JpaRepository<BoFunction, UUID> {
|
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;
|
package com.vega.hrm.core.repositories;
|
||||||
|
|
||||||
import com.vega.hrm.core.entities.BoRole;
|
import com.vega.hrm.core.entities.BoRole;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface CoreRoleRepository extends JpaRepository<BoRole, UUID> {
|
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
|
@Repository
|
||||||
public interface CoreUserRepository extends JpaRepository<BoUser, UUID> {
|
public interface CoreUserRepository extends JpaRepository<BoUser, UUID> {
|
||||||
BoUser findByUserName(String username);
|
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
|
# File Upload Configuration
|
||||||
google.client.clientSecret = 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
|
file.upload.avatar-dir=uploads/avatars
|
||||||
google.client.redirect.uri=http://localhost:8089/api/google/user/callback
|
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
|
vega.jpa.entity.basePackage=com.vega.hrm.core.entities
|
||||||
spring.config.import=file:config/shared.properties
|
spring.config.import=file:config/shared.properties
|
||||||
logging.config=file:config/log4j2.properties
|
logging.config=file:config/log4j2.properties
|
||||||
springdoc.api-docs.path=/api-docs/report
|
springdoc.swagger-ui.path=/report/swagger-ui/index.html
|
||||||
springdoc.swagger-ui.path=/swagger-ui/report
|
springdoc.api-docs.path=/report/api-docs
|
||||||
springdoc.swagger-ui.operations-sorter=method
|
springdoc.swagger-ui.operations-sorter=method
|
||||||
springdoc.swagger-ui.tags-sorter=alpha
|
springdoc.swagger-ui.tags-sorter=alpha
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user