| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- package com.example.hrlab.security;
- import com.example.hrlab.audit.AuditService;
- import com.example.hrlab.common.ApiResponse;
- import com.example.hrlab.common.BusinessException;
- import com.example.hrlab.common.ErrorCodes;
- import com.example.hrlab.common.Rows;
- import jakarta.validation.Valid;
- import jakarta.validation.constraints.NotBlank;
- import java.util.List;
- import java.util.Map;
- import org.springframework.http.HttpStatus;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.security.access.prepost.PreAuthorize;
- import org.springframework.transaction.annotation.Transactional;
- import org.springframework.web.bind.annotation.DeleteMapping;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
- 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.RestController;
- @RestController
- @RequestMapping("/api/v1/security")
- public class SecurityAdminController {
- private final JdbcTemplate jdbcTemplate;
- private final AuditService auditService;
- public SecurityAdminController(JdbcTemplate jdbcTemplate, AuditService auditService) {
- this.jdbcTemplate = jdbcTemplate;
- this.auditService = auditService;
- }
- @GetMapping("/roles")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- public ApiResponse<Object> roles() {
- return ApiResponse.ok(jdbcTemplate.queryForList("""
- SELECT *, role_code IN ('SYSTEM_ADMIN','HR_ADMIN','RECRUITER','DEPT_MANAGER','PAYROLL_ADMIN',
- 'EMPLOYEE_SELF','AUDITOR','INTEGRATION_CLIENT') AS builtin
- FROM roles ORDER BY role_code
- """));
- }
- @GetMapping("/permissions")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- public ApiResponse<Object> permissions() {
- return ApiResponse.ok(jdbcTemplate.queryForList("""
- SELECT *, permission_code IN ('system:admin','employee:write','employee:read','recruitment:write',
- 'attendance:write','payroll:write','performance:write','report:export','audit:read','integration:access') AS builtin
- FROM permissions ORDER BY permission_code
- """));
- }
- @PostMapping("/roles")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- public ApiResponse<Map<String, Object>> createRole(@Valid @RequestBody RoleRequest request) {
- Long id = jdbcTemplate.queryForObject("""
- INSERT INTO roles(role_code, role_name, data_scope) VALUES (?, ?, ?) RETURNING id
- """, Long.class, request.roleCode(), request.roleName(), request.dataScope());
- auditService.record("ROLE_CREATE", "roles", id, null, request);
- return ApiResponse.ok(findRole(id));
- }
- @PutMapping("/roles/{id}")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- public ApiResponse<Map<String, Object>> updateRole(@PathVariable Long id, @Valid @RequestBody RoleRequest request) {
- Map<String, Object> before = findRole(id);
- SecurityCatalogPolicy.requireRoleCodeMutable(String.valueOf(before.get("role_code")), request.roleCode());
- jdbcTemplate.update("UPDATE roles SET role_code = ?, role_name = ?, data_scope = ?, updated_at = now() WHERE id = ?",
- request.roleCode(), request.roleName(), request.dataScope(), id);
- auditService.record("ROLE_UPDATE", "roles", id, before, request);
- return ApiResponse.ok(findRole(id));
- }
- @DeleteMapping("/roles/{id}")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- public ApiResponse<Map<String, Object>> deleteRole(@PathVariable Long id) {
- Map<String, Object> before = findRole(id);
- String roleCode = String.valueOf(before.get("role_code"));
- SecurityCatalogPolicy.requireRoleDeletable(roleCode);
- Long boundUsers = jdbcTemplate.queryForObject("SELECT count(*) FROM user_roles WHERE role_id = ?", Long.class, id);
- if (boundUsers != null && boundUsers > 0) {
- throw new BusinessException(ErrorCodes.BUSINESS_RULE_VIOLATION, "角色已分配给用户,不能删除");
- }
- jdbcTemplate.update("DELETE FROM roles WHERE id = ?", id);
- auditService.record("ROLE_DELETE", "roles", id, before, Map.of("deleted", true));
- return ApiResponse.ok(Map.of("deleted", true));
- }
- @GetMapping("/roles/{id}/permissions")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- public ApiResponse<Object> rolePermissions(@PathVariable Long id) {
- findRole(id);
- return ApiResponse.ok(permissionsForRole(id));
- }
- @PutMapping("/roles/{id}/permissions")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- @Transactional
- public ApiResponse<Map<String, Object>> replaceRolePermissions(@PathVariable Long id,
- @RequestBody PermissionCodesRequest request) {
- findRole(id);
- Map<String, Object> before = Map.of("permissions", permissionsForRole(id));
- jdbcTemplate.update("DELETE FROM role_permissions WHERE role_id = ?", id);
- for (String code : request.permissionCodes()) {
- jdbcTemplate.update("""
- INSERT INTO role_permissions(role_id, permission_id)
- SELECT ?, id FROM permissions WHERE permission_code = ?
- """, id, code);
- }
- Map<String, Object> after = Map.of("permissions", permissionsForRole(id));
- auditService.record("ROLE_PERMISSION_REPLACE", "roles", id, before, after);
- return ApiResponse.ok(after);
- }
- @PostMapping("/permissions")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- public ApiResponse<Map<String, Object>> createPermission(@Valid @RequestBody PermissionRequest request) {
- Long id = jdbcTemplate.queryForObject("""
- INSERT INTO permissions(permission_code, permission_name) VALUES (?, ?) RETURNING id
- """, Long.class, request.permissionCode(), request.permissionName());
- auditService.record("PERMISSION_CREATE", "permissions", id, null, request);
- return ApiResponse.ok(findPermission(id));
- }
- @PutMapping("/permissions/{id}")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- public ApiResponse<Map<String, Object>> updatePermission(@PathVariable Long id, @Valid @RequestBody PermissionRequest request) {
- Map<String, Object> before = findPermission(id);
- SecurityCatalogPolicy.requirePermissionCodeMutable(String.valueOf(before.get("permission_code")), request.permissionCode());
- jdbcTemplate.update("UPDATE permissions SET permission_code = ?, permission_name = ?, updated_at = now() WHERE id = ?",
- request.permissionCode(), request.permissionName(), id);
- auditService.record("PERMISSION_UPDATE", "permissions", id, before, request);
- return ApiResponse.ok(findPermission(id));
- }
- @DeleteMapping("/permissions/{id}")
- @PreAuthorize("hasRole('SYSTEM_ADMIN')")
- @Transactional
- public ApiResponse<Map<String, Object>> deletePermission(@PathVariable Long id) {
- Map<String, Object> before = findPermission(id);
- SecurityCatalogPolicy.requirePermissionDeletable(String.valueOf(before.get("permission_code")));
- jdbcTemplate.update("DELETE FROM role_permissions WHERE permission_id = ?", id);
- jdbcTemplate.update("DELETE FROM permissions WHERE id = ?", id);
- auditService.record("PERMISSION_DELETE", "permissions", id, before, Map.of("deleted", true));
- return ApiResponse.ok(Map.of("deleted", true));
- }
- private List<String> permissionsForRole(Long roleId) {
- return jdbcTemplate.queryForList("""
- SELECT p.permission_code FROM permissions p
- JOIN role_permissions rp ON rp.permission_id = p.id
- WHERE rp.role_id = ?
- ORDER BY p.permission_code
- """, String.class, roleId);
- }
- private Map<String, Object> findRole(Long id) {
- return jdbcTemplate.query("SELECT * FROM roles WHERE id = ?", rs -> {
- if (!rs.next()) {
- throw new BusinessException(ErrorCodes.NOT_FOUND, "role not found", HttpStatus.NOT_FOUND);
- }
- return Rows.map(rs);
- }, id);
- }
- private Map<String, Object> findPermission(Long id) {
- return jdbcTemplate.query("SELECT * FROM permissions WHERE id = ?", rs -> {
- if (!rs.next()) {
- throw new BusinessException(ErrorCodes.NOT_FOUND, "权限不存在", HttpStatus.NOT_FOUND);
- }
- return Rows.map(rs);
- }, id);
- }
- public record RoleRequest(@NotBlank String roleCode, @NotBlank String roleName, @NotBlank String dataScope) {
- }
- public record PermissionRequest(@NotBlank String permissionCode, @NotBlank String permissionName) {
- }
- public record PermissionCodesRequest(List<String> permissionCodes) {
- }
- }
|