diff --git a/docs/dev-ops/pgsql/sql/init_permissions.sql b/docs/dev-ops/pgsql/sql/init_permissions.sql new file mode 100644 index 0000000..8046ca7 --- /dev/null +++ b/docs/dev-ops/pgsql/sql/init_permissions.sql @@ -0,0 +1,145 @@ +-- ============================================ +-- 初始化系统权限数据 +-- ============================================ + +-- 清空现有权限数据(谨慎使用) +-- TRUNCATE TABLE sys_role_permission CASCADE; +-- TRUNCATE TABLE sys_permission CASCADE; + +-- ============================================ +-- 1. 插入菜单权限 +-- ============================================ + +-- 系统管理菜单(parent_id 为 NULL 表示顶级菜单) +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, path, component, icon, sort_order, visible, status, create_time, update_time, deleted) +VALUES (1, NULL, 'system:manage', '系统管理', 1, '/system', 'Layout', 'Setting', 1, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- 用户管理菜单 +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, path, component, icon, sort_order, visible, status, create_time, update_time, deleted) +VALUES (2, 1, 'system:user', '用户管理', 1, '/system/user', 'system/user/index', 'User', 1, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- 角色管理菜单 +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, path, component, icon, sort_order, visible, status, create_time, update_time, deleted) +VALUES (3, 1, 'system:role', '角色管理', 1, '/system/role', 'system/role/index', 'Role', 2, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- 权限管理菜单 +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, path, component, icon, sort_order, visible, status, create_time, update_time, deleted) +VALUES (4, 1, 'system:permission', '权限管理', 1, '/system/permission', 'system/permission/index', 'Lock', 3, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- ============================================ +-- 2. 插入用户管理相关按钮权限 +-- ============================================ + +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, api_url, api_method, sort_order, visible, status, create_time, update_time, deleted) +VALUES +(101, 2, 'system:user:list', '用户列表', 2, '/api/v1/system/user/list', 'GET', 1, 1, 1, NOW(), NOW(), 0), +(102, 2, 'system:user:view', '查看用户', 2, '/api/v1/system/user/*', 'GET', 2, 1, 1, NOW(), NOW(), 0), +(103, 2, 'system:user:create', '新增用户', 2, '/api/v1/system/user', 'POST', 3, 1, 1, NOW(), NOW(), 0), +(104, 2, 'system:user:update', '修改用户', 2, '/api/v1/system/user', 'PUT', 4, 1, 1, NOW(), NOW(), 0), +(105, 2, 'system:user:delete', '删除用户', 2, '/api/v1/system/user/*', 'DELETE', 5, 1, 1, NOW(), NOW(), 0), +(106, 2, 'system:user:bindRole', '绑定角色', 2, '/api/v1/system/user/*/roles', 'POST', 6, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- ============================================ +-- 3. 插入角色管理相关按钮权限 +-- ============================================ + +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, api_url, api_method, sort_order, visible, status, create_time, update_time, deleted) +VALUES +(201, 3, 'system:role:list', '角色列表', 2, '/api/v1/system/role/list', 'GET', 1, 1, 1, NOW(), NOW(), 0), +(202, 3, 'system:role:view', '查看角色', 2, '/api/v1/system/role/*', 'GET', 2, 1, 1, NOW(), NOW(), 0), +(203, 3, 'system:role:create', '新增角色', 2, '/api/v1/system/role', 'POST', 3, 1, 1, NOW(), NOW(), 0), +(204, 3, 'system:role:update', '修改角色', 2, '/api/v1/system/role', 'PUT', 4, 1, 1, NOW(), NOW(), 0), +(205, 3, 'system:role:delete', '删除角色', 2, '/api/v1/system/role/*', 'DELETE', 5, 1, 1, NOW(), NOW(), 0), +(206, 3, 'system:role:bindPermission', '分配权限', 2, '/api/v1/system/role/*/permissions', 'POST', 6, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- ============================================ +-- 4. 插入权限管理相关按钮权限 +-- ============================================ + +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, api_url, api_method, sort_order, visible, status, create_time, update_time, deleted) +VALUES +(301, 4, 'system:permission:list', '权限列表', 2, '/api/v1/system/permission/list', 'GET', 1, 1, 1, NOW(), NOW(), 0), +(302, 4, 'system:permission:view', '查看权限', 2, '/api/v1/system/permission/*', 'GET', 2, 1, 1, NOW(), NOW(), 0), +(303, 4, 'system:permission:create', '新增权限', 2, '/api/v1/system/permission', 'POST', 3, 1, 1, NOW(), NOW(), 0), +(304, 4, 'system:permission:update', '修改权限', 2, '/api/v1/system/permission', 'PUT', 4, 1, 1, NOW(), NOW(), 0), +(305, 4, 'system:permission:delete', '删除权限', 2, '/api/v1/system/permission/*', 'DELETE', 5, 1, 1, NOW(), NOW(), 0), +(306, 4, 'system:permission:tree', '权限树', 2, '/api/v1/system/permission/tree', 'GET', 6, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- ============================================ +-- 5. 插入项目管理相关权限(三级结构) +-- ============================================ + +-- 一级:项目管理菜单(parent_id 为 NULL 表示顶级菜单) +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, path, component, icon, sort_order, visible, status, create_time, update_time, deleted) +VALUES (10, NULL, 'project:manage', '项目管理', 1, '/project', 'Layout', 'Project', 2, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- 二级:项目中心菜单 +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, path, component, icon, sort_order, visible, status, create_time, update_time, deleted) +VALUES (11, 10, 'project:center', '项目中心', 1, '/project/center', 'project/center/index', 'Folder', 1, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- 三级:项目中心按钮权限 +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, api_url, api_method, sort_order, visible, status, create_time, update_time, deleted) +VALUES +(1001, 11, 'project:center:list', '项目列表', 2, '/api/v1/project/list', 'GET', 1, 1, 1, NOW(), NOW(), 0), +(1002, 11, 'project:center:view', '查看项目', 2, '/api/v1/project/*', 'GET', 2, 1, 1, NOW(), NOW(), 0), +(1003, 11, 'project:center:create', '创建项目', 2, '/api/v1/project', 'POST', 3, 1, 1, NOW(), NOW(), 0), +(1004, 11, 'project:center:update', '编辑项目', 2, '/api/v1/project', 'PUT', 4, 1, 1, NOW(), NOW(), 0), +(1005, 11, 'project:center:delete', '删除项目', 2, '/api/v1/project/*', 'DELETE', 5, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- 二级:我的项目菜单 +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, path, component, icon, sort_order, visible, status, create_time, update_time, deleted) +VALUES (12, 10, 'project:my', '我的项目', 1, '/project/my', 'project/my/index', 'User', 2, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- 三级:我的项目按钮权限 +INSERT INTO sys_permission (id, parent_id, permission_code, permission_name, permission_type, api_url, api_method, sort_order, visible, status, create_time, update_time, deleted) +VALUES +(1101, 12, 'project:my:list', '我的项目列表', 2, '/api/v1/project/my/list', 'GET', 1, 1, 1, NOW(), NOW(), 0), +(1102, 12, 'project:my:view', '查看我的项目', 2, '/api/v1/project/my/*', 'GET', 2, 1, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET permission_code = EXCLUDED.permission_code, permission_name = EXCLUDED.permission_name; + +-- ============================================ +-- 6. 插入系统角色 +-- ============================================ + +INSERT INTO sys_role (id, role_code, role_name, role_type, description, data_scope, sort_order, status, create_time, update_time, deleted) +VALUES +(1, 'admin', '系统管理员', 'system', '拥有系统所有权限', 1, 1, 1, NOW(), NOW(), 0), +(2, 'project_manager', '项目经理', 'system', '负责项目管理', 4, 2, 1, NOW(), NOW(), 0), +(3, 'team_leader', '团队负责人', 'system', '负责团队管理', 4, 3, 1, NOW(), NOW(), 0), +(4, 'member', '普通成员', 'system', '普通项目成员', 3, 4, 1, NOW(), NOW(), 0) +ON CONFLICT (id) DO UPDATE SET role_code = EXCLUDED.role_code, role_name = EXCLUDED.role_name; + +-- ============================================ +-- 7. 给admin角色分配所有权限 +-- ============================================ + +INSERT INTO sys_role_permission (role_id, permission_id, create_time) +SELECT 1, id, NOW() FROM sys_permission WHERE deleted = 0 +ON CONFLICT DO NOTHING; + +-- ============================================ +-- 8. 给项目经理分配项目相关权限 +-- ============================================ + +INSERT INTO sys_role_permission (role_id, permission_id, create_time) +SELECT 2, id, NOW() FROM sys_permission WHERE permission_code LIKE 'project:%' AND deleted = 0 +ON CONFLICT DO NOTHING; + +-- ============================================ +-- 9. 给普通成员分配查看权限 +-- ============================================ + +INSERT INTO sys_role_permission (role_id, permission_id, create_time) +SELECT 4, id, NOW() FROM sys_permission WHERE permission_code LIKE '%:view' OR permission_code LIKE '%:list' AND deleted = 0 +ON CONFLICT DO NOTHING; diff --git a/src/main/java/cn/yinlihupo/common/exception/GlobalExceptionHandler.java b/src/main/java/cn/yinlihupo/common/exception/GlobalExceptionHandler.java index 01424c0..e7839da 100644 --- a/src/main/java/cn/yinlihupo/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/cn/yinlihupo/common/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package cn.yinlihupo.common.exception; +import cn.dev33.satoken.exception.NotLoginException; import cn.hutool.core.exceptions.UtilException; import cn.yinlihupo.common.core.BaseResponse; import cn.yinlihupo.common.enums.ErrorCode; @@ -60,4 +61,9 @@ public class GlobalExceptionHandler { return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误"); } + @ExceptionHandler(NotLoginException.class) + public BaseResponse notLoginExceptionHandler(NotLoginException e) { + log.error("NotLoginException", e); + return ResultUtils.error(ErrorCode.TOKEN_INVALID, "没有传递token"); + } } diff --git a/src/main/java/cn/yinlihupo/controller/auth/FeishuAuthController.java b/src/main/java/cn/yinlihupo/controller/auth/FeishuAuthController.java index 93b5f9b..9fee9b8 100644 --- a/src/main/java/cn/yinlihupo/controller/auth/FeishuAuthController.java +++ b/src/main/java/cn/yinlihupo/controller/auth/FeishuAuthController.java @@ -1,16 +1,20 @@ package cn.yinlihupo.controller.auth; +import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.stp.StpUtil; import cn.yinlihupo.common.core.BaseResponse; import cn.yinlihupo.common.util.ResultUtils; import cn.yinlihupo.domain.entity.SysUser; import cn.yinlihupo.service.system.FeishuAuthService; +import cn.yinlihupo.service.system.ProjectPermissionService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; /** * 飞书OAuth认证控制器 @@ -22,13 +26,14 @@ import java.util.Map; public class FeishuAuthController { private final FeishuAuthService feishuAuthService; + private final ProjectPermissionService projectPermissionService; /** * 飞书OAuth登录接口(前端回调后调用) * 前端从飞书回调中获取code,然后调用此接口完成登录 * * @param code 飞书授权码 - * @return 登录结果(包含token和用户信息) + * @return 登录结果(包含token、用户信息和权限列表) */ @PostMapping("/login") public BaseResponse> login(@RequestParam String code) { @@ -41,6 +46,11 @@ public class FeishuAuthController { // 使用Sa-Token登录 StpUtil.login(user.getId()); + // 获取用户角色和权限 + List roles = feishuAuthService.getUserRoles(user.getId()); + List permissions = feishuAuthService.getUserPermissions(user.getId()); + boolean isAdmin = projectPermissionService.isAdmin(user.getId()); + // 构建返回数据 Map result = new HashMap<>(); result.put("token", StpUtil.getTokenValue()); @@ -51,6 +61,9 @@ public class FeishuAuthController { result.put("avatar", user.getAvatar()); result.put("phone", user.getPhone()); result.put("email", user.getEmail()); + result.put("roles", roles); + result.put("permissions", permissions); + result.put("isAdmin", isAdmin); log.info("飞书登录成功, userId: {}, phone: {}", user.getId(), user.getPhone()); return ResultUtils.success("登录成功", result); @@ -77,4 +90,53 @@ public class FeishuAuthController { return ResultUtils.success(isLogin ? "已登录" : "未登录", result); } + + /** + * 获取当前登录用户信息 + * + * @return 用户详细信息(包含角色和权限) + */ + @SaCheckLogin + @GetMapping("/user/info") + public BaseResponse> getCurrentUserInfo() { + Long userId = StpUtil.getLoginIdAsLong(); + log.info("获取当前用户信息, userId: {}", userId); + + try { + // 获取用户基本信息 + SysUser user = feishuAuthService.getUserById(userId); + if (user == null) { + return ResultUtils.error("用户不存在"); + } + + // 获取用户角色和权限 + List roles = feishuAuthService.getUserRoles(userId); + List permissions = feishuAuthService.getUserPermissions(userId); + boolean isAdmin = projectPermissionService.isAdmin(userId); + + // 构建返回数据 + Map result = new HashMap<>(); + result.put("userId", user.getId()); + result.put("username", user.getUsername()); + result.put("realName", user.getRealName()); + result.put("nickname", user.getNickname()); + result.put("avatar", user.getAvatar()); + result.put("phone", user.getPhone()); + result.put("email", user.getEmail()); + result.put("gender", user.getGender()); + result.put("position", user.getPosition()); + result.put("employeeNo", user.getEmployeeNo()); + result.put("status", user.getStatus()); + result.put("roles", roles); + result.put("permissions", permissions); + result.put("isAdmin", isAdmin); + result.put("lastLoginTime", user.getLastLoginTime()); + result.put("createTime", user.getCreateTime()); + + return ResultUtils.success("获取成功", result); + } catch (Exception e) { + log.error("获取用户信息失败", e); + return ResultUtils.error("获取用户信息失败: " + e.getMessage()); + } + } } diff --git a/src/main/java/cn/yinlihupo/controller/system/PermissionController.java b/src/main/java/cn/yinlihupo/controller/system/PermissionController.java new file mode 100644 index 0000000..0a9731b --- /dev/null +++ b/src/main/java/cn/yinlihupo/controller/system/PermissionController.java @@ -0,0 +1,154 @@ +package cn.yinlihupo.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.yinlihupo.common.core.BaseResponse; +import cn.yinlihupo.common.util.ResultUtils; +import cn.yinlihupo.domain.entity.SysPermission; +import cn.yinlihupo.mapper.SysPermissionMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 权限管理控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/system/permission") +@RequiredArgsConstructor +public class PermissionController { + + private final SysPermissionMapper permissionMapper; + + /** + * 分页查询权限列表 + */ + @SaCheckPermission("system:permission:list") + @GetMapping("/list") + public BaseResponse> list( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String keyword) { + + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysPermission::getDeleted, 0); + + if (keyword != null && !keyword.isEmpty()) { + wrapper.and(w -> w.like(SysPermission::getPermissionName, keyword) + .or() + .like(SysPermission::getPermissionCode, keyword)); + } + + wrapper.orderByAsc(SysPermission::getSortOrder); + permissionMapper.selectPage(page, wrapper); + + return ResultUtils.success("查询成功", page); + } + + /** + * 查询权限树 + */ + @SaCheckPermission("system:permission:tree") + @GetMapping("/tree") + public BaseResponse> tree() { + List list = permissionMapper.selectMenuTree(); + return ResultUtils.success("查询成功", list); + } + + /** + * 根据ID查询权限 + */ + @SaCheckPermission("system:permission:view") + @GetMapping("/{id}") + public BaseResponse getById(@PathVariable Long id) { + SysPermission permission = permissionMapper.selectById(id); + if (permission == null || permission.getDeleted() == 1) { + return ResultUtils.error("权限不存在"); + } + return ResultUtils.success("查询成功", permission); + } + + /** + * 新增权限 + */ + @SaCheckPermission("system:permission:create") + @PostMapping + public BaseResponse create(@RequestBody SysPermission permission) { + // 检查权限编码是否已存在 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysPermission::getPermissionCode, permission.getPermissionCode()) + .eq(SysPermission::getDeleted, 0); + if (permissionMapper.selectCount(wrapper) > 0) { + return ResultUtils.error("权限编码已存在"); + } + + permission.setStatus(1); + permission.setDeleted(0); + permissionMapper.insert(permission); + + log.info("新增权限成功, id: {}, code: {}", permission.getId(), permission.getPermissionCode()); + return ResultUtils.success("新增成功", permission.getId()); + } + + /** + * 修改权限 + */ + @SaCheckPermission("system:permission:update") + @PutMapping + public BaseResponse update(@RequestBody SysPermission permission) { + if (permission.getId() == null) { + return ResultUtils.error("权限ID不能为空"); + } + + SysPermission exist = permissionMapper.selectById(permission.getId()); + if (exist == null || exist.getDeleted() == 1) { + return ResultUtils.error("权限不存在"); + } + + // 如果修改了权限编码,检查是否与其他权限冲突 + if (!exist.getPermissionCode().equals(permission.getPermissionCode())) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysPermission::getPermissionCode, permission.getPermissionCode()) + .eq(SysPermission::getDeleted, 0) + .ne(SysPermission::getId, permission.getId()); + if (permissionMapper.selectCount(wrapper) > 0) { + return ResultUtils.error("权限编码已存在"); + } + } + + permissionMapper.updateById(permission); + + log.info("修改权限成功, id: {}", permission.getId()); + return ResultUtils.success("修改成功", null); + } + + /** + * 删除权限 + */ + @SaCheckPermission("system:permission:delete") + @DeleteMapping("/{id}") + public BaseResponse delete(@PathVariable Long id) { + SysPermission permission = permissionMapper.selectById(id); + if (permission == null || permission.getDeleted() == 1) { + return ResultUtils.error("权限不存在"); + } + + // 检查是否有子权限 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysPermission::getParentId, id) + .eq(SysPermission::getDeleted, 0); + if (permissionMapper.selectCount(wrapper) > 0) { + return ResultUtils.error("该权限下存在子权限,请先删除子权限"); + } + + permissionMapper.deleteById(id); + + log.info("删除权限成功, id: {}", id); + return ResultUtils.success("删除成功", null); + } +} diff --git a/src/main/java/cn/yinlihupo/controller/system/RoleController.java b/src/main/java/cn/yinlihupo/controller/system/RoleController.java new file mode 100644 index 0000000..19bad12 --- /dev/null +++ b/src/main/java/cn/yinlihupo/controller/system/RoleController.java @@ -0,0 +1,234 @@ +package cn.yinlihupo.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.yinlihupo.common.core.BaseResponse; +import cn.yinlihupo.common.util.ResultUtils; +import cn.yinlihupo.domain.entity.SysPermission; +import cn.yinlihupo.domain.entity.SysRole; +import cn.yinlihupo.domain.entity.SysRolePermission; +import cn.yinlihupo.mapper.SysPermissionMapper; +import cn.yinlihupo.mapper.SysRoleMapper; +import cn.yinlihupo.mapper.SysRolePermissionMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 角色管理控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/system/role") +@RequiredArgsConstructor +public class RoleController { + + private final SysRoleMapper roleMapper; + private final SysPermissionMapper permissionMapper; + private final SysRolePermissionMapper rolePermissionMapper; + + /** + * 分页查询角色列表 + */ + @SaCheckPermission("system:role:list") + @GetMapping("/list") + public BaseResponse> list( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String keyword) { + + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysRole::getDeleted, 0); + + if (keyword != null && !keyword.isEmpty()) { + wrapper.and(w -> w.like(SysRole::getRoleName, keyword) + .or() + .like(SysRole::getRoleCode, keyword)); + } + + wrapper.orderByAsc(SysRole::getSortOrder); + roleMapper.selectPage(page, wrapper); + + return ResultUtils.success("查询成功", page); + } + + /** + * 查询所有角色(用于下拉选择) + */ + @SaCheckPermission("system:role:list") + @GetMapping("/all") + public BaseResponse> all() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysRole::getDeleted, 0) + .eq(SysRole::getStatus, 1) + .orderByAsc(SysRole::getSortOrder); + List list = roleMapper.selectList(wrapper); + return ResultUtils.success("查询成功", list); + } + + /** + * 根据ID查询角色 + */ + @SaCheckPermission("system:role:view") + @GetMapping("/{id}") + public BaseResponse getById(@PathVariable Long id) { + SysRole role = roleMapper.selectById(id); + if (role == null || role.getDeleted() == 1) { + return ResultUtils.error("角色不存在"); + } + return ResultUtils.success("查询成功", role); + } + + /** + * 查询角色的权限列表 + */ + @SaCheckPermission("system:role:view") + @GetMapping("/{id}/permissions") + public BaseResponse> getRolePermissions(@PathVariable Long id) { + SysRole role = roleMapper.selectById(id); + if (role == null || role.getDeleted() == 1) { + return ResultUtils.error("角色不存在"); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysRolePermission::getRoleId, id); + List list = rolePermissionMapper.selectList(wrapper); + + List permissionIds = list.stream() + .map(SysRolePermission::getPermissionId) + .collect(Collectors.toList()); + + return ResultUtils.success("查询成功", permissionIds); + } + + /** + * 新增角色 + */ + @SaCheckPermission("system:role:create") + @PostMapping + public BaseResponse create(@RequestBody SysRole role) { + // 检查角色编码是否已存在 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysRole::getRoleCode, role.getRoleCode()) + .eq(SysRole::getDeleted, 0); + if (roleMapper.selectCount(wrapper) > 0) { + return ResultUtils.error("角色编码已存在"); + } + + role.setStatus(1); + role.setDeleted(0); + roleMapper.insert(role); + + log.info("新增角色成功, id: {}, code: {}", role.getId(), role.getRoleCode()); + return ResultUtils.success("新增成功", role.getId()); + } + + /** + * 修改角色 + */ + @SaCheckPermission("system:role:update") + @PutMapping + public BaseResponse update(@RequestBody SysRole role) { + if (role.getId() == null) { + return ResultUtils.error("角色ID不能为空"); + } + + SysRole exist = roleMapper.selectById(role.getId()); + if (exist == null || exist.getDeleted() == 1) { + return ResultUtils.error("角色不存在"); + } + + // 不允许修改系统角色的编码 + if ("system".equals(exist.getRoleType()) && !exist.getRoleCode().equals(role.getRoleCode())) { + return ResultUtils.error("系统角色不允许修改编码"); + } + + // 如果修改了角色编码,检查是否与其他角色冲突 + if (!exist.getRoleCode().equals(role.getRoleCode())) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysRole::getRoleCode, role.getRoleCode()) + .eq(SysRole::getDeleted, 0) + .ne(SysRole::getId, role.getId()); + if (roleMapper.selectCount(wrapper) > 0) { + return ResultUtils.error("角色编码已存在"); + } + } + + roleMapper.updateById(role); + + log.info("修改角色成功, id: {}", role.getId()); + return ResultUtils.success("修改成功", null); + } + + /** + * 删除角色 + */ + @SaCheckPermission("system:role:delete") + @DeleteMapping("/{id}") + @Transactional(rollbackFor = Exception.class) + public BaseResponse delete(@PathVariable Long id) { + SysRole role = roleMapper.selectById(id); + if (role == null || role.getDeleted() == 1) { + return ResultUtils.error("角色不存在"); + } + + // 不允许删除系统角色 + if ("system".equals(role.getRoleType())) { + return ResultUtils.error("系统角色不允许删除"); + } + + // 删除角色 + roleMapper.deleteById(id); + + // 删除角色权限关联 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysRolePermission::getRoleId, id); + rolePermissionMapper.delete(wrapper); + + log.info("删除角色成功, id: {}", id); + return ResultUtils.success("删除成功", null); + } + + /** + * 为角色分配权限 + */ + @SaCheckPermission("system:role:bindPermission") + @PostMapping("/{id}/permissions") + @Transactional(rollbackFor = Exception.class) + public BaseResponse bindPermissions(@PathVariable Long id, @RequestBody List permissionIds) { + SysRole role = roleMapper.selectById(id); + if (role == null || role.getDeleted() == 1) { + return ResultUtils.error("角色不存在"); + } + + // 删除原有权限关联 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysRolePermission::getRoleId, id); + rolePermissionMapper.delete(wrapper); + + // 插入新的权限关联 + if (permissionIds != null && !permissionIds.isEmpty()) { + for (Long permissionId : permissionIds) { + SysPermission permission = permissionMapper.selectById(permissionId); + if (permission != null && permission.getDeleted() == 0) { + SysRolePermission rp = new SysRolePermission(); + rp.setRoleId(id); + rp.setPermissionId(permissionId); + rp.setCreateTime(LocalDateTime.now()); + rolePermissionMapper.insert(rp); + } + } + } + + log.info("角色分配权限成功, roleId: {}, permissionCount: {}", id, + permissionIds != null ? permissionIds.size() : 0); + return ResultUtils.success("分配权限成功", null); + } +} diff --git a/src/main/java/cn/yinlihupo/controller/system/UserRoleController.java b/src/main/java/cn/yinlihupo/controller/system/UserRoleController.java new file mode 100644 index 0000000..64e2fb4 --- /dev/null +++ b/src/main/java/cn/yinlihupo/controller/system/UserRoleController.java @@ -0,0 +1,263 @@ +package cn.yinlihupo.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.yinlihupo.common.core.BaseResponse; +import cn.yinlihupo.common.util.ResultUtils; +import cn.yinlihupo.domain.entity.SysRole; +import cn.yinlihupo.domain.entity.SysUser; +import cn.yinlihupo.domain.entity.SysUserRole; +import cn.yinlihupo.mapper.SysRoleMapper; +import cn.yinlihupo.mapper.SysUserMapper; +import cn.yinlihupo.mapper.SysUserRoleMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户角色管理控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/system/user") +@RequiredArgsConstructor +public class UserRoleController { + + private final SysUserMapper userMapper; + private final SysRoleMapper roleMapper; + private final SysUserRoleMapper userRoleMapper; + + /** + * 分页查询用户列表 + */ + @SaCheckPermission("system:user:list") + @GetMapping("/list") + public BaseResponse> list( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String keyword) { + + Page page = new Page<>(pageNum, pageSize); + List users = userMapper.selectPageList(null, null, keyword); + + // 手动设置分页结果 + page.setRecords(users); + page.setTotal(users.size()); + + return ResultUtils.success("查询成功", page); + } + + /** + * 根据ID查询用户 + */ + @SaCheckPermission("system:user:view") + @GetMapping("/{id}") + public BaseResponse getById(@PathVariable Long id) { + SysUser user = userMapper.selectById(id); + if (user == null || user.getDeleted() == 1) { + return ResultUtils.error("用户不存在"); + } + // 不返回密码 + user.setPassword(null); + return ResultUtils.success("查询成功", user); + } + + /** + * 查询用户的角色列表 + */ + @SaCheckPermission("system:user:view") + @GetMapping("/{id}/roles") + public BaseResponse> getUserRoles(@PathVariable Long id) { + SysUser user = userMapper.selectById(id); + if (user == null || user.getDeleted() == 1) { + return ResultUtils.error("用户不存在"); + } + + // 查询用户角色关联 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysUserRole::getUserId, id); + List userRoles = userRoleMapper.selectList(wrapper); + + if (userRoles.isEmpty()) { + return ResultUtils.success("查询成功", List.of()); + } + + // 查询角色详情 + List roleIds = userRoles.stream() + .map(SysUserRole::getRoleId) + .collect(Collectors.toList()); + + LambdaQueryWrapper roleWrapper = new LambdaQueryWrapper<>(); + roleWrapper.in(SysRole::getId, roleIds) + .eq(SysRole::getDeleted, 0) + .eq(SysRole::getStatus, 1); + List roles = roleMapper.selectList(roleWrapper); + + return ResultUtils.success("查询成功", roles); + } + + /** + * 查询用户角色ID列表(用于回显) + */ + @SaCheckPermission("system:user:view") + @GetMapping("/{id}/roleIds") + public BaseResponse> getUserRoleIds(@PathVariable Long id) { + SysUser user = userMapper.selectById(id); + if (user == null || user.getDeleted() == 1) { + return ResultUtils.error("用户不存在"); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysUserRole::getUserId, id); + List userRoles = userRoleMapper.selectList(wrapper); + + List roleIds = userRoles.stream() + .map(SysUserRole::getRoleId) + .collect(Collectors.toList()); + + return ResultUtils.success("查询成功", roleIds); + } + + /** + * 新增用户 + */ + @SaCheckPermission("system:user:create") + @PostMapping + public BaseResponse create(@RequestBody SysUser user) { + // 检查用户名是否已存在 + if (user.getUsername() != null && !user.getUsername().isEmpty()) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysUser::getUsername, user.getUsername()) + .eq(SysUser::getDeleted, 0); + if (userMapper.selectCount(wrapper) > 0) { + return ResultUtils.error("用户名已存在"); + } + } + + // 检查手机号是否已存在 + if (user.getPhone() != null && !user.getPhone().isEmpty()) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysUser::getPhone, user.getPhone()) + .eq(SysUser::getDeleted, 0); + if (userMapper.selectCount(wrapper) > 0) { + return ResultUtils.error("手机号已存在"); + } + } + + user.setStatus(1); + user.setDeleted(0); + // 设置默认密码 + if (user.getPassword() == null || user.getPassword().isEmpty()) { + user.setPassword("123456"); // 实际项目中需要加密 + } + + userMapper.insert(user); + + log.info("新增用户成功, id: {}, username: {}", user.getId(), user.getUsername()); + return ResultUtils.success("新增成功", user.getId()); + } + + /** + * 修改用户 + */ + @SaCheckPermission("system:user:update") + @PutMapping + public BaseResponse update(@RequestBody SysUser user) { + if (user.getId() == null) { + return ResultUtils.error("用户ID不能为空"); + } + + SysUser exist = userMapper.selectById(user.getId()); + if (exist == null || exist.getDeleted() == 1) { + return ResultUtils.error("用户不存在"); + } + + // 如果修改了手机号,检查是否与其他用户冲突 + if (user.getPhone() != null && !user.getPhone().isEmpty() + && !user.getPhone().equals(exist.getPhone())) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysUser::getPhone, user.getPhone()) + .eq(SysUser::getDeleted, 0) + .ne(SysUser::getId, user.getId()); + if (userMapper.selectCount(wrapper) > 0) { + return ResultUtils.error("手机号已存在"); + } + } + + // 不更新密码和敏感字段 + user.setPassword(null); + user.setCreateTime(null); + user.setDeleted(null); + + userMapper.updateById(user); + + log.info("修改用户成功, id: {}", user.getId()); + return ResultUtils.success("修改成功", null); + } + + /** + * 删除用户 + */ + @SaCheckPermission("system:user:delete") + @DeleteMapping("/{id}") + @Transactional(rollbackFor = Exception.class) + public BaseResponse delete(@PathVariable Long id) { + SysUser user = userMapper.selectById(id); + if (user == null || user.getDeleted() == 1) { + return ResultUtils.error("用户不存在"); + } + + // 删除用户 + userMapper.deleteById(id); + + // 删除用户角色关联 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysUserRole::getUserId, id); + userRoleMapper.delete(wrapper); + + log.info("删除用户成功, id: {}", id); + return ResultUtils.success("删除成功", null); + } + + /** + * 为用户绑定角色 + */ + @SaCheckPermission("system:user:bindRole") + @PostMapping("/{id}/roles") + @Transactional(rollbackFor = Exception.class) + public BaseResponse bindRoles(@PathVariable Long id, @RequestBody List roleIds) { + SysUser user = userMapper.selectById(id); + if (user == null || user.getDeleted() == 1) { + return ResultUtils.error("用户不存在"); + } + + // 删除原有角色关联 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysUserRole::getUserId, id); + userRoleMapper.delete(wrapper); + + // 插入新的角色关联 + if (roleIds != null && !roleIds.isEmpty()) { + for (Long roleId : roleIds) { + SysRole role = roleMapper.selectById(roleId); + if (role != null && role.getDeleted() == 0 && role.getStatus() == 1) { + SysUserRole ur = new SysUserRole(); + ur.setUserId(id); + ur.setRoleId(roleId); + ur.setCreateTime(LocalDateTime.now()); + userRoleMapper.insert(ur); + } + } + } + + log.info("用户绑定角色成功, userId: {}, roleCount: {}", id, + roleIds != null ? roleIds.size() : 0); + return ResultUtils.success("绑定角色成功", null); + } +} diff --git a/src/main/java/cn/yinlihupo/service/system/FeishuAuthService.java b/src/main/java/cn/yinlihupo/service/system/FeishuAuthService.java index d5ca166..35eefb3 100644 --- a/src/main/java/cn/yinlihupo/service/system/FeishuAuthService.java +++ b/src/main/java/cn/yinlihupo/service/system/FeishuAuthService.java @@ -2,6 +2,8 @@ package cn.yinlihupo.service.system; import cn.yinlihupo.domain.entity.SysUser; +import java.util.List; + /** * 飞书认证服务接口 */ @@ -36,4 +38,28 @@ public interface FeishuAuthService { * @return 用户实体 */ SysUser getOrCreateUserByPhone(String phone, String realName, String avatar, String email, String openId); + + /** + * 根据用户ID查询用户 + * + * @param userId 用户ID + * @return 用户实体 + */ + SysUser getUserById(Long userId); + + /** + * 获取用户角色列表 + * + * @param userId 用户ID + * @return 角色编码列表 + */ + List getUserRoles(Long userId); + + /** + * 获取用户权限列表 + * + * @param userId 用户ID + * @return 权限编码列表 + */ + List getUserPermissions(Long userId); } diff --git a/src/main/java/cn/yinlihupo/service/system/impl/FeishuAuthServiceImpl.java b/src/main/java/cn/yinlihupo/service/system/impl/FeishuAuthServiceImpl.java index 03f5f75..d9e3d0c 100644 --- a/src/main/java/cn/yinlihupo/service/system/impl/FeishuAuthServiceImpl.java +++ b/src/main/java/cn/yinlihupo/service/system/impl/FeishuAuthServiceImpl.java @@ -6,6 +6,7 @@ import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.yinlihupo.common.config.FeishuConfig; import cn.yinlihupo.domain.entity.SysUser; +import cn.yinlihupo.mapper.SysPermissionMapper; import cn.yinlihupo.mapper.SysUserMapper; import cn.yinlihupo.service.system.FeishuAuthService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -17,6 +18,8 @@ import org.springframework.transaction.annotation.Transactional; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; /** * 飞书认证服务实现类 @@ -28,6 +31,7 @@ public class FeishuAuthServiceImpl implements FeishuAuthService { private final FeishuConfig feishuConfig; private final SysUserMapper sysUserMapper; + private final SysPermissionMapper sysPermissionMapper; /** * 飞书OAuth授权端点 @@ -203,4 +207,36 @@ public class FeishuAuthServiceImpl implements FeishuAuthService { return user; } + + @Override + public SysUser getUserById(Long userId) { + if (userId == null) { + return null; + } + return sysUserMapper.selectById(userId); + } + + @Override + public List getUserRoles(Long userId) { + if (userId == null) { + return Collections.emptyList(); + } + List roles = sysUserMapper.selectRoleCodesByUserId(userId); + return roles != null ? roles : Collections.emptyList(); + } + + @Override + public List getUserPermissions(Long userId) { + if (userId == null) { + return Collections.emptyList(); + } + // 先获取用户角色 + List roleCodes = sysUserMapper.selectRoleCodesByUserId(userId); + if (roleCodes == null || roleCodes.isEmpty()) { + return Collections.emptyList(); + } + // 根据角色查询权限 + List permissions = sysPermissionMapper.selectPermissionCodesByRoleCodes(roleCodes); + return permissions != null ? permissions : Collections.emptyList(); + } } diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index f4bb2a8..1f1a72d 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -4,6 +4,21 @@ spring: activate: on-profile: dev + # Redis 配置 + data: + redis: + host: 10.200.8.25 + port: 16379 + password: # 如果没有密码留空 + database: 0 + timeout: 10s + lettuce: + pool: + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: -1ms + # PostgreSQL 数据库配置 datasource: url: jdbc:postgresql://10.200.8.25:5432/aiprojectmanager diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 322bdfa..58d874b 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -33,4 +33,6 @@ sa-token: # 是否从请求体中读取token is-read-body: false # 是否从header中读取token - is-read-header: true \ No newline at end of file + is-read-header: true + # 指定 token 提交时的前缀 + token-prefix: Bearer \ No newline at end of file