diff --git a/pom.xml b/pom.xml
index 793045e..0b73e85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -125,6 +125,20 @@
2.9.1
+
+
+ cn.dev33
+ sa-token-spring-boot3-starter
+ 1.39.0
+
+
+
+
+ cn.dev33
+ sa-token-redis-jackson
+ 1.39.0
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/cn/yinlihupo/common/annotation/RequireProjectPermission.java b/src/main/java/cn/yinlihupo/common/annotation/RequireProjectPermission.java
new file mode 100644
index 0000000..6855323
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/annotation/RequireProjectPermission.java
@@ -0,0 +1,30 @@
+package cn.yinlihupo.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 项目权限校验注解
+ * 用于标注需要校验项目权限的方法
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequireProjectPermission {
+
+ /**
+ * 项目ID参数名
+ * 默认从方法参数中获取名为"projectId"的参数值
+ */
+ String projectIdParam() default "projectId";
+
+ /**
+ * 需要的权限编码
+ * 例如: "risk:manage", "task:assign"
+ */
+ String value();
+
+ /**
+ * 错误提示信息
+ */
+ String message() default "无权访问该项目资源";
+}
diff --git a/src/main/java/cn/yinlihupo/common/annotation/RequireProjectRole.java b/src/main/java/cn/yinlihupo/common/annotation/RequireProjectRole.java
new file mode 100644
index 0000000..0ef095c
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/annotation/RequireProjectRole.java
@@ -0,0 +1,36 @@
+package cn.yinlihupo.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 项目角色校验注解
+ * 用于标注需要校验项目角色的方法
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequireProjectRole {
+
+ /**
+ * 项目ID参数名
+ * 默认从方法参数中获取名为"projectId"的参数值
+ */
+ String projectIdParam() default "projectId";
+
+ /**
+ * 需要的项目角色
+ * 可选值: manager, leader, member, observer
+ */
+ String[] value();
+
+ /**
+ * 是否允许系统管理员绕过角色校验
+ * 默认true,管理员拥有所有权限
+ */
+ boolean allowAdmin() default true;
+
+ /**
+ * 错误提示信息
+ */
+ String message() default "无权执行该操作";
+}
diff --git a/src/main/java/cn/yinlihupo/common/aspect/ProjectPermissionAspect.java b/src/main/java/cn/yinlihupo/common/aspect/ProjectPermissionAspect.java
new file mode 100644
index 0000000..ccc7297
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/aspect/ProjectPermissionAspect.java
@@ -0,0 +1,162 @@
+package cn.yinlihupo.common.aspect;
+
+import cn.yinlihupo.common.annotation.RequireProjectPermission;
+import cn.yinlihupo.common.annotation.RequireProjectRole;
+import cn.yinlihupo.common.exception.BusinessException;
+import cn.yinlihupo.common.util.SecurityUtils;
+import cn.yinlihupo.service.system.ProjectPermissionService;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+/**
+ * 项目权限校验切面
+ */
+@Slf4j
+@Aspect
+@Component
+@RequiredArgsConstructor
+public class ProjectPermissionAspect {
+
+ private final ProjectPermissionService projectPermissionService;
+
+ /**
+ * 校验项目权限
+ */
+ @Before("@annotation(cn.yinlihupo.common.annotation.RequireProjectPermission)")
+ public void checkProjectPermission(JoinPoint joinPoint) {
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ Method method = signature.getMethod();
+ RequireProjectPermission annotation = method.getAnnotation(RequireProjectPermission.class);
+
+ Long userId = SecurityUtils.getCurrentUserId();
+ if (userId == null) {
+ throw new BusinessException(403, "用户未登录");
+ }
+
+ // 管理员直接放行
+ if (projectPermissionService.isAdmin(userId)) {
+ return;
+ }
+
+ // 获取项目ID
+ Long projectId = extractProjectId(joinPoint, annotation.projectIdParam());
+ if (projectId == null) {
+ throw new BusinessException(400, "无法获取项目ID");
+ }
+
+ // 校验权限
+ String requiredPermission = annotation.value();
+ boolean hasPermission = projectPermissionService.hasPermission(userId, projectId, requiredPermission);
+
+ if (!hasPermission) {
+ log.warn("用户 [{}] 没有项目 [{}] 的权限 [{}]", userId, projectId, requiredPermission);
+ throw new BusinessException(403, annotation.message());
+ }
+ }
+
+ /**
+ * 校验项目角色
+ */
+ @Before("@annotation(cn.yinlihupo.common.annotation.RequireProjectRole)")
+ public void checkProjectRole(JoinPoint joinPoint) {
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ Method method = signature.getMethod();
+ RequireProjectRole annotation = method.getAnnotation(RequireProjectRole.class);
+
+ Long userId = SecurityUtils.getCurrentUserId();
+ if (userId == null) {
+ throw new BusinessException(403, "用户未登录");
+ }
+
+ // 管理员直接放行(如果允许)
+ if (annotation.allowAdmin() && projectPermissionService.isAdmin(userId)) {
+ return;
+ }
+
+ // 获取项目ID
+ Long projectId = extractProjectId(joinPoint, annotation.projectIdParam());
+ if (projectId == null) {
+ throw new BusinessException(400, "无法获取项目ID");
+ }
+
+ // 获取用户项目角色
+ String userRole = projectPermissionService.getUserProjectRole(userId, projectId);
+ if (userRole == null) {
+ throw new BusinessException(403, "您不是该项目的成员");
+ }
+
+ // 校验角色
+ String[] requiredRoles = annotation.value();
+ boolean hasRole = false;
+ for (String requiredRole : requiredRoles) {
+ if (userRole.equals(requiredRole)) {
+ hasRole = true;
+ break;
+ }
+ }
+
+ if (!hasRole) {
+ log.warn("用户 [{}] 在项目 [{}] 中的角色 [{}] 不符合要求 {}",
+ userId, projectId, userRole, requiredRoles);
+ throw new BusinessException(403, annotation.message());
+ }
+ }
+
+ /**
+ * 从方法参数中提取项目ID
+ */
+ private Long extractProjectId(JoinPoint joinPoint, String paramName) {
+ Object[] args = joinPoint.getArgs();
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ Parameter[] parameters = signature.getMethod().getParameters();
+
+ // 1. 先从方法参数中查找
+ for (int i = 0; i < parameters.length; i++) {
+ if (parameters[i].getName().equals(paramName) && args[i] instanceof Long) {
+ return (Long) args[i];
+ }
+ }
+
+ // 2. 尝试从请求参数中获取
+ try {
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ if (attributes != null) {
+ HttpServletRequest request = attributes.getRequest();
+ String projectIdStr = request.getParameter(paramName);
+ if (projectIdStr != null && !projectIdStr.isEmpty()) {
+ return Long.valueOf(projectIdStr);
+ }
+
+ // 尝试从路径变量中获取
+ Object pathVar = request.getAttribute(paramName);
+ if (pathVar instanceof Long) {
+ return (Long) pathVar;
+ }
+ }
+ } catch (Exception e) {
+ log.debug("从请求中获取项目ID失败: {}", e.getMessage());
+ }
+
+ // 3. 查找任何名为projectId或包含projectId的参数
+ for (int i = 0; i < parameters.length; i++) {
+ String paramNameLower = parameters[i].getName().toLowerCase();
+ if ((paramNameLower.contains("projectid") || paramNameLower.contains("project_id"))
+ && args[i] instanceof Long) {
+ return (Long) args[i];
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/common/config/SaTokenConfig.java b/src/main/java/cn/yinlihupo/common/config/SaTokenConfig.java
new file mode 100644
index 0000000..f0b712b
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/config/SaTokenConfig.java
@@ -0,0 +1,71 @@
+package cn.yinlihupo.common.config;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.filter.SaServletFilter;
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.router.SaRouter;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.yinlihupo.common.core.BaseResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Sa-Token 配置类
+ */
+@Slf4j
+@Configuration
+public class SaTokenConfig implements WebMvcConfigurer {
+
+ /**
+ * 注册 Sa-Token 拦截器,打开注解式鉴权功能
+ */
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验
+ registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
+ .addPathPatterns("/**")
+ .excludePathPatterns(
+ "/auth/login",
+ "/auth/register",
+ "/error",
+ "/swagger-ui/**",
+ "/v3/api-docs/**"
+ );
+ }
+
+ /**
+ * 注册 [Sa-Token 全局过滤器]
+ */
+ @Bean
+ public SaServletFilter getSaServletFilter() {
+ return new SaServletFilter()
+ // 指定 [拦截路由] 与 [放行路由]
+ .addInclude("/**")
+ .addExclude("/favicon.ico", "/swagger-ui/**", "/v3/api-docs/**")
+ // 认证函数: 每次请求执行
+ .setAuth(obj -> {
+ log.debug("---------- 进入 Sa-Token 全局认证 -----------");
+ })
+ // 异常处理函数:每次认证函数发生异常时执行
+ .setError(e -> {
+ log.error("Sa-Token 认证异常: {}", e.getMessage());
+ return new BaseResponse<>(403, e.getMessage(), null);
+ })
+ // 前置函数:在每次认证函数之前执行
+ .setBeforeAuth(obj -> {
+ // 设置跨域响应头
+ SaHolder.getResponse()
+ .setHeader("Access-Control-Allow-Origin", "*")
+ .setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+ .setHeader("Access-Control-Allow-Headers", "*")
+ .setHeader("Access-Control-Max-Age", "3600");
+ // 如果是预检请求,则立即返回到前端
+ SaRouter.match("OPTIONS")
+ .free(r -> log.debug("--------OPTIONS预检请求,不做处理"))
+ .back();
+ });
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/common/config/SaTokenPermissionExtension.java b/src/main/java/cn/yinlihupo/common/config/SaTokenPermissionExtension.java
new file mode 100644
index 0000000..a5a35bd
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/config/SaTokenPermissionExtension.java
@@ -0,0 +1,67 @@
+package cn.yinlihupo.common.config;
+
+import cn.dev33.satoken.stp.StpInterface;
+import cn.yinlihupo.mapper.SysPermissionMapper;
+import cn.yinlihupo.mapper.SysUserMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Sa-Token 权限认证接口扩展
+ * 实现 StpInterface 接口,自定义权限和角色获取逻辑
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SaTokenPermissionExtension implements StpInterface {
+
+ private final SysUserMapper sysUserMapper;
+ private final SysPermissionMapper sysPermissionMapper;
+
+ /**
+ * 返回指定账号id所拥有的权限码集合
+ *
+ * @param loginId 账号id
+ * @param loginType 账号类型
+ * @return 权限码集合
+ */
+ @Override
+ public List getPermissionList(Object loginId, String loginType) {
+ // 项目权限通过 ProjectPermissionService 动态校验
+ // 这里返回系统级别的权限
+ try {
+ Long userId = Long.valueOf(loginId.toString());
+ List roleCodes = sysUserMapper.selectRoleCodesByUserId(userId);
+ if (roleCodes.isEmpty()) {
+ return new ArrayList<>();
+ }
+ // 根据角色查询权限
+ return sysPermissionMapper.selectPermissionCodesByRoleCodes(roleCodes);
+ } catch (Exception e) {
+ log.error("获取用户权限列表失败: {}", e.getMessage());
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * 返回指定账号id所拥有的角色标识集合
+ *
+ * @param loginId 账号id
+ * @param loginType 账号类型
+ * @return 角色标识集合
+ */
+ @Override
+ public List getRoleList(Object loginId, String loginType) {
+ try {
+ Long userId = Long.valueOf(loginId.toString());
+ return sysUserMapper.selectRoleCodesByUserId(userId);
+ } catch (Exception e) {
+ log.error("获取用户角色列表失败: {}", e.getMessage());
+ return new ArrayList<>();
+ }
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/common/constant/PermissionConstants.java b/src/main/java/cn/yinlihupo/common/constant/PermissionConstants.java
new file mode 100644
index 0000000..f7dedb5
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/constant/PermissionConstants.java
@@ -0,0 +1,79 @@
+package cn.yinlihupo.common.constant;
+
+/**
+ * 权限常量类
+ * 定义系统角色、项目角色和权限编码
+ */
+public class PermissionConstants {
+
+ // ==================== 系统角色 ====================
+ /** 系统管理员 */
+ public static final String ROLE_ADMIN = "admin";
+ /** 项目经理 */
+ public static final String ROLE_PROJECT_MANAGER = "project_manager";
+ /** 团队负责人 */
+ public static final String ROLE_TEAM_LEADER = "team_leader";
+ /** 普通成员 */
+ public static final String ROLE_MEMBER = "member";
+
+ // ==================== 项目角色 ====================
+ /** 项目经理 - 拥有项目全部权限 */
+ public static final String PROJECT_ROLE_MANAGER = "manager";
+ /** 负责人 - 协助管理项目 */
+ public static final String PROJECT_ROLE_LEADER = "leader";
+ /** 普通成员 */
+ public static final String PROJECT_ROLE_MEMBER = "member";
+ /** 观察者 - 只读权限 */
+ public static final String PROJECT_ROLE_OBSERVER = "observer";
+
+ // ==================== 项目权限编码 ====================
+ // 项目查看权限
+ public static final String PERM_PROJECT_VIEW = "project:view";
+ // 项目管理权限(编辑、删除)
+ public static final String PERM_PROJECT_MANAGE = "project:manage";
+
+ // 风险查看权限
+ public static final String PERM_RISK_VIEW = "risk:view";
+ // 风险管控权限(创建、编辑、删除)
+ public static final String PERM_RISK_MANAGE = "risk:manage";
+
+ // 资源查看权限
+ public static final String PERM_RESOURCE_VIEW = "resource:view";
+ // 资源管控权限(申请、审批、分配)
+ public static final String PERM_RESOURCE_MANAGE = "resource:manage";
+
+ // 里程碑查看权限
+ public static final String PERM_MILESTONE_VIEW = "milestone:view";
+ // 里程碑推进权限
+ public static final String PERM_MILESTONE_PUSH = "milestone:push";
+
+ // 任务工单查看权限
+ public static final String PERM_TASK_VIEW = "task:view";
+ // 任务工单分配权限
+ public static final String PERM_TASK_ASSIGN = "task:assign";
+ // 任务工单处理权限
+ public static final String PERM_TASK_PROCESS = "task:process";
+
+ // 日报查看权限
+ public static final String PERM_REPORT_VIEW = "report:view";
+ // 日报上传权限
+ public static final String PERM_REPORT_CREATE = "report:create";
+
+ // 知识库查看权限
+ public static final String PERM_KB_VIEW = "kb:view";
+ // 知识库上传权限
+ public static final String PERM_KB_CREATE = "kb:create";
+
+ // ==================== 数据权限范围 ====================
+ /** 全部数据 */
+ public static final int DATA_SCOPE_ALL = 1;
+ /** 本部门数据 */
+ public static final int DATA_SCOPE_DEPT = 2;
+ /** 本人数据 */
+ public static final int DATA_SCOPE_SELF = 3;
+ /** 本项目数据 */
+ public static final int DATA_SCOPE_PROJECT = 4;
+
+ private PermissionConstants() {
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/common/util/SecurityUtils.java b/src/main/java/cn/yinlihupo/common/util/SecurityUtils.java
new file mode 100644
index 0000000..6873fb7
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/util/SecurityUtils.java
@@ -0,0 +1,125 @@
+package cn.yinlihupo.common.util;
+
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.yinlihupo.common.constant.PermissionConstants;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 安全工具类 (Sa-Token 版本)
+ * 提供获取当前用户、权限校验等方法
+ */
+public class SecurityUtils {
+
+ /**
+ * 获取当前登录用户ID
+ */
+ public static Long getCurrentUserId() {
+ try {
+ Object loginId = StpUtil.getLoginIdDefaultNull();
+ if (loginId == null) {
+ return null;
+ }
+ return Long.valueOf(loginId.toString());
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取当前用户角色列表
+ */
+ @SuppressWarnings("unchecked")
+ public static Set getCurrentUserRoles() {
+ try {
+ SaSession session = StpUtil.getSession();
+ Object roles = session.get("roles");
+ if (roles instanceof Set) {
+ return (Set) roles;
+ }
+ if (roles instanceof List) {
+ return Set.copyOf((List) roles);
+ }
+ } catch (Exception e) {
+ // 未登录或会话不存在
+ }
+ return Collections.emptySet();
+ }
+
+ /**
+ * 判断当前用户是否为系统管理员
+ */
+ public static boolean isAdmin() {
+ return hasRole(PermissionConstants.ROLE_ADMIN);
+ }
+
+ /**
+ * 判断当前用户是否拥有指定角色
+ */
+ public static boolean hasRole(String role) {
+ return StpUtil.hasRole(role);
+ }
+
+ /**
+ * 判断当前用户是否拥有任意一个指定角色
+ */
+ public static boolean hasAnyRole(String... roles) {
+ for (String role : roles) {
+ if (StpUtil.hasRole(role)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 判断当前用户是否拥有指定权限
+ */
+ public static boolean hasPermission(String permission) {
+ return StpUtil.hasPermission(permission);
+ }
+
+ /**
+ * 判断当前用户是否拥有任意一个指定权限
+ */
+ public static boolean hasAnyPermission(String... permissions) {
+ for (String permission : permissions) {
+ if (StpUtil.hasPermission(permission)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 登录并缓存用户角色
+ *
+ * @param userId 用户ID
+ * @param roles 角色列表
+ */
+ public static void login(Long userId, Set roles) {
+ StpUtil.login(userId);
+ SaSession session = StpUtil.getSession();
+ session.set("roles", roles);
+ }
+
+ /**
+ * 登出
+ */
+ public static void logout() {
+ StpUtil.logout();
+ }
+
+ /**
+ * 获取当前登录Token
+ */
+ public static String getTokenValue() {
+ return StpUtil.getTokenValue();
+ }
+
+ private SecurityUtils() {
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysPermission.java b/src/main/java/cn/yinlihupo/domain/entity/SysPermission.java
new file mode 100644
index 0000000..41ed096
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/domain/entity/SysPermission.java
@@ -0,0 +1,66 @@
+package cn.yinlihupo.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 系统权限实体类
+ * 对应数据库表: sys_permission
+ */
+@Data
+@TableName("sys_permission")
+public class SysPermission {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /** 父级权限ID */
+ private Long parentId;
+
+ /** 权限编码 */
+ private String permissionCode;
+
+ /** 权限名称 */
+ private String permissionName;
+
+ /** 权限类型: 1-菜单, 2-按钮, 3-接口 */
+ private Integer permissionType;
+
+ /** 路由路径 */
+ private String path;
+
+ /** 组件路径 */
+ private String component;
+
+ /** 图标 */
+ private String icon;
+
+ /** 接口URL */
+ private String apiUrl;
+
+ /** 接口方法 */
+ private String apiMethod;
+
+ /** 排序 */
+ private Integer sortOrder;
+
+ /** 是否可见: 1-是, 0-否 */
+ private Integer visible;
+
+ /** 状态: 1-正常, 0-禁用 */
+ private Integer status;
+
+ /** 创建时间 */
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createTime;
+
+ /** 更新时间 */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private LocalDateTime updateTime;
+
+ /** 删除标记 */
+ @TableLogic
+ private Integer deleted;
+}
diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysRole.java b/src/main/java/cn/yinlihupo/domain/entity/SysRole.java
new file mode 100644
index 0000000..53ddf8b
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/domain/entity/SysRole.java
@@ -0,0 +1,57 @@
+package cn.yinlihupo.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 系统角色实体类
+ * 对应数据库表: sys_role
+ */
+@Data
+@TableName("sys_role")
+public class SysRole {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /** 角色编码 */
+ private String roleCode;
+
+ /** 角色名称 */
+ private String roleName;
+
+ /** 角色类型: system-系统角色, custom-自定义角色 */
+ private String roleType;
+
+ /** 角色描述 */
+ private String description;
+
+ /** 数据权限: 1-全部, 2-本部门, 3-本人 */
+ private Integer dataScope;
+
+ /** 排序 */
+ private Integer sortOrder;
+
+ /** 状态: 1-正常, 0-禁用 */
+ private Integer status;
+
+ /** 创建人 */
+ private Long createBy;
+
+ /** 创建时间 */
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createTime;
+
+ /** 更新人 */
+ private Long updateBy;
+
+ /** 更新时间 */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private LocalDateTime updateTime;
+
+ /** 删除标记 */
+ @TableLogic
+ private Integer deleted;
+}
diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysRolePermission.java b/src/main/java/cn/yinlihupo/domain/entity/SysRolePermission.java
new file mode 100644
index 0000000..631ddc2
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/domain/entity/SysRolePermission.java
@@ -0,0 +1,29 @@
+package cn.yinlihupo.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 角色权限关联实体类
+ * 对应数据库表: sys_role_permission
+ */
+@Data
+@TableName("sys_role_permission")
+public class SysRolePermission {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /** 角色ID */
+ private Long roleId;
+
+ /** 权限ID */
+ private Long permissionId;
+
+ /** 创建时间 */
+ private LocalDateTime createTime;
+}
diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysUser.java b/src/main/java/cn/yinlihupo/domain/entity/SysUser.java
new file mode 100644
index 0000000..8dc2099
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/domain/entity/SysUser.java
@@ -0,0 +1,82 @@
+package cn.yinlihupo.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 系统用户实体类
+ * 对应数据库表: sys_user
+ */
+@Data
+@TableName("sys_user")
+public class SysUser {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /** 用户名 */
+ private String username;
+
+ /** 密码(加密) */
+ private String password;
+
+ /** 真实姓名 */
+ private String realName;
+
+ /** 昵称 */
+ private String nickname;
+
+ /** 头像URL */
+ private String avatar;
+
+ /** 性别: 0-未知, 1-男, 2-女 */
+ private Integer gender;
+
+ /** 手机号 */
+ private String phone;
+
+ /** 邮箱 */
+ private String email;
+
+ /** 所属部门ID */
+ private Long deptId;
+
+ /** 职位 */
+ private String position;
+
+ /** 工号 */
+ private String employeeNo;
+
+ /** 入职日期 */
+ private LocalDate entryDate;
+
+ /** 状态: 1-正常, 0-禁用, 2-锁定 */
+ private Integer status;
+
+ /** 最后登录时间 */
+ private LocalDateTime lastLoginTime;
+
+ /** 最后登录IP */
+ private String lastLoginIp;
+
+ /** 创建人 */
+ private Long createBy;
+
+ /** 创建时间 */
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createTime;
+
+ /** 更新人 */
+ private Long updateBy;
+
+ /** 更新时间 */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private LocalDateTime updateTime;
+
+ /** 删除标记 */
+ @TableLogic
+ private Integer deleted;
+}
diff --git a/src/main/java/cn/yinlihupo/domain/entity/SysUserRole.java b/src/main/java/cn/yinlihupo/domain/entity/SysUserRole.java
new file mode 100644
index 0000000..4282dce
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/domain/entity/SysUserRole.java
@@ -0,0 +1,29 @@
+package cn.yinlihupo.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 用户角色关联实体类
+ * 对应数据库表: sys_user_role
+ */
+@Data
+@TableName("sys_user_role")
+public class SysUserRole {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /** 用户ID */
+ private Long userId;
+
+ /** 角色ID */
+ private Long roleId;
+
+ /** 创建时间 */
+ private LocalDateTime createTime;
+}
diff --git a/src/main/java/cn/yinlihupo/mapper/FileAttachmentMapper.java b/src/main/java/cn/yinlihupo/mapper/FileAttachmentMapper.java
index bc5b25b..69f9ab3 100644
--- a/src/main/java/cn/yinlihupo/mapper/FileAttachmentMapper.java
+++ b/src/main/java/cn/yinlihupo/mapper/FileAttachmentMapper.java
@@ -3,10 +3,31 @@ package cn.yinlihupo.mapper;
import cn.yinlihupo.domain.entity.FileAttachment;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
/**
* 文件附件Mapper接口
*/
@Mapper
public interface FileAttachmentMapper extends BaseMapper {
+
+ /**
+ * 查询某业务实体的附件列表(含上传者信息)
+ */
+ List