You've already forked DataMate
feat(auth): 角色管理CRUD与角色权限绑定功能
Some checks failed
Some checks failed
新增角色创建/编辑/删除接口和角色-权限绑定接口,支持管理员自定义角色并灵活配置权限。 前端新增角色CRUD弹窗、按模块分组的权限配置面板,内置角色禁止删除但允许编辑和配置权限。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse;
|
|||||||
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
|
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
|
||||||
import com.datamate.common.auth.interfaces.rest.dto.AuthUserView;
|
import com.datamate.common.auth.interfaces.rest.dto.AuthUserView;
|
||||||
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
|
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
|
||||||
|
import com.datamate.common.auth.interfaces.rest.dto.RoleWithPermissionsResponse;
|
||||||
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
||||||
import com.datamate.common.security.JwtUtils;
|
import com.datamate.common.security.JwtUtils;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
@@ -17,6 +18,7 @@ import io.jsonwebtoken.JwtException;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -26,6 +28,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,6 +132,56 @@ public class AuthApplicationService {
|
|||||||
authMapper.insertUserRoles(userId, new ArrayList<>(distinctRoleIds));
|
authMapper.insertUserRoles(userId, new ArrayList<>(distinctRoleIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthRoleInfo createRole(String roleCode, String roleName, String description) {
|
||||||
|
AuthRoleInfo existing = authMapper.findRoleByCode(roleCode);
|
||||||
|
BusinessAssert.isTrue(existing == null, AuthErrorCode.ROLE_CODE_DUPLICATE);
|
||||||
|
|
||||||
|
String id = UUID.randomUUID().toString();
|
||||||
|
authMapper.insertRole(id, roleCode, roleName, description);
|
||||||
|
return authMapper.findRoleById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthRoleInfo updateRole(String roleId, String roleName, String description, Boolean enabled) {
|
||||||
|
AuthRoleInfo role = authMapper.findRoleById(roleId);
|
||||||
|
BusinessAssert.notNull(role, AuthErrorCode.ROLE_NOT_FOUND);
|
||||||
|
|
||||||
|
authMapper.updateRole(roleId, roleName, description, enabled);
|
||||||
|
return authMapper.findRoleById(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteRole(String roleId) {
|
||||||
|
AuthRoleInfo role = authMapper.findRoleById(roleId);
|
||||||
|
BusinessAssert.notNull(role, AuthErrorCode.ROLE_NOT_FOUND);
|
||||||
|
BusinessAssert.isTrue(!Boolean.TRUE.equals(role.getIsBuiltIn()), AuthErrorCode.ROLE_DELETE_BUILT_IN);
|
||||||
|
|
||||||
|
authMapper.deleteRolePermissions(roleId);
|
||||||
|
authMapper.deleteUserRolesByRoleId(roleId);
|
||||||
|
authMapper.deleteRoleById(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoleWithPermissionsResponse getRoleWithPermissions(String roleId) {
|
||||||
|
AuthRoleInfo role = authMapper.findRoleById(roleId);
|
||||||
|
BusinessAssert.notNull(role, AuthErrorCode.ROLE_NOT_FOUND);
|
||||||
|
|
||||||
|
List<String> permissionIds = authMapper.findPermissionIdsByRoleId(roleId);
|
||||||
|
return new RoleWithPermissionsResponse(role, permissionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void bindRolePermissions(String roleId, List<String> permissionIds) {
|
||||||
|
AuthRoleInfo role = authMapper.findRoleById(roleId);
|
||||||
|
BusinessAssert.notNull(role, AuthErrorCode.ROLE_NOT_FOUND);
|
||||||
|
|
||||||
|
authMapper.deleteRolePermissions(roleId);
|
||||||
|
if (permissionIds != null && !permissionIds.isEmpty()) {
|
||||||
|
Set<String> distinctIds = new LinkedHashSet<>(permissionIds);
|
||||||
|
int existingCount = authMapper.countPermissionsByIds(new ArrayList<>(distinctIds));
|
||||||
|
BusinessAssert.isTrue(existingCount == distinctIds.size(), AuthErrorCode.PERMISSION_NOT_FOUND);
|
||||||
|
authMapper.insertRolePermissions(roleId, new ArrayList<>(distinctIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String buildToken(AuthBundle authBundle) {
|
private String buildToken(AuthBundle authBundle) {
|
||||||
Map<String, Object> claims = Map.of(
|
Map<String, Object> claims = Map.of(
|
||||||
"userId", authBundle.user().getId(),
|
"userId", authBundle.user().getId(),
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ public class AuthRoleInfo {
|
|||||||
private String roleName;
|
private String roleName;
|
||||||
private String description;
|
private String description;
|
||||||
private Boolean enabled;
|
private Boolean enabled;
|
||||||
|
private Boolean isBuiltIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ public enum AuthErrorCode implements ErrorCode {
|
|||||||
TOKEN_INVALID("auth.0003", "登录状态已失效"),
|
TOKEN_INVALID("auth.0003", "登录状态已失效"),
|
||||||
USER_NOT_FOUND("auth.0004", "用户不存在"),
|
USER_NOT_FOUND("auth.0004", "用户不存在"),
|
||||||
ROLE_NOT_FOUND("auth.0005", "角色不存在"),
|
ROLE_NOT_FOUND("auth.0005", "角色不存在"),
|
||||||
AUTHORIZATION_DENIED("auth.0006", "无权限执行该操作");
|
AUTHORIZATION_DENIED("auth.0006", "无权限执行该操作"),
|
||||||
|
ROLE_CODE_DUPLICATE("auth.0007", "角色编码已存在"),
|
||||||
|
ROLE_DELETE_BUILT_IN("auth.0008", "内置角色不允许删除"),
|
||||||
|
PERMISSION_NOT_FOUND("auth.0009", "权限不存在");
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|||||||
@@ -35,5 +35,27 @@ public interface AuthMapper {
|
|||||||
int deleteUserRoles(@Param("userId") Long userId);
|
int deleteUserRoles(@Param("userId") Long userId);
|
||||||
|
|
||||||
int insertUserRoles(@Param("userId") Long userId, @Param("roleIds") List<String> roleIds);
|
int insertUserRoles(@Param("userId") Long userId, @Param("roleIds") List<String> roleIds);
|
||||||
|
|
||||||
|
AuthRoleInfo findRoleById(@Param("roleId") String roleId);
|
||||||
|
|
||||||
|
AuthRoleInfo findRoleByCode(@Param("roleCode") String roleCode);
|
||||||
|
|
||||||
|
int insertRole(@Param("id") String id, @Param("roleCode") String roleCode,
|
||||||
|
@Param("roleName") String roleName, @Param("description") String description);
|
||||||
|
|
||||||
|
int updateRole(@Param("roleId") String roleId, @Param("roleName") String roleName,
|
||||||
|
@Param("description") String description, @Param("enabled") Boolean enabled);
|
||||||
|
|
||||||
|
int deleteRoleById(@Param("roleId") String roleId);
|
||||||
|
|
||||||
|
List<String> findPermissionIdsByRoleId(@Param("roleId") String roleId);
|
||||||
|
|
||||||
|
int deleteRolePermissions(@Param("roleId") String roleId);
|
||||||
|
|
||||||
|
int insertRolePermissions(@Param("roleId") String roleId, @Param("permissionIds") List<String> permissionIds);
|
||||||
|
|
||||||
|
int countPermissionsByIds(@Param("permissionIds") List<String> permissionIds);
|
||||||
|
|
||||||
|
int deleteUserRolesByRoleId(@Param("roleId") String roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,17 @@ import com.datamate.common.auth.interfaces.rest.dto.AssignUserRolesRequest;
|
|||||||
import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse;
|
import com.datamate.common.auth.interfaces.rest.dto.AuthCurrentUserResponse;
|
||||||
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
|
import com.datamate.common.auth.interfaces.rest.dto.AuthLoginResponse;
|
||||||
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
|
import com.datamate.common.auth.interfaces.rest.dto.AuthUserWithRolesResponse;
|
||||||
|
import com.datamate.common.auth.interfaces.rest.dto.BindRolePermissionsRequest;
|
||||||
|
import com.datamate.common.auth.interfaces.rest.dto.CreateRoleRequest;
|
||||||
import com.datamate.common.auth.interfaces.rest.dto.LoginRequest;
|
import com.datamate.common.auth.interfaces.rest.dto.LoginRequest;
|
||||||
|
import com.datamate.common.auth.interfaces.rest.dto.RoleWithPermissionsResponse;
|
||||||
|
import com.datamate.common.auth.interfaces.rest.dto.UpdateRoleRequest;
|
||||||
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
||||||
import com.datamate.common.auth.infrastructure.exception.AuthErrorCode;
|
import com.datamate.common.auth.infrastructure.exception.AuthErrorCode;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -69,6 +74,33 @@ public class AuthController {
|
|||||||
return authApplicationService.listPermissions();
|
return authApplicationService.listPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/roles")
|
||||||
|
public AuthRoleInfo createRole(@RequestBody @Valid CreateRoleRequest request) {
|
||||||
|
return authApplicationService.createRole(request.roleCode(), request.roleName(), request.description());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/roles/{roleId}")
|
||||||
|
public AuthRoleInfo updateRole(@PathVariable("roleId") String roleId,
|
||||||
|
@RequestBody @Valid UpdateRoleRequest request) {
|
||||||
|
return authApplicationService.updateRole(roleId, request.roleName(), request.description(), request.enabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/roles/{roleId}")
|
||||||
|
public void deleteRole(@PathVariable("roleId") String roleId) {
|
||||||
|
authApplicationService.deleteRole(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/roles/{roleId}/permissions")
|
||||||
|
public RoleWithPermissionsResponse getRolePermissions(@PathVariable("roleId") String roleId) {
|
||||||
|
return authApplicationService.getRoleWithPermissions(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/roles/{roleId}/permissions")
|
||||||
|
public void bindRolePermissions(@PathVariable("roleId") String roleId,
|
||||||
|
@RequestBody @Valid BindRolePermissionsRequest request) {
|
||||||
|
authApplicationService.bindRolePermissions(roleId, request.permissionIds());
|
||||||
|
}
|
||||||
|
|
||||||
private String extractBearerToken(String authorizationHeader) {
|
private String extractBearerToken(String authorizationHeader) {
|
||||||
BusinessAssert.isTrue(
|
BusinessAssert.isTrue(
|
||||||
authorizationHeader != null && authorizationHeader.startsWith("Bearer "),
|
authorizationHeader != null && authorizationHeader.startsWith("Bearer "),
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.datamate.common.auth.interfaces.rest.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色权限绑定请求
|
||||||
|
*/
|
||||||
|
public record BindRolePermissionsRequest(
|
||||||
|
@NotNull(message = "权限列表不能为null") List<String> permissionIds
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.datamate.common.auth.interfaces.rest.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建角色请求
|
||||||
|
*/
|
||||||
|
public record CreateRoleRequest(
|
||||||
|
@NotBlank(message = "角色编码不能为空") String roleCode,
|
||||||
|
@NotBlank(message = "角色名称不能为空") String roleName,
|
||||||
|
String description
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.datamate.common.auth.interfaces.rest.dto;
|
||||||
|
|
||||||
|
import com.datamate.common.auth.domain.model.AuthRoleInfo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色及其权限响应
|
||||||
|
*/
|
||||||
|
public record RoleWithPermissionsResponse(
|
||||||
|
AuthRoleInfo role,
|
||||||
|
List<String> permissionIds
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.datamate.common.auth.interfaces.rest.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新角色请求
|
||||||
|
*/
|
||||||
|
public record UpdateRoleRequest(
|
||||||
|
@NotBlank(message = "角色名称不能为空") String roleName,
|
||||||
|
String description,
|
||||||
|
Boolean enabled
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -73,10 +73,11 @@
|
|||||||
|
|
||||||
<select id="listRoles" resultType="com.datamate.common.auth.domain.model.AuthRoleInfo">
|
<select id="listRoles" resultType="com.datamate.common.auth.domain.model.AuthRoleInfo">
|
||||||
SELECT id,
|
SELECT id,
|
||||||
role_code AS roleCode,
|
role_code AS roleCode,
|
||||||
role_name AS roleName,
|
role_name AS roleName,
|
||||||
description,
|
description,
|
||||||
enabled
|
enabled,
|
||||||
|
is_built_in AS isBuiltIn
|
||||||
FROM t_auth_roles
|
FROM t_auth_roles
|
||||||
ORDER BY role_code ASC
|
ORDER BY role_code ASC
|
||||||
</select>
|
</select>
|
||||||
@@ -116,5 +117,80 @@
|
|||||||
(#{userId}, #{roleId})
|
(#{userId}, #{roleId})
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<select id="findRoleById" resultType="com.datamate.common.auth.domain.model.AuthRoleInfo">
|
||||||
|
SELECT id,
|
||||||
|
role_code AS roleCode,
|
||||||
|
role_name AS roleName,
|
||||||
|
description,
|
||||||
|
enabled,
|
||||||
|
is_built_in AS isBuiltIn
|
||||||
|
FROM t_auth_roles
|
||||||
|
WHERE id = #{roleId}
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findRoleByCode" resultType="com.datamate.common.auth.domain.model.AuthRoleInfo">
|
||||||
|
SELECT id,
|
||||||
|
role_code AS roleCode,
|
||||||
|
role_name AS roleName,
|
||||||
|
description,
|
||||||
|
enabled,
|
||||||
|
is_built_in AS isBuiltIn
|
||||||
|
FROM t_auth_roles
|
||||||
|
WHERE role_code = #{roleCode}
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertRole">
|
||||||
|
INSERT INTO t_auth_roles (id, role_code, role_name, description, is_built_in, enabled)
|
||||||
|
VALUES (#{id}, #{roleCode}, #{roleName}, #{description}, 0, 1)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateRole">
|
||||||
|
UPDATE t_auth_roles
|
||||||
|
SET role_name = #{roleName},
|
||||||
|
description = #{description},
|
||||||
|
enabled = #{enabled}
|
||||||
|
WHERE id = #{roleId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="deleteRoleById">
|
||||||
|
DELETE FROM t_auth_roles
|
||||||
|
WHERE id = #{roleId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<select id="findPermissionIdsByRoleId" resultType="string">
|
||||||
|
SELECT permission_id
|
||||||
|
FROM t_auth_role_permissions
|
||||||
|
WHERE role_id = #{roleId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<delete id="deleteRolePermissions">
|
||||||
|
DELETE FROM t_auth_role_permissions
|
||||||
|
WHERE role_id = #{roleId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<insert id="insertRolePermissions">
|
||||||
|
INSERT INTO t_auth_role_permissions (role_id, permission_id)
|
||||||
|
VALUES
|
||||||
|
<foreach collection="permissionIds" item="permissionId" separator=",">
|
||||||
|
(#{roleId}, #{permissionId})
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="countPermissionsByIds" resultType="int">
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM t_auth_permissions
|
||||||
|
WHERE id IN
|
||||||
|
<foreach collection="permissionIds" item="permissionId" open="(" separator="," close=")">
|
||||||
|
#{permissionId}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<delete id="deleteUserRolesByRoleId">
|
||||||
|
DELETE FROM t_auth_user_roles
|
||||||
|
WHERE role_id = #{roleId}
|
||||||
|
</delete>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,18 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
Checkbox,
|
||||||
|
Col,
|
||||||
Empty,
|
Empty,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
message,
|
message,
|
||||||
Modal,
|
Modal,
|
||||||
|
Popconfirm,
|
||||||
|
Row,
|
||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
Tag,
|
Tag,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -14,14 +21,20 @@ import {
|
|||||||
import type { ColumnsType } from "antd/es/table";
|
import type { ColumnsType } from "antd/es/table";
|
||||||
import {
|
import {
|
||||||
assignUserRolesUsingPut,
|
assignUserRolesUsingPut,
|
||||||
|
bindRolePermissionsUsingPut,
|
||||||
|
createRoleUsingPost,
|
||||||
|
deleteRoleUsingDelete,
|
||||||
|
getRolePermissionsUsingGet,
|
||||||
listAuthPermissionsUsingGet,
|
listAuthPermissionsUsingGet,
|
||||||
listAuthRolesUsingGet,
|
listAuthRolesUsingGet,
|
||||||
listAuthUsersUsingGet,
|
listAuthUsersUsingGet,
|
||||||
|
updateRoleUsingPut,
|
||||||
} from "./settings.apis";
|
} from "./settings.apis";
|
||||||
import type {
|
import type {
|
||||||
AuthPermissionInfo,
|
AuthPermissionInfo,
|
||||||
AuthRoleInfo,
|
AuthRoleInfo,
|
||||||
AuthUserWithRoles,
|
AuthUserWithRoles,
|
||||||
|
RoleWithPermissionsResponse,
|
||||||
} from "./settings.apis";
|
} from "./settings.apis";
|
||||||
|
|
||||||
interface ApiResponse<T> {
|
interface ApiResponse<T> {
|
||||||
@@ -49,6 +62,17 @@ export default function UserPermissionManagement({
|
|||||||
const [selectedRoleCodes, setSelectedRoleCodes] = useState<string[]>([]);
|
const [selectedRoleCodes, setSelectedRoleCodes] = useState<string[]>([]);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
// Role CRUD state
|
||||||
|
const [createRoleOpen, setCreateRoleOpen] = useState(false);
|
||||||
|
const [editingRole, setEditingRole] = useState<AuthRoleInfo | null>(null);
|
||||||
|
const [createRoleForm] = Form.useForm();
|
||||||
|
const [editRoleForm] = Form.useForm();
|
||||||
|
|
||||||
|
// Permission config state
|
||||||
|
const [permConfigRole, setPermConfigRole] = useState<AuthRoleInfo | null>(null);
|
||||||
|
const [selectedPermissionIds, setSelectedPermissionIds] = useState<string[]>([]);
|
||||||
|
const [permConfigLoading, setPermConfigLoading] = useState(false);
|
||||||
|
|
||||||
const canShowAnything = canManageUsers || canViewRoles || canViewPermissions;
|
const canShowAnything = canManageUsers || canViewRoles || canViewPermissions;
|
||||||
const canAssignRoles = canManageUsers && roles.length > 0;
|
const canAssignRoles = canManageUsers && roles.length > 0;
|
||||||
|
|
||||||
@@ -61,6 +85,17 @@ export default function UserPermissionManagement({
|
|||||||
[roles]
|
[roles]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const permissionsByModule = useMemo(() => {
|
||||||
|
const map = new Map<string, AuthPermissionInfo[]>();
|
||||||
|
for (const perm of permissions) {
|
||||||
|
const module = perm.module || "其他";
|
||||||
|
const list = map.get(module) ?? [];
|
||||||
|
list.push(perm);
|
||||||
|
map.set(module, list);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, [permissions]);
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -169,6 +204,14 @@ export default function UserPermissionManagement({
|
|||||||
const roleColumns: ColumnsType<AuthRoleInfo> = [
|
const roleColumns: ColumnsType<AuthRoleInfo> = [
|
||||||
{ title: "角色编码", dataIndex: "roleCode", key: "roleCode", width: 220 },
|
{ title: "角色编码", dataIndex: "roleCode", key: "roleCode", width: 220 },
|
||||||
{ title: "角色名称", dataIndex: "roleName", key: "roleName", width: 180 },
|
{ title: "角色名称", dataIndex: "roleName", key: "roleName", width: 180 },
|
||||||
|
{
|
||||||
|
title: "类型",
|
||||||
|
dataIndex: "isBuiltIn",
|
||||||
|
key: "isBuiltIn",
|
||||||
|
width: 100,
|
||||||
|
render: (isBuiltIn?: boolean) =>
|
||||||
|
isBuiltIn ? <Tag color="blue">内置</Tag> : <Tag color="orange">自定义</Tag>,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "状态",
|
title: "状态",
|
||||||
dataIndex: "enabled",
|
dataIndex: "enabled",
|
||||||
@@ -183,6 +226,49 @@ export default function UserPermissionManagement({
|
|||||||
key: "description",
|
key: "description",
|
||||||
render: (value?: string) => value || "-",
|
render: (value?: string) => value || "-",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "actions",
|
||||||
|
width: 220,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingRole(record);
|
||||||
|
editRoleForm.setFieldsValue({
|
||||||
|
roleName: record.roleName,
|
||||||
|
description: record.description ?? "",
|
||||||
|
enabled: record.enabled ?? true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={() => void openPermissionConfig(record)}
|
||||||
|
>
|
||||||
|
权限配置
|
||||||
|
</Button>
|
||||||
|
{!record.isBuiltIn && (
|
||||||
|
<Popconfirm
|
||||||
|
title="确定要删除该角色吗?"
|
||||||
|
description="删除后关联的用户绑定将同步移除"
|
||||||
|
onConfirm={() => void handleDeleteRole(record.id)}
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<Button type="link" size="small" danger>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const permissionColumns: ColumnsType<AuthPermissionInfo> = [
|
const permissionColumns: ColumnsType<AuthPermissionInfo> = [
|
||||||
@@ -250,6 +336,97 @@ export default function UserPermissionManagement({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCreateRole = async () => {
|
||||||
|
try {
|
||||||
|
const values = await createRoleForm.validateFields();
|
||||||
|
setSubmitting(true);
|
||||||
|
await createRoleUsingPost({
|
||||||
|
roleCode: values.roleCode,
|
||||||
|
roleName: values.roleName,
|
||||||
|
description: values.description,
|
||||||
|
});
|
||||||
|
message.success("角色创建成功");
|
||||||
|
setCreateRoleOpen(false);
|
||||||
|
createRoleForm.resetFields();
|
||||||
|
await loadData();
|
||||||
|
} catch (error) {
|
||||||
|
if (error && typeof error === "object" && "errorFields" in error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error("角色创建失败");
|
||||||
|
console.error("角色创建失败:", error);
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateRole = async () => {
|
||||||
|
if (!editingRole) return;
|
||||||
|
try {
|
||||||
|
const values = await editRoleForm.validateFields();
|
||||||
|
setSubmitting(true);
|
||||||
|
await updateRoleUsingPut(editingRole.id, {
|
||||||
|
roleName: values.roleName,
|
||||||
|
description: values.description,
|
||||||
|
enabled: values.enabled,
|
||||||
|
});
|
||||||
|
message.success("角色更新成功");
|
||||||
|
setEditingRole(null);
|
||||||
|
editRoleForm.resetFields();
|
||||||
|
await loadData();
|
||||||
|
} catch (error) {
|
||||||
|
if (error && typeof error === "object" && "errorFields" in error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error("角色更新失败");
|
||||||
|
console.error("角色更新失败:", error);
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteRole = async (roleId: string) => {
|
||||||
|
try {
|
||||||
|
await deleteRoleUsingDelete(roleId);
|
||||||
|
message.success("角色删除成功");
|
||||||
|
await loadData();
|
||||||
|
} catch (error) {
|
||||||
|
message.error("角色删除失败");
|
||||||
|
console.error("角色删除失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openPermissionConfig = async (role: AuthRoleInfo) => {
|
||||||
|
setPermConfigRole(role);
|
||||||
|
setPermConfigLoading(true);
|
||||||
|
try {
|
||||||
|
const response = (await getRolePermissionsUsingGet(role.id)) as ApiResponse<RoleWithPermissionsResponse>;
|
||||||
|
setSelectedPermissionIds(response?.data?.permissionIds ?? []);
|
||||||
|
} catch (error) {
|
||||||
|
message.error("加载角色权限失败");
|
||||||
|
console.error("加载角色权限失败:", error);
|
||||||
|
setPermConfigRole(null);
|
||||||
|
} finally {
|
||||||
|
setPermConfigLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBindPermissions = async () => {
|
||||||
|
if (!permConfigRole) return;
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
await bindRolePermissionsUsingPut(permConfigRole.id, selectedPermissionIds);
|
||||||
|
message.success("权限配置成功");
|
||||||
|
setPermConfigRole(null);
|
||||||
|
setSelectedPermissionIds([]);
|
||||||
|
} catch (error) {
|
||||||
|
message.error("权限配置失败");
|
||||||
|
console.error("权限配置失败:", error);
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!canShowAnything) {
|
if (!canShowAnything) {
|
||||||
return <Empty description="当前账号无用户与权限管理权限" />;
|
return <Empty description="当前账号无用户与权限管理权限" />;
|
||||||
}
|
}
|
||||||
@@ -266,7 +443,14 @@ export default function UserPermissionManagement({
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
{canViewRoles && (
|
{canViewRoles && (
|
||||||
<Card title="角色列表">
|
<Card
|
||||||
|
title="角色列表"
|
||||||
|
extra={
|
||||||
|
<Button type="primary" onClick={() => setCreateRoleOpen(true)}>
|
||||||
|
创建角色
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
@@ -287,6 +471,8 @@ export default function UserPermissionManagement({
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 分配角色 Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
title={`分配角色 - ${editingUser?.username || ""}`}
|
title={`分配角色 - ${editingUser?.username || ""}`}
|
||||||
open={Boolean(editingUser)}
|
open={Boolean(editingUser)}
|
||||||
@@ -315,7 +501,122 @@ export default function UserPermissionManagement({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 创建角色 Modal */}
|
||||||
|
<Modal
|
||||||
|
title="创建角色"
|
||||||
|
open={createRoleOpen}
|
||||||
|
confirmLoading={submitting}
|
||||||
|
onOk={() => {
|
||||||
|
void handleCreateRole();
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setCreateRoleOpen(false);
|
||||||
|
createRoleForm.resetFields();
|
||||||
|
}}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form form={createRoleForm} layout="vertical" style={{ marginTop: 16 }}>
|
||||||
|
<Form.Item
|
||||||
|
name="roleCode"
|
||||||
|
label="角色编码"
|
||||||
|
rules={[{ required: true, message: "请输入角色编码" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="如:data_analyst" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="roleName"
|
||||||
|
label="角色名称"
|
||||||
|
rules={[{ required: true, message: "请输入角色名称" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="如:数据分析师" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="description" label="描述">
|
||||||
|
<Input.TextArea rows={3} placeholder="角色描述(可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 编辑角色 Modal */}
|
||||||
|
<Modal
|
||||||
|
title="编辑角色"
|
||||||
|
open={Boolean(editingRole)}
|
||||||
|
confirmLoading={submitting}
|
||||||
|
onOk={() => {
|
||||||
|
void handleUpdateRole();
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setEditingRole(null);
|
||||||
|
editRoleForm.resetFields();
|
||||||
|
}}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form form={editRoleForm} layout="vertical" style={{ marginTop: 16 }}>
|
||||||
|
<Form.Item label="角色编码">
|
||||||
|
<Input value={editingRole?.roleCode} disabled />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="roleName"
|
||||||
|
label="角色名称"
|
||||||
|
rules={[{ required: true, message: "请输入角色名称" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="角色名称" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="description" label="描述">
|
||||||
|
<Input.TextArea rows={3} placeholder="角色描述(可选)" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="enabled" label="启用状态" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* 权限配置 Modal */}
|
||||||
|
<Modal
|
||||||
|
title={`权限配置 - ${permConfigRole?.roleName || ""}`}
|
||||||
|
open={Boolean(permConfigRole)}
|
||||||
|
confirmLoading={submitting}
|
||||||
|
onOk={() => {
|
||||||
|
void handleBindPermissions();
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setPermConfigRole(null);
|
||||||
|
setSelectedPermissionIds([]);
|
||||||
|
}}
|
||||||
|
width={640}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
{permConfigLoading ? (
|
||||||
|
<Typography.Text type="secondary">加载中...</Typography.Text>
|
||||||
|
) : permissions.length === 0 ? (
|
||||||
|
<Typography.Text type="secondary">暂无可配置权限</Typography.Text>
|
||||||
|
) : (
|
||||||
|
<div style={{ maxHeight: 400, overflow: "auto" }}>
|
||||||
|
<Checkbox.Group
|
||||||
|
value={selectedPermissionIds}
|
||||||
|
onChange={(values) => setSelectedPermissionIds(values as string[])}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
|
{Array.from(permissionsByModule.entries()).map(([module, perms]) => (
|
||||||
|
<div key={module} style={{ marginBottom: 16 }}>
|
||||||
|
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||||
|
{module}
|
||||||
|
</Typography.Text>
|
||||||
|
<Row>
|
||||||
|
{perms.map((perm) => (
|
||||||
|
<Col span={12} key={perm.id}>
|
||||||
|
<Checkbox value={perm.id}>
|
||||||
|
{perm.permissionName}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Checkbox.Group>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export interface AuthRoleInfo {
|
|||||||
roleName: string;
|
roleName: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
isBuiltIn?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthPermissionInfo {
|
export interface AuthPermissionInfo {
|
||||||
@@ -85,3 +86,38 @@ export function listAuthPermissionsUsingGet() {
|
|||||||
export function assignUserRolesUsingPut(userId: number, roleIds: string[]) {
|
export function assignUserRolesUsingPut(userId: number, roleIds: string[]) {
|
||||||
return put(`/api/auth/users/${userId}/roles`, { roleIds });
|
return put(`/api/auth/users/${userId}/roles`, { roleIds });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RoleWithPermissionsResponse {
|
||||||
|
role: AuthRoleInfo;
|
||||||
|
permissionIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRoleUsingPost(data: {
|
||||||
|
roleCode: string;
|
||||||
|
roleName: string;
|
||||||
|
description?: string;
|
||||||
|
}) {
|
||||||
|
return post("/api/auth/roles", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateRoleUsingPut(
|
||||||
|
roleId: string,
|
||||||
|
data: { roleName: string; description?: string; enabled?: boolean }
|
||||||
|
) {
|
||||||
|
return put(`/api/auth/roles/${roleId}`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteRoleUsingDelete(roleId: string) {
|
||||||
|
return del(`/api/auth/roles/${roleId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRolePermissionsUsingGet(roleId: string) {
|
||||||
|
return get(`/api/auth/roles/${roleId}/permissions`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bindRolePermissionsUsingPut(
|
||||||
|
roleId: string,
|
||||||
|
permissionIds: string[]
|
||||||
|
) {
|
||||||
|
return put(`/api/auth/roles/${roleId}/permissions`, { permissionIds });
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user