1234 lines
38 KiB
Markdown
1234 lines
38 KiB
Markdown
# 权限与账户领域技术方案
|
||
|
||
**领域编号**: 4.5
|
||
**微服务**: ether-auth
|
||
**最后更新**: 2026-02-27
|
||
|
||
---
|
||
|
||
## 一、领域概述
|
||
|
||
### 1.1 领域职责
|
||
|
||
权限与账户领域负责 Ether 平台的身份认证与授权管理:
|
||
- 用户认证与登录
|
||
- RBAC 权限模型(角色-权限-用户关联)
|
||
- 项目隔离与数据权限
|
||
- 按钮级权限控制
|
||
- 状态驱动权限
|
||
- 访客凭证管理
|
||
- 操作日志审计
|
||
|
||
### 1.2 核心概念
|
||
|
||
| 概念 | 说明 | 对应实体 |
|
||
|------|------|----------|
|
||
| **用户** | 系统登录账户 | User |
|
||
| **角色** | 权限集合 | Role |
|
||
| **权限** | 功能访问控制点 | Permission |
|
||
| **项目** | 多租户隔离单位 | Project |
|
||
| **访客凭证** | 临时访问授权 | VisitorCredential |
|
||
|
||
---
|
||
|
||
## 二、角色体系设计
|
||
|
||
### 2.1 角色总览
|
||
|
||
| 类型 | 角色编码 | 角色名称 | 数据范围 | 终端类型 | 一线人员 |
|
||
|------|---------|---------|---------|---------|---------|
|
||
| **系统级** |
|
||
| 系统管理 | SUPER_ADMIN | 超级管理员 | ALL | ALL | 否 |
|
||
| 系统管理 | SYS_ADMIN | 系统管理员 | ALL | ADMIN_ONLY | 否 |
|
||
| **项目管理** |
|
||
| 项目管理 | PROPERTY_MANAGER | 物业经理 | PROJECT | ALL | 否 |
|
||
| 项目管理 | PROJECT_MANAGER | 项目经理 | PROJECT | ALL | 否 |
|
||
| **部门主管** |
|
||
| 工程管理 | ENGINEERING_LEAD | 工程主管 | DEPARTMENT | ALL | 否 |
|
||
| 安保管理 | SECURITY_LEAD | 安保主管 | DEPARTMENT | ALL | 否 |
|
||
| 保洁管理 | CLEANING_LEAD | 保洁主管 | DEPARTMENT | ALL | 否 |
|
||
| 财务管理 | FINANCE_LEAD | 财务主管 | DEPARTMENT | ALL | 否 |
|
||
| **一线执行** |
|
||
| 工程执行 | MAINTENANCE_STAFF | 维修人员 | SELF | MOBILE_ONLY | 是 |
|
||
| 安保执行 | SECURITY_STAFF | 安保人员 | SELF | MOBILE_ONLY | 是 |
|
||
| 保洁执行 | CLEANING_STAFF | 保洁人员 | SELF | MOBILE_ONLY | 是 |
|
||
| **服务支持** |
|
||
| 业主服务 | CS_STAFF | 客服人员 | PROJECT | ALL | 否 |
|
||
| **外部用户** |
|
||
| 业主 | OWNER | 业主 | SELF | OWNER_APP | 否 |
|
||
|
||
### 2.2 角色职责说明
|
||
|
||
#### 2.2.1 系统级角色
|
||
|
||
| 角色 | 职责范围 | 典型场景 |
|
||
|------|---------|---------|
|
||
| **超级管理员** | 系统最高权限,管理所有项目和用户 | 系统初始化、用户创建、项目创建 |
|
||
| **系统管理员** | 系统配置管理,不参与业务操作 | 系统参数配置、日志查看 |
|
||
|
||
#### 2.2.2 项目管理角色
|
||
|
||
| 角色 | 职责范围 | 典型场景 |
|
||
|------|---------|---------|
|
||
| **物业经理** | 项目经营第一负责人,拥有本项目全部权限 | 项目整体运营、费用审核、合同审批 |
|
||
| **项目经理** | 单项目管理,拥有本项目全部业务权限 | 工单管理、人员分配、报表查看 |
|
||
|
||
#### 2.2.3 部门主管角色
|
||
|
||
| 角色 | 管理模块 | 职责范围 | 典型场景 |
|
||
|------|---------|---------|---------|
|
||
| **工程主管** | 工单、设备、工程巡检 | 本部门工程事务管理 | 工单分配、设备管理、巡检计划制定 |
|
||
| **安保主管** | 安保巡检、访客管理 | 本部门安保事务管理 | 安保巡检计划、访客核验监督 |
|
||
| **保洁主管** | 保洁巡检、品质检查 | 本部门保洁事务管理 | 保洁巡检计划、品质检查 |
|
||
| **财务主管** | 账单、收费、费用审核 | 本部门财务事务管理 | 账单生成、费用审核、报表导出 |
|
||
|
||
#### 2.2.4 一线执行角色
|
||
|
||
| 角色 | 执行范围 | 终端限制 | 典型场景 |
|
||
|------|---------|---------|---------|
|
||
| **维修人员** | 工单处理、设备巡检 | 仅移动端 | 接单、维修、填报费用、设备巡检 |
|
||
| **安保人员** | 安保巡检、访客核验 | 仅移动端 | 扫码签到、访客核验、异常报送 |
|
||
| **保洁人员** | 保洁巡检、品质检查 | 仅移动端 | 扫码签到、品质异常报送 |
|
||
|
||
#### 2.2.5 服务支持角色
|
||
|
||
| 角色 | 职责范围 | 典型场景 |
|
||
|------|---------|---------|
|
||
| **客服人员** | 业主服务接口,不参与工单流程 | 业主咨询、资料维护、访客辅助核验 |
|
||
|
||
#### 2.2.6 外部用户角色
|
||
|
||
| 角色 | 权限范围 | 典型场景 |
|
||
|------|---------|---------|
|
||
| **业主** | 本人房产相关数据 | 在线报修、缴费、访客邀请、评价 |
|
||
|
||
---
|
||
|
||
## 三、领域模型
|
||
|
||
### 3.1 聚合根设计
|
||
|
||
#### User(系统用户)
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "auth_user")
|
||
@Data
|
||
public class User {
|
||
@Id
|
||
private UUID id;
|
||
|
||
private String username; // 登录账号
|
||
private String password; // 加密密码
|
||
private String salt; // 密码盐值
|
||
|
||
private String realName; // 真实姓名
|
||
private String phone;
|
||
private String email;
|
||
private String avatar; // 头像URL
|
||
|
||
private UserStatus status; // ACTIVE/LOCKED/DISABLED
|
||
private LocalDateTime lastLoginTime;
|
||
private String lastLoginIp;
|
||
|
||
@ManyToMany(fetch = FetchType.LAZY)
|
||
@JoinTable(
|
||
name = "auth_user_role",
|
||
joinColumns = @JoinColumn(name = "user_id"),
|
||
inverseJoinColumns = @JoinColumn(name = "role_id")
|
||
)
|
||
private List<Role> roles;
|
||
|
||
private LocalDateTime createdAt;
|
||
private LocalDateTime updatedAt;
|
||
private UUID createdBy;
|
||
}
|
||
|
||
public enum UserStatus {
|
||
ACTIVE("正常"),
|
||
LOCKED("锁定"),
|
||
DISABLED("禁用");
|
||
}
|
||
```
|
||
|
||
#### Role(角色)
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "auth_role")
|
||
@Data
|
||
public class Role {
|
||
@Id
|
||
private UUID id;
|
||
private UUID projectId; // 项目隔离(NULL表示系统级)
|
||
|
||
private String name; // 角色名称
|
||
private String code; // 角色编码
|
||
private String description; // 角色描述
|
||
|
||
private RoleType type; // SYSTEM(系统预设)/CUSTOM(自定义)
|
||
|
||
// 数据权限范围(四级)
|
||
private DataScope dataScope; // ALL/PROJECT/DEPARTMENT/SELF
|
||
|
||
// 新增:业务属性
|
||
private BusinessType businessType; // 业务类型
|
||
private TerminalType terminalType; // 终端类型限制
|
||
private Boolean isFrontline; // 是否一线执行人员
|
||
|
||
@ManyToMany(fetch = FetchType.LAZY)
|
||
@JoinTable(
|
||
name = "auth_role_permission",
|
||
joinColumns = @JoinColumn(name = "role_id"),
|
||
inverseJoinColumns = @JoinColumn(name = "permission_id")
|
||
)
|
||
private List<Permission> permissions;
|
||
|
||
private Boolean enabled;
|
||
private LocalDateTime createdAt;
|
||
private LocalDateTime updatedAt;
|
||
}
|
||
|
||
public enum RoleType {
|
||
SYSTEM("系统预设"),
|
||
CUSTOM("自定义");
|
||
}
|
||
|
||
// 数据权限范围枚举(四级)
|
||
public enum DataScope {
|
||
ALL("全部数据"),
|
||
PROJECT("本项目数据"),
|
||
DEPARTMENT("本部门数据"),
|
||
SELF("仅本人数据");
|
||
}
|
||
|
||
// 业务类型枚举
|
||
public enum BusinessType {
|
||
MANAGEMENT("管理类"),
|
||
ENGINEERING("工程类"),
|
||
SECURITY("安保类"),
|
||
CLEANING("保洁类"),
|
||
CUSTOMER_SERVICE("客服类"),
|
||
FINANCE("财务类");
|
||
}
|
||
|
||
// 终端类型枚举
|
||
public enum TerminalType {
|
||
ALL("全终端"),
|
||
ADMIN_ONLY("仅管理后台"),
|
||
MOBILE_ONLY("仅移动端"),
|
||
OWNER_APP("业主端");
|
||
}
|
||
```
|
||
|
||
#### Permission(权限)
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "auth_permission")
|
||
@Data
|
||
public class Permission {
|
||
@Id
|
||
private UUID id;
|
||
private UUID projectId; // 项目隔离(NULL表示系统级)
|
||
|
||
private String name; // 权限名称
|
||
private String code; // 权限编码: module:resource:action
|
||
private PermissionType type; // MENU/BUTTON/API
|
||
|
||
// 模块和资源信息
|
||
private String module; // 模块标识: system/mdm/ops/finance
|
||
private String resource; // 资源类型: user/role/work_order
|
||
private String action; // 操作类型: view/create/edit/delete/assign
|
||
|
||
// 菜单属性
|
||
private String path; // 路由路径
|
||
private String component; // 组件路径
|
||
private String icon; // 图标
|
||
private Integer sortOrder; // 排序
|
||
|
||
// 树形结构
|
||
private UUID parentId; // 父权限ID
|
||
private Integer level; // 层级
|
||
|
||
// API属性
|
||
private String apiMethod; // GET/POST/PUT/DELETE
|
||
private String apiPath; // API路径
|
||
|
||
private Boolean enabled;
|
||
private LocalDateTime createdAt;
|
||
private LocalDateTime updatedAt;
|
||
}
|
||
|
||
public enum PermissionType {
|
||
MENU("菜单"),
|
||
BUTTON("按钮"),
|
||
API("接口");
|
||
}
|
||
```
|
||
|
||
### 3.2 关联实体
|
||
|
||
#### UserRole(用户角色关联)
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "auth_user_role")
|
||
@Data
|
||
public class UserRole {
|
||
@Id
|
||
private UUID id;
|
||
|
||
private UUID userId;
|
||
private UUID roleId;
|
||
private UUID projectId; // 项目隔离
|
||
private Boolean isDefault; // 是否默认角色
|
||
|
||
private LocalDateTime createdAt;
|
||
}
|
||
```
|
||
|
||
#### RolePermission(角色权限关联)
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "auth_role_permission")
|
||
@Data
|
||
public class RolePermission {
|
||
@Id
|
||
private UUID id;
|
||
|
||
private UUID roleId;
|
||
private UUID permissionId;
|
||
|
||
private LocalDateTime createdAt;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、权限编码规范
|
||
|
||
### 4.1 编码格式
|
||
|
||
**格式**: `module:resource:action`
|
||
|
||
| 组成部分 | 说明 | 示例值 |
|
||
|---------|------|--------|
|
||
| module | 模块标识 | system, mdm, ops, asset, finance |
|
||
| resource | 资源类型 | user, role, work_order, equipment, bill |
|
||
| action | 操作类型 | view, create, edit, delete, assign, accept, complete |
|
||
|
||
### 4.2 操作类型说明
|
||
|
||
| 操作类型 | 说明 | 适用场景 |
|
||
|---------|------|---------|
|
||
| view | 查看 | 列表、详情查看 |
|
||
| create | 创建 | 新增记录 |
|
||
| edit | 编辑 | 修改记录 |
|
||
| delete | 删除 | 删除记录 |
|
||
| assign | 分配 | 工单分配、任务分配 |
|
||
| accept | 接单 | 工单接单 |
|
||
| start | 开始 | 开始处理、开始巡检 |
|
||
| complete | 完成 | 完成工单、完成巡检 |
|
||
| transfer | 转单 | 工单转单 |
|
||
| close | 关闭 | 关闭工单 |
|
||
| evaluate | 评价 | 工单评价 |
|
||
| report_fee | 填报费用 | 工单费用填报 |
|
||
| audit_fee | 费用审核 | 费用审核 |
|
||
| audit_quality | 质量审核 | 质量审核 |
|
||
| export | 导出 | 数据导出 |
|
||
| generate_qr | 生成二维码 | 二维码生成 |
|
||
| scan_view | 扫码查看 | 扫码查看设备 |
|
||
| force_close | 强制闭环 | 逾期任务强制关闭 |
|
||
| verify | 核验 | 访客核验 |
|
||
|
||
---
|
||
|
||
## 五、认证授权流程
|
||
|
||
### 5.1 JWT Token 认证
|
||
|
||
```java
|
||
@Component
|
||
public class JwtTokenProvider {
|
||
|
||
@Value("${jwt.secret}")
|
||
private String jwtSecret;
|
||
|
||
@Value("${jwt.expiration:86400000}")
|
||
private long jwtExpiration;
|
||
|
||
private SecretKey secretKey;
|
||
|
||
@PostConstruct
|
||
public void init() {
|
||
this.secretKey = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
|
||
}
|
||
|
||
public String generateToken(User user, UUID projectId) {
|
||
Date now = new Date();
|
||
Date expiryDate = new Date(now.getTime() + jwtExpiration);
|
||
|
||
return Jwts.builder()
|
||
.setSubject(user.getId().toString())
|
||
.claim("username", user.getUsername())
|
||
.claim("realName", user.getRealName())
|
||
.claim("projectId", projectId != null ? projectId.toString() : null)
|
||
.setIssuedAt(now)
|
||
.setExpiration(expiryDate)
|
||
.signWith(secretKey, SignatureAlgorithm.HS256)
|
||
.compact();
|
||
}
|
||
|
||
public boolean validateToken(String token) {
|
||
try {
|
||
Jwts.parserBuilder()
|
||
.setSigningKey(secretKey)
|
||
.build()
|
||
.parseClaimsJws(token);
|
||
return true;
|
||
} catch (Exception e) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public UUID getUserIdFromToken(String token) {
|
||
Claims claims = Jwts.parserBuilder()
|
||
.setSigningKey(secretKey)
|
||
.build()
|
||
.parseClaimsJws(token)
|
||
.getBody();
|
||
|
||
return UUID.fromString(claims.getSubject());
|
||
}
|
||
|
||
public UUID getProjectIdFromToken(String token) {
|
||
Claims claims = Jwts.parserBuilder()
|
||
.setSigningKey(secretKey)
|
||
.build()
|
||
.parseClaimsJws(token)
|
||
.getBody();
|
||
|
||
String projectId = claims.get("projectId", String.class);
|
||
return projectId != null ? UUID.fromString(projectId) : null;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 项目上下文
|
||
|
||
```java
|
||
public class ProjectContextHolder {
|
||
private static final ThreadLocal<UUID> PROJECT_CONTEXT = new ThreadLocal<>();
|
||
|
||
public static void setCurrentProjectId(UUID projectId) {
|
||
PROJECT_CONTEXT.set(projectId);
|
||
}
|
||
|
||
public static UUID getCurrentProjectId() {
|
||
return PROJECT_CONTEXT.get();
|
||
}
|
||
|
||
public static void clear() {
|
||
PROJECT_CONTEXT.remove();
|
||
}
|
||
}
|
||
|
||
@Component
|
||
public class ProjectContextInterceptor implements HandlerInterceptor {
|
||
|
||
@Override
|
||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||
String projectId = request.getHeader("X-Project-ID");
|
||
if (StringUtils.hasText(projectId)) {
|
||
try {
|
||
ProjectContextHolder.setCurrentProjectId(UUID.fromString(projectId));
|
||
} catch (IllegalArgumentException e) {
|
||
// 无效的Project ID格式
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
@Override
|
||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||
ProjectContextHolder.clear();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 权限检查
|
||
|
||
```java
|
||
@Service
|
||
public class PermissionService {
|
||
|
||
@Autowired
|
||
private UserRepository userRepository;
|
||
|
||
@Autowired
|
||
private RoleRepository roleRepository;
|
||
|
||
// 检查用户是否有指定权限
|
||
public boolean hasPermission(UUID userId, String permissionCode) {
|
||
User user = userRepository.findById(userId)
|
||
.orElseThrow(() -> new NotFoundException("用户不存在"));
|
||
|
||
// 超级管理员拥有所有权限
|
||
if (isSuperAdmin(user)) {
|
||
return true;
|
||
}
|
||
|
||
// 检查用户角色是否包含该权限
|
||
for (Role role : user.getRoles()) {
|
||
if (!role.getEnabled()) {
|
||
continue;
|
||
}
|
||
for (Permission permission : role.getPermissions()) {
|
||
if (permission.getCode().equals(permissionCode)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 检查用户是否有指定权限(带项目隔离)
|
||
public boolean hasPermission(UUID userId, UUID projectId, String permissionCode) {
|
||
// 超级管理员
|
||
if (isSuperAdmin(userId)) {
|
||
return true;
|
||
}
|
||
|
||
// 获取用户在该项目下的权限
|
||
List<Permission> permissions = permissionRepository
|
||
.findPermissionsByUserIdAndProjectId(userId, projectId);
|
||
|
||
return permissions.stream()
|
||
.anyMatch(p -> p.getCode().equals(permissionCode));
|
||
}
|
||
|
||
// 获取用户菜单
|
||
public List<Permission> getUserMenus(UUID userId, UUID projectId) {
|
||
List<Permission> permissions = permissionRepository
|
||
.findByUserIdAndProjectId(userId, projectId);
|
||
|
||
return permissions.stream()
|
||
.filter(p -> p.getType() == PermissionType.MENU)
|
||
.filter(Permission::getEnabled)
|
||
.sorted(Comparator.comparing(Permission::getSortOrder))
|
||
.collect(Collectors.toList());
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、数据权限设计
|
||
|
||
### 6.1 数据范围级别
|
||
|
||
| 数据范围 | 编码 | 说明 | SQL预设 |
|
||
|---------|------|------|---------|
|
||
| 全部 | ALL | 所有项目数据 | `WHERE 1=1` |
|
||
| 项目 | PROJECT | 本项目数据 | `WHERE project_id = $current_project` |
|
||
| 部门 | DEPARTMENT | 本部门数据 | `WHERE project_id = $current_project AND dept_id = $current_dept` |
|
||
| 本人 | SELF | 仅本人数据 | `WHERE assigned_to = $current_user` |
|
||
|
||
### 6.2 数据权限工具类
|
||
|
||
```java
|
||
public final class DataScopeHelper {
|
||
|
||
public static String getDataFilterSql(String tableAlias, String creatorField,
|
||
String deptField, String projectField) {
|
||
DataScopeInfo scope = DataScopeContext.getDataScopeInfo();
|
||
String prefix = tableAlias != null ? tableAlias + "." : "";
|
||
|
||
return switch (scope.getScope()) {
|
||
case ALL -> "1=1";
|
||
case PROJECT -> prefix + projectField + " = '" + scope.getProjectId() + "'";
|
||
case DEPARTMENT -> prefix + projectField + " = '" + scope.getProjectId() + "' " +
|
||
"AND " + prefix + deptField + " = '" + scope.getDepartmentId() + "'";
|
||
case SELF -> prefix + creatorField + " = '" + scope.getUserId() + "'";
|
||
};
|
||
}
|
||
|
||
public static boolean canAccessData(UUID dataProjectId, UUID dataDeptId, UUID dataCreatorId) {
|
||
DataScopeInfo scope = DataScopeContext.getDataScopeInfo();
|
||
|
||
return switch (scope.getScope()) {
|
||
case ALL -> true;
|
||
case PROJECT -> dataProjectId.equals(scope.getProjectId());
|
||
case DEPARTMENT -> dataProjectId.equals(scope.getProjectId()) &&
|
||
(dataDeptId == null || dataDeptId.equals(scope.getDepartmentId()));
|
||
case SELF -> dataCreatorId.equals(scope.getUserId());
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 七、状态驱动权限
|
||
|
||
### 7.1 工单状态与操作映射
|
||
|
||
```java
|
||
public enum WorkOrderStatus {
|
||
CREATED("已创建"),
|
||
ASSIGNED("已分配"),
|
||
ACCEPTED("已接单"),
|
||
IN_PROGRESS("处理中"),
|
||
PENDING("待确认"),
|
||
COMPLETED("已完成"),
|
||
CLOSED("已关闭"),
|
||
SUSPENDED("已挂起"),
|
||
RETURNED("已退回");
|
||
|
||
// 获取该状态下可执行的操作
|
||
public Set<WorkOrderAction> getAllowedActions() {
|
||
return switch (this) {
|
||
case CREATED -> Set.of(WorkOrderAction.ASSIGN, WorkOrderAction.EDIT, WorkOrderAction.DELETE);
|
||
case ASSIGNED -> Set.of(WorkOrderAction.ACCEPT, WorkOrderAction.TRANSFER,
|
||
WorkOrderAction.SUSPEND, WorkOrderAction.RETURN);
|
||
case ACCEPTED -> Set.of(WorkOrderAction.START, WorkOrderAction.TRANSFER, WorkOrderAction.SUSPEND);
|
||
case IN_PROGRESS -> Set.of(WorkOrderAction.COMPLETE, WorkOrderAction.REPORT_FEE, WorkOrderAction.SUSPEND);
|
||
case PENDING -> Set.of(WorkOrderAction.COMPLETE, WorkOrderAction.AUDIT_QUALITY);
|
||
case COMPLETED -> Set.of(WorkOrderAction.EVALUATE, WorkOrderAction.CLOSE, WorkOrderAction.AUDIT_FEE);
|
||
case CLOSED -> Set.of();
|
||
case SUSPENDED -> Set.of(WorkOrderAction.RESUME);
|
||
case RETURNED -> Set.of(WorkOrderAction.ASSIGN, WorkOrderAction.DELETE);
|
||
};
|
||
}
|
||
}
|
||
|
||
public enum WorkOrderAction {
|
||
VIEW, CREATE, EDIT, DELETE,
|
||
ASSIGN, ACCEPT, START, COMPLETE, TRANSFER, CLOSE, SUSPEND, RESUME, RETURN,
|
||
EVALUATE, REPORT_FEE, AUDIT_FEE, AUDIT_QUALITY
|
||
}
|
||
```
|
||
|
||
### 7.2 权限校验增强
|
||
|
||
```java
|
||
// WorkOrderServiceImpl.java
|
||
public WorkOrder assign(UUID workOrderId, UUID assigneeId, String assigneeName) {
|
||
WorkOrder workOrder = getWorkOrder(workOrderId);
|
||
UUID currentUserId = getCurrentUserId();
|
||
|
||
// 1. 状态校验
|
||
validateStatusTransition(workOrder.getStatus(), WorkOrderStatus.ASSIGNED);
|
||
|
||
// 2. 权限校验
|
||
if (!hasActionPermission(workOrder, WorkOrderAction.ASSIGN)) {
|
||
throw new PermissionDeniedException("无分配工单权限");
|
||
}
|
||
|
||
// 3. 数据权限校验
|
||
if (!canAccessWorkOrder(workOrder)) {
|
||
throw new PermissionDeniedException("无权访问该工单");
|
||
}
|
||
|
||
// 执行分配逻辑...
|
||
}
|
||
|
||
private boolean hasActionPermission(WorkOrder workOrder, WorkOrderAction action) {
|
||
// 1. 检查状态是否允许该操作
|
||
if (!workOrder.getStatus().getAllowedActions().contains(action)) {
|
||
return false;
|
||
}
|
||
|
||
// 2. 检查用户是否有该操作的权限编码
|
||
String permissionCode = "ops:work_order:" + action.name().toLowerCase();
|
||
return permissionService.hasPermission(getCurrentUserId(), getProjectId(), permissionCode);
|
||
}
|
||
|
||
private boolean canAccessWorkOrder(WorkOrder workOrder) {
|
||
DataScopeInfo scope = dataScopeContext.getDataScopeInfo();
|
||
|
||
return switch (scope.getScope()) {
|
||
case ALL -> true;
|
||
case PROJECT -> workOrder.getProjectId().equals(scope.getProjectId());
|
||
case DEPARTMENT -> workOrder.getDepartmentId().equals(scope.getDepartmentId());
|
||
case SELF -> workOrder.getAssigneeId().equals(scope.getUserId()) ||
|
||
workOrder.getCreatorId().equals(scope.getUserId());
|
||
};
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 八、访客凭证管理
|
||
|
||
### 8.1 访客凭证设计
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "auth_visitor_credential")
|
||
@Data
|
||
public class VisitorCredential {
|
||
@Id
|
||
private UUID id;
|
||
private UUID projectId;
|
||
|
||
private String visitorName;
|
||
private String visitorPhone;
|
||
private String visitorIdCard;
|
||
|
||
private String credentialCode;
|
||
private String qrCode;
|
||
private LocalDateTime expireTime;
|
||
|
||
private UUID spaceNodeId;
|
||
private String accessGates;
|
||
|
||
private CredentialStatus status;
|
||
|
||
private LocalDateTime firstUseTime;
|
||
private LocalDateTime lastUseTime;
|
||
private Integer useCount;
|
||
|
||
private UUID creatorId;
|
||
private String creatorName;
|
||
|
||
private LocalDateTime createdAt;
|
||
}
|
||
|
||
public enum CredentialStatus {
|
||
ACTIVE("有效"),
|
||
USED("已使用"),
|
||
EXPIRED("已过期"),
|
||
REVOKED("已撤销");
|
||
}
|
||
```
|
||
|
||
### 8.2 二维码生成与验证
|
||
|
||
```java
|
||
@Service
|
||
public class VisitorCredentialService {
|
||
|
||
@Autowired
|
||
private VisitorCredentialRepository credentialRepository;
|
||
|
||
public VisitorCredential generateCredential(VisitorCredentialRequest request) {
|
||
VisitorCredential credential = new VisitorCredential();
|
||
credential.setId(UUID.randomUUID());
|
||
credential.setProjectId(request.getProjectId());
|
||
credential.setVisitorName(request.getVisitorName());
|
||
credential.setVisitorPhone(request.getVisitorPhone());
|
||
credential.setVisitorIdCard(request.getVisitorIdCard());
|
||
credential.setSpaceNodeId(request.getSpaceNodeId());
|
||
credential.setAccessGates(JsonUtils.toJson(request.getAccessGates()));
|
||
credential.setStatus(CredentialStatus.ACTIVE);
|
||
credential.setExpireTime(LocalDateTime.now().plusHours(24));
|
||
credential.setUseCount(0);
|
||
credential.setCreatorId(request.getCreatorId());
|
||
credential.setCreatedAt(LocalDateTime.now());
|
||
|
||
String qrContent = generateQrContent(credential);
|
||
credential.setQrCode(qrContent);
|
||
|
||
return credentialRepository.save(credential);
|
||
}
|
||
|
||
private String generateQrContent(VisitorCredential credential) {
|
||
String data = String.format("VC:%s:%d",
|
||
credential.getId(),
|
||
System.currentTimeMillis());
|
||
|
||
String signature = generateSignature(data);
|
||
return data + ":" + signature;
|
||
}
|
||
|
||
public CredentialVerifyResult verifyCredential(String qrCode, String gateCode) {
|
||
String[] parts = qrCode.split(":");
|
||
if (parts.length != 4 || !"VC".equals(parts[0])) {
|
||
return CredentialVerifyResult.fail("无效的二维码格式");
|
||
}
|
||
|
||
UUID credentialId = UUID.fromString(parts[1]);
|
||
|
||
VisitorCredential credential = credentialRepository.findById(credentialId)
|
||
.orElse(null);
|
||
|
||
if (credential == null) {
|
||
return CredentialVerifyResult.fail("凭证不存在");
|
||
}
|
||
|
||
if (credential.getStatus() == CredentialStatus.EXPIRED ||
|
||
credential.getExpireTime().isBefore(LocalDateTime.now())) {
|
||
return CredentialVerifyResult.fail("凭证已过期");
|
||
}
|
||
|
||
if (credential.getStatus() == CredentialStatus.REVOKED) {
|
||
return CredentialVerifyResult.fail("凭证已撤销");
|
||
}
|
||
|
||
List<String> accessGates = JsonUtils.fromJson(credential.getAccessGates(), List.class);
|
||
if (!accessGates.contains(gateCode)) {
|
||
return CredentialVerifyResult.fail("无此门禁通行权限");
|
||
}
|
||
|
||
credential.setUseCount(credential.getUseCount() + 1);
|
||
credential.setLastUseTime(LocalDateTime.now());
|
||
if (credential.getFirstUseTime() == null) {
|
||
credential.setFirstUseTime(LocalDateTime.now());
|
||
}
|
||
credentialRepository.save(credential);
|
||
|
||
return CredentialVerifyResult.success(credential);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 九、操作日志审计
|
||
|
||
### 9.1 操作日志设计
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "auth_operation_log")
|
||
@Data
|
||
public class OperationLog {
|
||
@Id
|
||
private UUID id;
|
||
private UUID projectId;
|
||
|
||
private UUID userId;
|
||
private String username;
|
||
private String realName;
|
||
|
||
private String module;
|
||
private String operation;
|
||
private String description;
|
||
|
||
private String requestMethod;
|
||
private String requestUrl;
|
||
private String requestParams;
|
||
private String requestBody;
|
||
|
||
private Integer responseStatus;
|
||
private String responseBody;
|
||
|
||
private Long executionTime;
|
||
private String ipAddress;
|
||
private String userAgent;
|
||
|
||
private Boolean hasError;
|
||
private String errorMessage;
|
||
|
||
private LocalDateTime createdAt;
|
||
}
|
||
```
|
||
|
||
### 9.2 日志记录切面
|
||
|
||
```java
|
||
@Aspect
|
||
@Component
|
||
@Slf4j
|
||
public class OperationLogAspect {
|
||
|
||
@Autowired
|
||
private OperationLogRepository operationLogRepository;
|
||
|
||
@Around("@annotation(operationLog)")
|
||
public Object around(ProceedingJoinPoint point, LogOperation operationLog) throws Throwable {
|
||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||
|
||
UUID userId = SecurityUtils.getCurrentUserId();
|
||
String username = SecurityUtils.getCurrentUsername();
|
||
|
||
OperationLog logEntry = new OperationLog();
|
||
logEntry.setId(UUID.randomUUID());
|
||
logEntry.setProjectId(ProjectContextHolder.getCurrentProjectId());
|
||
logEntry.setUserId(userId);
|
||
logEntry.setUsername(username);
|
||
logEntry.setModule(operationLog.module());
|
||
logEntry.setOperation(operationLog.operation());
|
||
logEntry.setDescription(operationLog.description());
|
||
logEntry.setRequestMethod(request.getMethod());
|
||
logEntry.setRequestUrl(request.getRequestURI());
|
||
logEntry.setIpAddress(getClientIp(request));
|
||
logEntry.setUserAgent(request.getHeader("User-Agent"));
|
||
logEntry.setCreatedAt(LocalDateTime.now());
|
||
|
||
long startTime = System.currentTimeMillis();
|
||
|
||
try {
|
||
Object result = point.proceed();
|
||
|
||
logEntry.setResponseStatus(200);
|
||
logEntry.setHasError(false);
|
||
|
||
return result;
|
||
} catch (Exception e) {
|
||
logEntry.setHasError(true);
|
||
logEntry.setErrorMessage(e.getMessage());
|
||
throw e;
|
||
} finally {
|
||
logEntry.setExecutionTime(System.currentTimeMillis() - startTime);
|
||
operationLogRepository.save(logEntry);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Target(ElementType.METHOD)
|
||
@Retention(RetentionPolicy.RUNTIME)
|
||
public @interface LogOperation {
|
||
String module();
|
||
String operation();
|
||
String description();
|
||
String businessType() default "";
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十、API 接口
|
||
|
||
### 10.1 认证接口
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/api/v1/auth")
|
||
@Tag(name = "认证管理")
|
||
public class AuthController {
|
||
|
||
@PostMapping("/login")
|
||
@Operation(summary = "用户登录")
|
||
public Result<LoginResponse> login(@RequestBody @Valid LoginRequest request);
|
||
|
||
@PostMapping("/logout")
|
||
@Operation(summary = "用户登出")
|
||
public Result<Void> logout();
|
||
|
||
@PostMapping("/refresh-token")
|
||
@Operation(summary = "刷新Token")
|
||
public Result<TokenResponse> refreshToken(@RequestBody RefreshTokenRequest request);
|
||
|
||
@GetMapping("/current-user")
|
||
@Operation(summary = "获取当前用户信息")
|
||
public Result<UserVO> getCurrentUser();
|
||
|
||
@PostMapping("/change-password")
|
||
@Operation(summary = "修改密码")
|
||
public Result<Void> changePassword(@RequestBody @Valid ChangePasswordRequest request);
|
||
|
||
@GetMapping("/user-permissions")
|
||
@Operation(summary = "获取用户权限信息")
|
||
public Result<UserPermissionsResponse> getUserPermissions();
|
||
}
|
||
```
|
||
|
||
### 10.2 用户管理接口
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/api/v1/auth/users")
|
||
@Tag(name = "用户管理")
|
||
public class UserController {
|
||
|
||
@PostMapping
|
||
@Operation(summary = "创建用户")
|
||
public Result<UserVO> create(@RequestBody @Valid UserCreateRequest request);
|
||
|
||
@GetMapping("/{id}")
|
||
@Operation(summary = "获取用户详情")
|
||
public Result<UserVO> getById(@PathVariable UUID id);
|
||
|
||
@GetMapping
|
||
@Operation(summary = "分页查询用户")
|
||
public Result<Page<UserVO>> page(UserQueryRequest request);
|
||
|
||
@PutMapping("/{id}")
|
||
@Operation(summary = "更新用户")
|
||
public Result<UserVO> update(@PathVariable UUID id, @RequestBody @Valid UserUpdateRequest request);
|
||
|
||
@DeleteMapping("/{id}")
|
||
@Operation(summary = "删除用户")
|
||
public Result<Void> delete(@PathVariable UUID id);
|
||
|
||
@PostMapping("/{id}/roles")
|
||
@Operation(summary = "分配角色")
|
||
public Result<Void> assignRoles(@PathVariable UUID id, @RequestBody UserRoleAssignRequest request);
|
||
|
||
@PostMapping("/{id}/status")
|
||
@Operation(summary = "修改用户状态")
|
||
public Result<Void> changeStatus(@PathVariable UUID id, @RequestBody UserStatusChangeRequest request);
|
||
}
|
||
```
|
||
|
||
### 10.3 角色权限接口
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/api/v1/auth")
|
||
@Tag(name = "角色权限管理")
|
||
public class RolePermissionController {
|
||
|
||
@PostMapping("/roles")
|
||
@Operation(summary = "创建角色")
|
||
public Result<RoleVO> createRole(@RequestBody @Valid RoleCreateRequest request);
|
||
|
||
@GetMapping("/roles")
|
||
@Operation(summary = "查询角色列表")
|
||
public Result<List<RoleVO>> listRoles();
|
||
|
||
@PutMapping("/roles/{id}")
|
||
@Operation(summary = "更新角色")
|
||
public Result<RoleVO> updateRole(@PathVariable UUID id, @RequestBody @Valid RoleUpdateRequest request);
|
||
|
||
@DeleteMapping("/roles/{id}")
|
||
@Operation(summary = "删除角色")
|
||
public Result<Void> deleteRole(@PathVariable UUID id);
|
||
|
||
@PostMapping("/roles/{id}/permissions")
|
||
@Operation(summary = "分配权限")
|
||
public Result<Void> assignPermissions(@PathVariable UUID id, @RequestBody RolePermissionAssignRequest request);
|
||
|
||
@GetMapping("/permissions")
|
||
@Operation(summary = "查询权限列表")
|
||
public Result<List<PermissionVO>> listPermissions();
|
||
|
||
@GetMapping("/permissions/tree")
|
||
@Operation(summary = "获取权限树")
|
||
public Result<List<PermissionTreeVO>> getPermissionTree();
|
||
|
||
@GetMapping("/users/{id}/permissions")
|
||
@Operation(summary = "获取用户权限")
|
||
public Result<List<PermissionVO>> getUserPermissions(@PathVariable UUID id);
|
||
|
||
@GetMapping("/users/{id}/menus")
|
||
@Operation(summary = "获取用户菜单")
|
||
public Result<List<MenuVO>> getUserMenus(@PathVariable UUID id);
|
||
|
||
@PostMapping("/check")
|
||
@Operation(summary = "校验权限")
|
||
public Result<Boolean> checkPermission(@RequestBody PermissionCheckRequest request);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十一、实现状态与差异
|
||
|
||
### 11.1 实现状态
|
||
|
||
| 功能模块 | 实现状态 | 备注 |
|
||
|---------|---------|------|
|
||
| User | 🟢 已实现 | 基础CRUD |
|
||
| Role | 🟢 已实现 | 基础CRUD |
|
||
| Permission | 🟢 已实现 | 基础CRUD |
|
||
| JWT认证 | 🟢 已实现 | Token生成/验证 |
|
||
| 项目上下文 | 🟢 已实现 | 项目隔离 |
|
||
| 访客凭证 | 🟢 已实现 | 二维码生成/验证 |
|
||
| 数据权限 | 🟡 部分实现 | 框架已搭建,需补充PROJECT级别 |
|
||
| 操作日志 | 🟢 已实现 | AOP切面记录 |
|
||
| 按钮级权限 | ⏳ 待实现 | 细粒度操作权限 |
|
||
| 状态驱动权限 | ⏳ 待实现 | 根据业务状态控制操作 |
|
||
| 字段脱敏 | ⏳ 待实现 | 敏感字段脱敏 |
|
||
|
||
### 11.2 改进计划
|
||
|
||
| 优先级 | 改进项 | 说明 |
|
||
|--------|--------|------|
|
||
| P0 | 按钮级权限控制 | 新增操作按钮权限编码 |
|
||
| P0 | 状态驱动权限 | 工单状态与操作联动 |
|
||
| P1 | 数据权限完善 | 补充PROJECT级别 |
|
||
| P1 | 扫码强制机制 | 巡检扫码策略配置 |
|
||
| P2 | 字段脱敏 | 敏感字段脱敏 |
|
||
| P2 | 角色业务属性 | 新增业务类型、终端类型字段 |
|
||
|
||
---
|
||
|
||
## 十二、数据库表结构
|
||
|
||
```sql
|
||
-- 用户表
|
||
CREATE TABLE auth_user (
|
||
id UUID PRIMARY KEY,
|
||
username VARCHAR(50) NOT NULL,
|
||
password VARCHAR(100) NOT NULL,
|
||
salt VARCHAR(20) NOT NULL,
|
||
real_name VARCHAR(100),
|
||
phone VARCHAR(20),
|
||
email VARCHAR(100),
|
||
avatar VARCHAR(255),
|
||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||
last_login_time TIMESTAMP,
|
||
last_login_ip VARCHAR(50),
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
created_by UUID,
|
||
UNIQUE(username)
|
||
);
|
||
|
||
-- 角色表
|
||
CREATE TABLE auth_role (
|
||
id UUID PRIMARY KEY,
|
||
project_id UUID, -- NULL表示系统级角色
|
||
name VARCHAR(100) NOT NULL,
|
||
code VARCHAR(50) NOT NULL,
|
||
description VARCHAR(500),
|
||
type VARCHAR(20) NOT NULL DEFAULT 'CUSTOM',
|
||
data_scope VARCHAR(20) DEFAULT 'SELF',
|
||
business_type VARCHAR(30), -- 新增:业务类型
|
||
terminal_type VARCHAR(20), -- 新增:终端类型
|
||
is_frontline BOOLEAN DEFAULT FALSE, -- 新增:是否一线人员
|
||
enabled BOOLEAN DEFAULT TRUE,
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
UNIQUE(project_id, code)
|
||
);
|
||
|
||
-- 权限表
|
||
CREATE TABLE auth_permission (
|
||
id UUID PRIMARY KEY,
|
||
project_id UUID, -- NULL表示系统级权限
|
||
name VARCHAR(100) NOT NULL,
|
||
code VARCHAR(100) NOT NULL, -- module:resource:action
|
||
type VARCHAR(20) NOT NULL,
|
||
module VARCHAR(50), -- 新增:模块标识
|
||
resource VARCHAR(50), -- 新增:资源类型
|
||
action VARCHAR(30), -- 新增:操作类型
|
||
path VARCHAR(255),
|
||
component VARCHAR(255),
|
||
icon VARCHAR(50),
|
||
sort_order INTEGER DEFAULT 0,
|
||
parent_id UUID,
|
||
level INTEGER DEFAULT 0,
|
||
api_method VARCHAR(10),
|
||
api_path VARCHAR(255),
|
||
enabled BOOLEAN DEFAULT TRUE,
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
UNIQUE(code, project_id)
|
||
);
|
||
|
||
-- 用户角色关联表
|
||
CREATE TABLE auth_user_role (
|
||
id UUID PRIMARY KEY,
|
||
user_id UUID NOT NULL,
|
||
role_id UUID NOT NULL,
|
||
project_id UUID,
|
||
is_default BOOLEAN DEFAULT FALSE,
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
UNIQUE(user_id, role_id, project_id)
|
||
);
|
||
|
||
-- 角色权限关联表
|
||
CREATE TABLE auth_role_permission (
|
||
id UUID PRIMARY KEY,
|
||
role_id UUID NOT NULL,
|
||
permission_id UUID NOT NULL,
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
UNIQUE(role_id, permission_id)
|
||
);
|
||
|
||
-- 项目表
|
||
CREATE TABLE auth_project (
|
||
id UUID PRIMARY KEY,
|
||
name VARCHAR(100) NOT NULL,
|
||
code VARCHAR(50) NOT NULL,
|
||
description VARCHAR(500),
|
||
province VARCHAR(50),
|
||
city VARCHAR(50),
|
||
district VARCHAR(50),
|
||
address VARCHAR(255),
|
||
manager_id UUID,
|
||
manager_name VARCHAR(100),
|
||
manager_phone VARCHAR(20),
|
||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||
start_date DATE,
|
||
end_date DATE,
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
UNIQUE(code)
|
||
);
|
||
|
||
-- 访客凭证表
|
||
CREATE TABLE auth_visitor_credential (
|
||
id UUID PRIMARY KEY,
|
||
project_id UUID NOT NULL,
|
||
visitor_name VARCHAR(100) NOT NULL,
|
||
visitor_phone VARCHAR(20),
|
||
visitor_id_card VARCHAR(18),
|
||
credential_code VARCHAR(50) NOT NULL,
|
||
qr_code TEXT NOT NULL,
|
||
expire_time TIMESTAMP NOT NULL,
|
||
space_node_id UUID,
|
||
access_gates JSONB,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||
first_use_time TIMESTAMP,
|
||
last_use_time TIMESTAMP,
|
||
use_count INTEGER DEFAULT 0,
|
||
creator_id UUID,
|
||
creator_name VARCHAR(100),
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
UNIQUE(credential_code)
|
||
);
|
||
|
||
-- 操作日志表
|
||
CREATE TABLE auth_operation_log (
|
||
id UUID PRIMARY KEY,
|
||
project_id UUID,
|
||
user_id UUID,
|
||
username VARCHAR(50),
|
||
real_name VARCHAR(100),
|
||
module VARCHAR(50) NOT NULL,
|
||
operation VARCHAR(50) NOT NULL,
|
||
description VARCHAR(255),
|
||
request_method VARCHAR(10),
|
||
request_url VARCHAR(255),
|
||
request_params TEXT,
|
||
request_body TEXT,
|
||
response_status INTEGER,
|
||
response_body TEXT,
|
||
execution_time BIGINT,
|
||
ip_address VARCHAR(50),
|
||
user_agent TEXT,
|
||
has_error BOOLEAN DEFAULT FALSE,
|
||
error_message TEXT,
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
-- 创建索引
|
||
CREATE INDEX idx_user_username ON auth_user(username);
|
||
CREATE INDEX idx_user_status ON auth_user(status);
|
||
CREATE INDEX idx_role_project ON auth_role(project_id);
|
||
CREATE INDEX idx_permission_parent ON auth_permission(parent_id);
|
||
CREATE INDEX idx_permission_module ON auth_permission(module);
|
||
CREATE INDEX idx_user_role_user ON auth_user_role(user_id);
|
||
CREATE INDEX idx_user_role_role ON auth_user_role(role_id);
|
||
CREATE INDEX idx_role_permission_role ON auth_role_permission(role_id);
|
||
CREATE INDEX idx_credential_project ON auth_visitor_credential(project_id);
|
||
CREATE INDEX idx_credential_status ON auth_visitor_credential(status);
|
||
CREATE INDEX idx_operation_log_user ON auth_operation_log(user_id);
|
||
CREATE INDEX idx_operation_log_created ON auth_operation_log(created_at);
|
||
```
|
||
|
||
---
|
||
|
||
## 十三、更新记录
|
||
|
||
| 日期 | 更新内容 | 更新人 |
|
||
|------|----------|--------|
|
||
| 2026-02-10 | 创建权限与账户领域技术方案 | - |
|
||
| 2026-02-27 | 重构角色体系,新增13个角色 | - |
|
||
| 2026-02-27 | 新增权限编码规范 | - |
|
||
| 2026-02-27 | 新增数据权限四级范围 | - |
|
||
| 2026-02-27 | 新增状态驱动权限设计 | - |
|
||
| 2026-02-27 | 新增角色业务属性字段 | - |
|
||
|
||
---
|
||
|
||
**文档维护**: 本领域技术方案由 ether-auth 服务负责人维护
|