├── .gitignore ├── README.md ├── doc └── sql │ └── pea.sql ├── img.png ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── pea │ │ │ ├── PeaApplication.java │ │ │ ├── business │ │ │ ├── auth │ │ │ │ ├── controller │ │ │ │ │ └── SysUserLoginController.java │ │ │ │ ├── param │ │ │ │ │ └── SysUserLoginParam.java │ │ │ │ └── vo │ │ │ │ │ └── LoginResult.java │ │ │ └── sys │ │ │ │ ├── controller │ │ │ │ ├── SysMenuController.java │ │ │ │ ├── SysOperationLogController.java │ │ │ │ ├── SysResourceController.java │ │ │ │ ├── SysRoleController.java │ │ │ │ ├── SysRouteController.java │ │ │ │ └── SysUserController.java │ │ │ │ ├── domain │ │ │ │ ├── SysOperationLog.java │ │ │ │ ├── SysResource.java │ │ │ │ ├── SysRole.java │ │ │ │ ├── SysRoleResource.java │ │ │ │ ├── SysUser.java │ │ │ │ └── SysUserRole.java │ │ │ │ ├── mapper │ │ │ │ ├── SysOperationLogMapper.java │ │ │ │ ├── SysResourceMapper.java │ │ │ │ ├── SysRoleMapper.java │ │ │ │ ├── SysRoleResourceMapper.java │ │ │ │ ├── SysUserMapper.java │ │ │ │ └── SysUserRoleMapper.java │ │ │ │ ├── param │ │ │ │ ├── CreateUserParam.java │ │ │ │ ├── ResourceParam.java │ │ │ │ └── RoleParam.java │ │ │ │ ├── service │ │ │ │ ├── SysOperationLogService.java │ │ │ │ ├── SysResourceService.java │ │ │ │ ├── SysRoleResourceService.java │ │ │ │ ├── SysRoleService.java │ │ │ │ ├── SysUserRoleService.java │ │ │ │ ├── SysUserService.java │ │ │ │ └── impl │ │ │ │ │ ├── SysOperationLogServiceImpl.java │ │ │ │ │ ├── SysResourceServiceImpl.java │ │ │ │ │ ├── SysRoleResourceServiceImpl.java │ │ │ │ │ ├── SysRoleServiceImpl.java │ │ │ │ │ ├── SysUserRoleServiceImpl.java │ │ │ │ │ └── SysUserServiceImpl.java │ │ │ │ └── vo │ │ │ │ ├── SysMenuTreeVO.java │ │ │ │ ├── SysMenuVO.java │ │ │ │ ├── SysRoleVO.java │ │ │ │ ├── SysRoutesVO.java │ │ │ │ ├── SysUserVO.java │ │ │ │ └── UserInfoVO.java │ │ │ ├── common │ │ │ ├── annotation │ │ │ │ └── SysLogInterface.java │ │ │ ├── api │ │ │ │ ├── IErrorCode.java │ │ │ │ ├── Result.java │ │ │ │ └── ResultCode.java │ │ │ ├── config │ │ │ │ ├── CacheConfiguration.java │ │ │ │ ├── GlobalWebConfig.java │ │ │ │ ├── MybatisPlusConfig.java │ │ │ │ ├── SecurityConfig.java │ │ │ │ ├── SwaggerConfig.java │ │ │ │ └── ThreadPoolConfig.java │ │ │ ├── enums │ │ │ │ ├── BusinessType.java │ │ │ │ ├── CacheConstants.java │ │ │ │ ├── DelStatusEnums.java │ │ │ │ ├── MenuTypeEnums.java │ │ │ │ └── StatusEnums.java │ │ │ ├── exception │ │ │ │ ├── GlobalException.java │ │ │ │ ├── GlobalExceptionEnum.java │ │ │ │ ├── GlobalExceptionHandler.java │ │ │ │ └── GlobalExceptionMap.java │ │ │ ├── manager │ │ │ │ ├── AsyncManager.java │ │ │ │ └── factory │ │ │ │ │ └── AsyncFactory.java │ │ │ ├── model │ │ │ │ └── Meta.java │ │ │ ├── mybatis │ │ │ │ ├── base │ │ │ │ │ └── BaseEntity.java │ │ │ │ └── config │ │ │ │ │ └── MybatisPlusMetaObjectHandler.java │ │ │ └── utils │ │ │ │ ├── ExceptionUtil.java │ │ │ │ ├── IpUtil.java │ │ │ │ ├── JwtTokenUtil.java │ │ │ │ ├── RouteUtil.java │ │ │ │ ├── SecurityUtil.java │ │ │ │ ├── ServletUtils.java │ │ │ │ ├── SpringUtils.java │ │ │ │ ├── SysUserDetail.java │ │ │ │ └── Threads.java │ │ │ └── component │ │ │ ├── aspect │ │ │ └── WebLogAspect.java │ │ │ └── security │ │ │ ├── filter │ │ │ └── JwtAuthenticationTokenFilter.java │ │ │ ├── handle │ │ │ ├── RestAuthenticationEntryPoint.java │ │ │ └── RestfulAccessDeniedHandler.java │ │ │ └── service │ │ │ ├── PermissionService.java │ │ │ └── UserDetailsServiceImpl.java │ └── resources │ │ ├── application-local.yaml.bak │ │ ├── application.yaml │ │ ├── ip2region │ │ └── ip2region.xdb │ │ ├── logback-spring.xml │ │ └── mapper │ │ └── sys │ │ ├── SysOperationLogMapper.xml │ │ ├── SysResourceMapper.xml │ │ ├── SysRoleMapper.xml │ │ └── SysUserMapper.xml └── test │ └── java │ └── com │ └── pea │ └── PeaApplicationTests.java └── web ├── .env ├── .env.test ├── manage ├── menu │ ├── index.vue │ └── modules │ │ ├── menu-operate-modal.vue │ │ └── shared.ts ├── role │ ├── index.vue │ └── modules │ │ ├── button-auth-modal.vue │ │ ├── menu-auth-modal.vue │ │ ├── role-operate-drawer.vue │ │ └── role-search.vue ├── user-detail │ └── [id].vue └── user │ ├── index.vue │ └── modules │ ├── user-operate-drawer.vue │ └── user-search.vue ├── service ├── api │ ├── auth.ts │ ├── index.ts │ ├── route.ts │ └── system-manage.ts └── request │ ├── index.ts │ ├── shared.ts │ └── type.ts └── typings └── api.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### log ### 36 | logs/ 37 | 38 | **/application-local.yaml 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 豌豆 2 | 3 | ## 简介 4 | 5 | 项目采用SpringBoot3.3 + 6 | JDK21、MyBatis-Plus、SpringSecurity安全框架等,适配 [soybean-admin](https://gitee.com/honghuangdc/soybean-admin) 7 | 开发的简单权限系统。 8 | 9 | 10 | ## **技术选型:** 11 | 12 | | 依赖 | 版本 | 13 | |--------------|--------| 14 | | Spring Boot | 3.3.0 | 15 | | JDK | 21 | 16 | | Mybatis-Plus | 3.5.8 | 17 | | hutool | 5.8.25 | 18 | | knife4j | 4.5.0 | 19 | | jwt | 0.9.1 | 20 | | mysql | 8.0.33 | 21 | | ... | ... | 22 | 23 | ## TODO 不固定更新 24 | 25 | - [x] 优化补充菜单 26 | - [x] 优化日志管理 27 | - [ ] 开发实现Google二次认证 28 | - [ ] 实现按钮权限逻辑 29 | 30 | ## 后端部署 31 | 32 | > - **GitHub仓库地址:** https://github.com/haitang1894/pea.git 33 | 34 | - idea、eclipse需安装lombok插件,不然会提示找不到entity的get set方法 35 | - 创建数据库pea,数据库编码为UTF-8 36 | - 执行doc/sql/pea.sql文件,初始化数据 37 | - 修改application-local.yml,更新MySQL账号和密码 38 | - Eclipse、IDEA运行PeaApplication.java,则可启动项目 39 | - Swagger注解路径:http://localhost:9528/doc.html 40 | 41 | ## 前端部署 42 | 43 | > - **GitHub仓库地址:** https://github.com/soybeanjs/soybean-admin.git 44 | 45 | - 前端部署以及更换访问路径请看下面文档 46 | 47 | 48 | - 前端部署方案:请参考 **[soybean-admin](https://docs.soybeanjs.cn/zh/)** 项目文档 49 | 50 | - 前端部署完毕,修改配置就可以使用该后端 51 | 52 | - 账号:Soybean,密码:123456 53 | 54 | - 账号:admin,密码:123456 55 | 56 | 57 | (弃用)考虑到soybeanjs作者前端暂时没有对接后端,为了方便新手拉取代码后再添加接口,在本项目web目录整理了需要调整的前端代码 58 | ,找到位置直接覆盖即可,随后启动前端、后端就可以正常使用。下图是前端修改位置 59 | 60 | ![img.png](img.png) 61 | 62 | ## 注解 63 | 64 | - 日志记录注解 @SysLogInterface 65 | - 权限认证注解 @PreAuthorize("@pre.hasPermission('system:user:add')") 66 | 目前 权限认证注解 开发并不完善,考虑前端暂未实现权限校验,没有进一步开发 67 | 68 | ## 6月1日更修内容: 69 | 70 | 1. 修改 添加/修改时候 创建人 创建人ID 不能获取问题 71 | 2. 更新sql修改 admin账号 密码变成 123456 72 | > update t_sys_user set `password` = '$2a$10$qbdPPGSnLm2oQwgLXyX8wOTgVZLHnm2pqS.We5.n6do3YfVxobCUy' where user_name = 'admin'; 73 | 3. 添加用户管理、角色管理、菜单管理的添加/修改接口 -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haitang1894/pea/a751adb98f592958d08321e690e87a3654ae5264/img.png -------------------------------------------------------------------------------- /src/main/java/com/pea/PeaApplication.java: -------------------------------------------------------------------------------- 1 | package com.pea; 2 | 3 | import com.pea.common.utils.IpUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 8 | import org.springframework.boot.builder.SpringApplicationBuilder; 9 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 10 | import org.springframework.cache.annotation.EnableCaching; 11 | import org.springframework.context.ConfigurableApplicationContext; 12 | 13 | @Slf4j 14 | @EnableCaching 15 | @SpringBootApplication(exclude = {RedisAutoConfiguration.class}) 16 | public class PeaApplication extends SpringBootServletInitializer { 17 | 18 | @Override 19 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 20 | return application.sources(PeaApplication.class); 21 | } 22 | 23 | public static void main(String[] args) { 24 | ConfigurableApplicationContext context = SpringApplication.run(PeaApplication.class, args); 25 | String hostIp = IpUtil.getHostIp(); 26 | String port = context.getEnvironment().getProperty("server.port"); 27 | log.info("Swagger访问地址:http://{}:{}/doc.html", hostIp, port); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/auth/controller/SysUserLoginController.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.auth.controller; 2 | 3 | import com.pea.business.auth.param.SysUserLoginParam; 4 | import com.pea.business.auth.vo.LoginResult; 5 | import com.pea.business.sys.service.SysUserService; 6 | import com.pea.business.sys.vo.UserInfoVO; 7 | import com.pea.common.annotation.SysLogInterface; 8 | import com.pea.common.api.Result; 9 | import com.pea.common.enums.BusinessType; 10 | import io.swagger.v3.oas.annotations.Operation; 11 | import io.swagger.v3.oas.annotations.tags.Tag; 12 | import lombok.RequiredArgsConstructor; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | @Slf4j 17 | @RestController 18 | @RequiredArgsConstructor 19 | @Tag(name = "SysUserLoginController") 20 | @RequestMapping("/auth") 21 | public class SysUserLoginController { 22 | 23 | private final SysUserService sysUserService; 24 | 25 | @Operation(summary = "登录") 26 | @PostMapping(value = "/login") 27 | @SysLogInterface(title = "登录", businessType = BusinessType.GRANT) 28 | public Result login(@RequestBody SysUserLoginParam sysUserLoginParam) { 29 | // 获取系统验证码开关 30 | // boolean sw = Boolean.parseBoolean(ParamResolver.getStr(ConfigEnums.SYS_CAPTCHA_IMG.name(), "true")); 31 | // if (sw) { 32 | // 验证码校验 33 | // boolean captcha = sysCaptchaService.validate(sysUserLoginParam.getUuid(), 34 | // sysUserLoginParam.getCode()); 35 | // if (!captcha) { 36 | // return Result.failed("验证码不正确"); 37 | // } 38 | // } 39 | return Result.success(sysUserService.login(sysUserLoginParam.getUserName(), 40 | sysUserLoginParam.getPassword())); 41 | } 42 | 43 | @Operation(summary = "获取用户信息") 44 | @GetMapping(value = "/getUserInfo") 45 | public Result getUserInfo(@RequestHeader("Authorization") String authorizationHeader) { 46 | return sysUserService.getUserInfo(authorizationHeader); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/auth/param/SysUserLoginParam.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.auth.param; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class SysUserLoginParam { 8 | 9 | @Schema(description = "用户名") 10 | private String userName; 11 | 12 | @Schema(description = "密码") 13 | private String password; 14 | 15 | /** 16 | * 图形验证码 17 | */ 18 | @Schema(description = "验证码") 19 | private String code; 20 | 21 | /** 22 | * 唯一标识 23 | */ 24 | @Schema(description = "唯一标识") 25 | private String uuid = ""; 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/auth/vo/LoginResult.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.auth.vo; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class LoginResult { 12 | 13 | @Schema(description = "token") 14 | private String token; 15 | 16 | @Schema(description = "refreshToken") 17 | private String refreshToken; 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/controller/SysMenuController.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.controller; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.pea.business.sys.service.SysResourceService; 5 | import com.pea.business.sys.vo.SysMenuTreeVO; 6 | import com.pea.business.sys.vo.SysMenuVO; 7 | import com.pea.common.annotation.SysLogInterface; 8 | import com.pea.common.api.Result; 9 | import com.pea.common.enums.BusinessType; 10 | import io.swagger.v3.oas.annotations.Operation; 11 | import io.swagger.v3.oas.annotations.Parameter; 12 | import io.swagger.v3.oas.annotations.Parameters; 13 | import io.swagger.v3.oas.annotations.tags.Tag; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | @Slf4j 25 | @RestController 26 | @RequiredArgsConstructor 27 | @Tag(name = "SysMenuController", description = "菜单信息控制层") 28 | //@RequestMapping("/sys/menu") 29 | @RequestMapping("/systemManage") 30 | public class SysMenuController { 31 | 32 | private final SysResourceService sysResourceService; 33 | 34 | @Operation(summary = "查询菜单信息") 35 | @GetMapping(value = "/getMenuList/v2") 36 | @Parameters({ 37 | @Parameter(name = "current", description = "当前页", required = true, example = "1"), 38 | @Parameter(name = "size", description = "每页显示条数", required = true, example = "10"), 39 | }) 40 | @SysLogInterface(title = "查询菜单信息", businessType = BusinessType.OTHER) 41 | // @PreAuthorize("@pre.hasPermission('sys:menu:list')") 42 | public Result> getMenuList(@Parameter(hidden = true) @RequestParam Map params) { 43 | return sysResourceService.getMenuList(params); 44 | } 45 | 46 | @Operation(summary = "查询全部页面组件名称") 47 | @GetMapping(value = "/getAllPages") 48 | public Result> getAllPages() { 49 | return sysResourceService.getAllPages(); 50 | } 51 | 52 | @Operation(summary = "查询菜单树") 53 | @GetMapping(value = "/getMenuTree") 54 | public Result> getMenuTree() { 55 | return sysResourceService.getMenuTree(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/controller/SysOperationLogController.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.controller; 2 | 3 | import com.pea.business.sys.domain.SysOperationLog; 4 | import com.pea.business.sys.service.SysOperationLogService; 5 | import com.pea.common.api.Result; 6 | import io.swagger.v3.oas.annotations.Operation; 7 | import io.swagger.v3.oas.annotations.tags.Tag; 8 | import lombok.RequiredArgsConstructor; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | import java.util.List; 16 | 17 | @Slf4j 18 | @RestController 19 | @RequiredArgsConstructor 20 | @Tag(name = "SysOperationLogController", description = "日志信息控制层") 21 | @RequestMapping("/sys/log") 22 | public class SysOperationLogController { 23 | 24 | final SysOperationLogService sysOperationLogService; 25 | 26 | @Operation(summary = "导出数据") 27 | @PostMapping(value = "/getExportList") 28 | public Result> add(@RequestBody SysOperationLog sysOperationLog) { 29 | return Result.success(sysOperationLogService.getExportList(sysOperationLog)); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/controller/SysResourceController.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.controller; 2 | 3 | import com.pea.business.sys.domain.SysResource; 4 | import com.pea.business.sys.param.ResourceParam; 5 | import com.pea.business.sys.service.SysResourceService; 6 | import com.pea.common.api.Result; 7 | import com.pea.common.utils.RouteUtil; 8 | import io.swagger.v3.oas.annotations.Operation; 9 | import io.swagger.v3.oas.annotations.tags.Tag; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @Slf4j 18 | @RestController 19 | @RequiredArgsConstructor 20 | @Tag(name = "SysResourceController", description = "资源信息控制层") 21 | @RequestMapping("/sys/resource") 22 | public class SysResourceController { 23 | 24 | private static final RouteUtil routeUtil = new RouteUtil(); 25 | 26 | private final SysResourceService sysResourceService; 27 | 28 | @Operation(summary = "新增资源信息") 29 | @PostMapping(value = "/add") 30 | public Result add(@RequestBody ResourceParam resourceParam) { 31 | SysResource sysResource = routeUtil.paramConvertSysResource(resourceParam); 32 | return sysResourceService.add(sysResource); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/controller/SysRoleController.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.controller; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.pea.business.sys.domain.SysRole; 5 | import com.pea.business.sys.param.RoleParam; 6 | import com.pea.business.sys.service.SysRoleService; 7 | import com.pea.business.sys.vo.SysRoleVO; 8 | import com.pea.common.annotation.SysLogInterface; 9 | import com.pea.common.api.Result; 10 | import com.pea.common.enums.BusinessType; 11 | import io.swagger.v3.oas.annotations.Operation; 12 | import io.swagger.v3.oas.annotations.Parameter; 13 | import io.swagger.v3.oas.annotations.Parameters; 14 | import io.swagger.v3.oas.annotations.tags.Tag; 15 | import lombok.RequiredArgsConstructor; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | @Slf4j 23 | @RestController 24 | @RequiredArgsConstructor 25 | @Tag(name = "SysRoleController", description = "用户角色控制层") 26 | //@RequestMapping("/sys/role") 27 | @RequestMapping("/systemManage") 28 | public class SysRoleController { 29 | 30 | private final SysRoleService sysRoleService; 31 | 32 | @Operation(summary = "list 分页列表") 33 | @Parameters({ 34 | @Parameter(name = "current", description = "当前页", required = true, example = "1"), 35 | @Parameter(name = "size", description = "每页显示条数", required = true, example = "10"), 36 | @Parameter(name = "username", description = "用户名称"), 37 | }) 38 | @GetMapping(value = "/getRoleList") 39 | @SysLogInterface(title = "查询角色信息", businessType = BusinessType.OTHER) 40 | public Result> list(@Parameter(hidden = true) @RequestParam Map params) { 41 | IPage sysUsers = sysRoleService.getPage(params); 42 | return Result.success(sysUsers); 43 | } 44 | 45 | @Operation(summary = "查询角色信息") 46 | @GetMapping(value = "/getAllRoles") 47 | public Result> getAllRoles(@RequestHeader("Authorization") String authorizationHeader) { 48 | return sysRoleService.getAllRoles(authorizationHeader); 49 | } 50 | 51 | @Operation(summary = "新增角色信息") 52 | @PostMapping(value = "/addRole") 53 | public Result add(@RequestBody SysRole sysRole) { 54 | return sysRoleService.add(sysRole); 55 | } 56 | 57 | @Operation(summary = "查询角色资源ID") 58 | @PostMapping(value = "/getRoleResourceId") 59 | public Result> getRoleResourceId(@RequestBody Long roleId) { 60 | return sysRoleService.getRoleResourceId(roleId); 61 | } 62 | 63 | @Operation(summary = "修改角色资源信息") 64 | @PostMapping(value = "/updateRoleResourceInfo") 65 | public Result updateRoleResourceInfo(@RequestBody RoleParam roleParam) { 66 | return sysRoleService.updateRoleResourceInfo(roleParam.getRoleId(), roleParam.getResourceId()); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/controller/SysRouteController.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.controller; 2 | 3 | import com.pea.business.sys.service.SysResourceService; 4 | import com.pea.business.sys.vo.SysRoutesVO; 5 | import com.pea.common.api.Result; 6 | import io.swagger.v3.oas.annotations.Operation; 7 | import io.swagger.v3.oas.annotations.tags.Tag; 8 | import lombok.RequiredArgsConstructor; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestHeader; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | @Slf4j 19 | @RestController 20 | @RequiredArgsConstructor 21 | @Tag(name = "SysRouteController", description = "路由信息控制层") 22 | @RequestMapping("/route") 23 | public class SysRouteController { 24 | 25 | private final SysResourceService sysResourceService; 26 | 27 | @Operation(summary = "查询用户路由信息") 28 | @GetMapping(value = "/getUserRoutes") 29 | public Result> getUserRoutes(@RequestHeader("Authorization") String authorizationHeader) { 30 | return sysResourceService.getUserRoutes(authorizationHeader); 31 | } 32 | 33 | @Operation(summary = "查询基础路由信息") 34 | @GetMapping(value = "/getConstantRoutes") 35 | public Result> getConstantRoutes() { 36 | return sysResourceService.getConstantRoutes(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/controller/SysUserController.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.controller; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.pea.business.sys.param.CreateUserParam; 5 | import com.pea.business.sys.service.SysUserService; 6 | import com.pea.business.sys.vo.SysUserVO; 7 | import com.pea.common.annotation.SysLogInterface; 8 | import com.pea.common.api.Result; 9 | import com.pea.common.enums.BusinessType; 10 | import io.swagger.v3.oas.annotations.Operation; 11 | import io.swagger.v3.oas.annotations.Parameter; 12 | import io.swagger.v3.oas.annotations.Parameters; 13 | import io.swagger.v3.oas.annotations.tags.Tag; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.security.access.prepost.PreAuthorize; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import java.util.Map; 20 | 21 | @Slf4j 22 | @RestController 23 | @RequiredArgsConstructor 24 | @Tag(name = "SysUserController", description = "用户信息控制层") 25 | //@RequestMapping("/sys/user") 26 | @RequestMapping("/systemManage") 27 | public class SysUserController { 28 | 29 | private final SysUserService sysUserService; 30 | 31 | @Operation(summary = "list 分页列表") 32 | @Parameters({ 33 | @Parameter(name = "current", description = "当前页", required = true, example = "1"), 34 | @Parameter(name = "size", description = "每页显示条数", required = true, example = "10"), 35 | @Parameter(name = "username", description = "用户名称"), 36 | }) 37 | @GetMapping(value = "/getUserList") 38 | @SysLogInterface(title = "查询用户信息", businessType = BusinessType.OTHER) 39 | public Result> list(@Parameter(hidden = true) @RequestParam Map params) { 40 | IPage sysUsers = sysUserService.getList(params); 41 | return Result.success(sysUsers); 42 | } 43 | 44 | @Operation(summary = "创建用户信息") 45 | @PostMapping(value = "/createUser") 46 | @SysLogInterface(title = "创建用户信息", businessType = BusinessType.INSERT) 47 | public Result createUser(@RequestBody CreateUserParam createUserParam) { 48 | return sysUserService.createUser(createUserParam); 49 | } 50 | 51 | @Operation(summary = "修改用户信息") 52 | @PostMapping(value = "/update") 53 | @SysLogInterface(title = "修改用户信息", businessType = BusinessType.UPDATE) 54 | public Result updateUser(@RequestBody CreateUserParam createUserParam) { 55 | return sysUserService.updateUser(createUserParam); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/domain/SysOperationLog.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import com.pea.common.mybatis.base.BaseEntity; 9 | import io.swagger.v3.oas.annotations.media.Schema; 10 | import lombok.Data; 11 | import lombok.EqualsAndHashCode; 12 | 13 | import java.io.Serial; 14 | import java.io.Serializable; 15 | 16 | @Data 17 | @TableName(value = "t_sys_operation_log", autoResultMap = true) 18 | @EqualsAndHashCode(callSuper = true) 19 | public class SysOperationLog extends BaseEntity implements Serializable { 20 | 21 | @Serial 22 | private static final long serialVersionUID = 1L; 23 | 24 | @TableId(value = "id", type = IdType.AUTO) 25 | @Schema(description = "ID") 26 | private Long id; 27 | 28 | @Schema(description = "类型(0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据)") 29 | private Integer BusinessType; 30 | 31 | @Schema(description = "方法名称") 32 | private String method; 33 | 34 | @Schema(description = "请求方式") 35 | private String requestMethod; 36 | 37 | @Schema(description = "描述") 38 | private String description; 39 | 40 | @Schema(description = "请求IP") 41 | private String reqIp; 42 | 43 | @Schema(description = "操作人") 44 | private String operName; 45 | 46 | @Schema(description = "操作地点") 47 | private String operLocation; 48 | 49 | @Schema(description = "请求信息") 50 | private String reqParam; 51 | 52 | @Schema(description = "响应信息") 53 | private String resp; 54 | 55 | @Schema(description = "错误消息") 56 | private String errorMsg; 57 | 58 | @Schema(description = "状态") 59 | private String status; 60 | 61 | /** 62 | * 开始时间 63 | */ 64 | @JsonIgnore 65 | @TableField(exist = false) 66 | private String beginTime; 67 | 68 | /** 69 | * 结束时间 70 | */ 71 | @JsonIgnore 72 | @TableField(exist = false) 73 | private String endTime; 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/domain/SysResource.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.pea.common.mybatis.base.BaseEntity; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | 11 | import java.io.Serial; 12 | import java.io.Serializable; 13 | 14 | @Data 15 | @TableName(value = "t_sys_resource", autoResultMap = true) 16 | @EqualsAndHashCode(callSuper = true) 17 | public class SysResource extends BaseEntity implements Serializable { 18 | 19 | @Serial 20 | private static final long serialVersionUID = 1L; 21 | 22 | @TableId(value = "id", type = IdType.AUTO) 23 | @Schema(description = "资源ID") 24 | private Long id; 25 | 26 | @Schema(description = "父节点ID") 27 | private Integer parentId; 28 | 29 | @Schema(description = "唯一标识路径") 30 | private String uiPath; 31 | 32 | @Schema(description = "资源类型:1-菜单路由,2-资源(按钮等)", example = "1") 33 | private String menuType; 34 | 35 | @Schema(description = "状态:1-可用,2-禁用", example = "1") 36 | private String status; 37 | 38 | @Schema(description = "名称") 39 | private String menuName; 40 | 41 | @Schema(description = "路由名称") 42 | private String routeName; 43 | 44 | @Schema(description = "菜单路由路径或其他资源的唯一标识") 45 | private String routePath; 46 | 47 | @Schema(description = "布局方式") 48 | private String component; 49 | 50 | @Schema(description = "元数据") 51 | private String meta; 52 | 53 | @Schema(description = "权重顺序") 54 | private Integer weight; 55 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/domain/SysRole.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.pea.common.mybatis.base.BaseEntity; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | 11 | import java.io.Serial; 12 | import java.io.Serializable; 13 | 14 | @Data 15 | @TableName(value = "t_sys_role", autoResultMap = true) 16 | @EqualsAndHashCode(callSuper = false) 17 | public class SysRole extends BaseEntity implements Serializable { 18 | 19 | @Serial 20 | private static final long serialVersionUID = 1L; 21 | 22 | @TableId(value = "id", type = IdType.AUTO) 23 | @Schema(description = "角色ID") 24 | private Long id; 25 | 26 | @Schema(description = "角色名称", minLength = 1, maxLength = 16) 27 | private String roleName; 28 | 29 | @Schema(description = "角色编码", minLength = 1, maxLength = 64) 30 | private String roleCode; 31 | 32 | @Schema(description = "状态:1-可用,2-禁用", example = "1") 33 | private String status; 34 | 35 | @Schema(description = "备注信息") 36 | private String roleDesc; 37 | 38 | @Schema(description = "角色类型:1-公共角色,2-特殊角色", example = "1") 39 | private Integer type; 40 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/domain/SysRoleResource.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.pea.common.mybatis.base.BaseEntity; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | 11 | import java.io.Serial; 12 | import java.io.Serializable; 13 | 14 | 15 | @Data 16 | @TableName(value = "t_sys_role_resource", autoResultMap = true) 17 | @EqualsAndHashCode(callSuper = false) 18 | public class SysRoleResource extends BaseEntity implements Serializable { 19 | 20 | @Serial 21 | private static final long serialVersionUID = 1L; 22 | 23 | @TableId(value = "id", type = IdType.AUTO) 24 | @Schema(description = "角色资源关联ID") 25 | private Long id; 26 | 27 | @Schema(description = "角色ID") 28 | private Long roleId; 29 | 30 | @Schema(description = "资源ID") 31 | private Long resourceId; 32 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/domain/SysUser.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.domain; 2 | 3 | import cn.hutool.core.date.DatePattern; 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.fasterxml.jackson.annotation.JsonFormat; 8 | import com.pea.common.mybatis.base.BaseEntity; 9 | import io.swagger.v3.oas.annotations.media.Schema; 10 | import lombok.Data; 11 | import lombok.EqualsAndHashCode; 12 | 13 | import java.io.Serial; 14 | import java.io.Serializable; 15 | import java.time.LocalDateTime; 16 | 17 | @Data 18 | @TableName(value = "t_sys_user", autoResultMap = true) 19 | @EqualsAndHashCode(callSuper = true) 20 | public class SysUser extends BaseEntity implements Serializable { 21 | 22 | @Serial 23 | private static final long serialVersionUID = 1L; 24 | 25 | @TableId(value = "id", type = IdType.AUTO) 26 | @Schema(description = "用户ID") 27 | private Long id; 28 | 29 | @Schema(description = "昵称") 30 | private String nickName; 31 | 32 | @Schema(description = "登录用户名") 33 | private String userName; 34 | 35 | @Schema(description = "密码") 36 | private String password; 37 | 38 | @Schema(description = "性别") 39 | private String userGender; 40 | 41 | @Schema(description = "状态:1-可用,2-禁用", example = "1") 42 | private String status; 43 | 44 | @Schema(description = "OTP密钥") 45 | private String otpSecret; 46 | 47 | @Schema(description = "电话") 48 | private String userPhone; 49 | 50 | @Schema(description = "电子邮箱") 51 | private String userEmail; 52 | 53 | @Schema(description = "最后登录时间") 54 | @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8") 55 | private LocalDateTime lastLoginTime; 56 | 57 | @Schema(description = "最后登录IP") 58 | private String lastLoginIp; 59 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/domain/SysUserRole.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.pea.common.mybatis.base.BaseEntity; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.Data; 9 | import lombok.EqualsAndHashCode; 10 | 11 | import java.io.Serial; 12 | import java.io.Serializable; 13 | 14 | @Data 15 | @TableName(value = "t_sys_user_role", autoResultMap = true) 16 | @EqualsAndHashCode(callSuper = true) 17 | public class SysUserRole extends BaseEntity implements Serializable { 18 | 19 | @Serial 20 | private static final long serialVersionUID = 1L; 21 | 22 | @TableId(value = "id", type = IdType.AUTO) 23 | @Schema(description = "id") 24 | private Long id; 25 | 26 | @Schema(description = "用户Id") 27 | private Long userId; 28 | 29 | @Schema(description = "角色Id") 30 | private Long roleId; 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/mapper/SysOperationLogMapper.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.pea.business.sys.domain.SysOperationLog; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface SysOperationLogMapper extends BaseMapper { 9 | 10 | int clean(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/mapper/SysResourceMapper.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.pea.business.sys.domain.SysResource; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface SysResourceMapper extends BaseMapper { 11 | 12 | List getUserRoutes(String id); 13 | 14 | List getUserPermissions(Long id); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/mapper/SysRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.pea.business.sys.domain.SysRole; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface SysRoleMapper extends BaseMapper { 11 | 12 | List getUserRole(Long id); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/mapper/SysRoleResourceMapper.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.pea.business.sys.domain.SysRoleResource; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 角色资源关联表 9 | */ 10 | @Mapper 11 | public interface SysRoleResourceMapper extends BaseMapper { 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/mapper/SysUserMapper.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.pea.business.sys.domain.SysUser; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 用户表 9 | */ 10 | @Mapper 11 | public interface SysUserMapper extends BaseMapper { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/mapper/SysUserRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.pea.business.sys.domain.SysUserRole; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * 用户角色关联表 9 | */ 10 | @Mapper 11 | public interface SysUserRoleMapper extends BaseMapper { 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/param/CreateUserParam.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.param; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class CreateUserParam { 10 | 11 | @Schema(description = "用户ID") 12 | private Long id; 13 | 14 | @Schema(description = "昵称") 15 | private String nickName; 16 | 17 | @Schema(description = "用户名") 18 | private String userName; 19 | 20 | @Schema(description = "密码") 21 | private String password; 22 | 23 | @Schema(description = "性别") 24 | private String userGender; 25 | 26 | @Schema(description = "状态:1-可用,2-禁用") 27 | private String status; 28 | 29 | @Schema(description = "电话") 30 | private String userPhone; 31 | 32 | @Schema(description = "电子邮箱") 33 | private String userEmail; 34 | 35 | @Schema(description = "角色信息") 36 | private List userRoles; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/param/ResourceParam.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.param; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class ResourceParam { 10 | 11 | @Schema(description = "资源ID") 12 | private Long id; 13 | 14 | @Schema(description = "父节点ID") 15 | private Integer parentId; 16 | 17 | @Schema(description = "菜单类型:1-菜单路由,2-资源(按钮等)") 18 | private String menuType; 19 | 20 | @Schema(description = "资源名称") 21 | private String menuName; 22 | 23 | @Schema(description = "路由名称") 24 | private String routeName; 25 | 26 | @Schema(description = "路由路径") 27 | private String routePath; 28 | 29 | @Schema(description = "路径参数") 30 | private String pathParam; 31 | 32 | @Schema(description = "组件路径或名称") 33 | private String component; 34 | 35 | @Schema(description = "布局类型") 36 | private String layout; 37 | 38 | @Schema(description = "页面关联标识") 39 | private String page; 40 | 41 | @Schema(description = "国际化键") 42 | private String i18nKey; 43 | 44 | @Schema(description = "图标") 45 | private String icon; 46 | 47 | @Schema(description = "本地图标路径") 48 | private String localIcon; 49 | 50 | @Schema(description = "图标类型:1-默认类型") 51 | private String iconType; 52 | 53 | @Schema(description = "状态:1-可用,2-禁用") 54 | private String status; 55 | 56 | @Schema(description = "是否缓存") 57 | private Boolean keepAlive; 58 | 59 | @Schema(description = "是否为常量资源") 60 | private Boolean constant; 61 | 62 | @Schema(description = "排序号") 63 | private Integer order; 64 | 65 | @Schema(description = "外部链接") 66 | private String href; 67 | 68 | @Schema(description = "是否在菜单中隐藏") 69 | private Boolean hideInMenu; 70 | 71 | @Schema(description = "激活时对应的菜单路径") 72 | private String activeMenu; 73 | 74 | @Schema(description = "是否支持多标签页") 75 | private Boolean multiTab; 76 | 77 | @Schema(description = "在多标签页中的固定索引") 78 | private Integer fixedIndexInTab; 79 | 80 | @Schema(description = "角色列表,逗号分隔") 81 | private List roles; 82 | 83 | @Schema(description = "查询参数集合") 84 | private List query; 85 | 86 | @Schema(description = "按钮权限集合") 87 | private List buttons; 88 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/param/RoleParam.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.param; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class RoleParam { 10 | 11 | 12 | @Schema(description = "角色ID") 13 | private Long roleId; 14 | 15 | @Schema(description = "资源ID") 16 | private List resourceId; 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/SysOperationLogService.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.pea.business.sys.domain.SysOperationLog; 5 | 6 | import java.util.List; 7 | 8 | public interface SysOperationLogService extends IService { 9 | 10 | /** 11 | * 清空 12 | * 13 | * @return 14 | */ 15 | int clean(); 16 | 17 | /** 18 | * 导出 19 | * 20 | * @param sysOperationLog 21 | * @return 22 | */ 23 | List getExportList(SysOperationLog sysOperationLog); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/SysResourceService.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.pea.business.sys.domain.SysResource; 6 | import com.pea.business.sys.vo.SysMenuTreeVO; 7 | import com.pea.business.sys.vo.SysMenuVO; 8 | import com.pea.business.sys.vo.SysRoutesVO; 9 | import com.pea.common.api.Result; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public interface SysResourceService extends IService { 15 | 16 | Result> getUserRoutes(String authorizationHeader); 17 | 18 | Result> getMenuList(Map params); 19 | 20 | Result> getConstantRoutes(); 21 | 22 | List getUserPermissions(Long id); 23 | 24 | Result> getAllPages(); 25 | 26 | Result> getMenuTree(); 27 | 28 | Result add(SysResource sysResource); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/SysRoleResourceService.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.pea.business.sys.domain.SysRoleResource; 5 | import com.pea.common.api.Result; 6 | 7 | import java.util.List; 8 | 9 | public interface SysRoleResourceService extends IService { 10 | 11 | /** 12 | * 绑定角色资源信息 13 | * 14 | * @param roleId 角色Id 15 | * @param longList 资源Id 16 | * @return boolean 17 | */ 18 | Result bindingRoleBasicResource(Long roleId, List longList); 19 | 20 | Result> getRoleResourceId(Long roleId); 21 | 22 | Boolean deleteDataByRoleId(Long roleId); 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/SysRoleService.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.pea.business.sys.domain.SysRole; 6 | import com.pea.business.sys.vo.SysRoleVO; 7 | import com.pea.common.api.Result; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public interface SysRoleService extends IService { 13 | 14 | /** 15 | * 分页获取数据 16 | * 17 | * @param params 查询参数 18 | * @return IPage 19 | */ 20 | IPage getPage(Map params); 21 | 22 | Result> getAllRoles(String authorizationHeader); 23 | 24 | List getUserRole(Long id); 25 | 26 | List queryRoleListByRoleCode(List roleCode); 27 | 28 | Result add(SysRole sysRole); 29 | 30 | Result> getRoleResourceId(Long roleId); 31 | 32 | Result updateRoleResourceInfo(Long roleId, List resourceId); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/SysUserRoleService.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.pea.business.sys.domain.SysUserRole; 5 | import com.pea.common.api.Result; 6 | 7 | import java.util.List; 8 | 9 | public interface SysUserRoleService extends IService { 10 | 11 | /** 12 | * 绑定用户角色 13 | * 14 | * @param roleCodes 角色code集合 15 | * @param userId 用户Id 16 | * @return boolean 17 | */ 18 | Result bindingUserRoles(List roleCodes, Long userId); 19 | 20 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/SysUserService.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.pea.business.auth.vo.LoginResult; 6 | import com.pea.business.sys.domain.SysUser; 7 | import com.pea.business.sys.param.CreateUserParam; 8 | import com.pea.business.sys.vo.SysUserVO; 9 | import com.pea.business.sys.vo.UserInfoVO; 10 | import com.pea.common.api.Result; 11 | 12 | import java.util.Map; 13 | 14 | public interface SysUserService extends IService { 15 | 16 | /** 17 | * 分页获取数据 18 | * 19 | * @param params 查询参数 20 | * @return IPage 21 | */ 22 | IPage getList(Map params); 23 | 24 | /** 25 | * 登录 26 | * 27 | * @param username 用户名 28 | * @param password 密码 29 | * @return 生成的JWT的token 30 | */ 31 | LoginResult login(String username, String password); 32 | 33 | /** 34 | * 创建用户 35 | * 36 | * @param createUserParam 创建用户信息 37 | * @return 创建结果 38 | */ 39 | Result createUser(CreateUserParam createUserParam); 40 | 41 | /** 42 | * 获取用户信息 43 | * 44 | * @return UserInfoVO 45 | */ 46 | Result getUserInfo(String authorizationHeader); 47 | 48 | /** 49 | * 修改用户信息 50 | * 51 | * @param createUserParam 修改用户信息 52 | * @return 修改结果 53 | */ 54 | Result updateUser(CreateUserParam createUserParam); 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/impl/SysOperationLogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.pea.business.sys.domain.SysOperationLog; 5 | import com.pea.business.sys.mapper.SysOperationLogMapper; 6 | import com.pea.business.sys.service.SysOperationLogService; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | 13 | @Slf4j 14 | @Service 15 | @RequiredArgsConstructor 16 | public class SysOperationLogServiceImpl extends ServiceImpl 17 | implements SysOperationLogService { 18 | 19 | @Override 20 | public int clean() { 21 | return this.baseMapper.clean(); 22 | } 23 | 24 | @Override 25 | public List getExportList(SysOperationLog sysOperationLog) { 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/impl/SysRoleResourceServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service.impl; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.pea.business.sys.domain.SysRoleResource; 7 | import com.pea.business.sys.mapper.SysRoleResourceMapper; 8 | import com.pea.business.sys.service.SysRoleResourceService; 9 | import com.pea.common.api.Result; 10 | import com.pea.common.enums.DelStatusEnums; 11 | import com.pea.common.exception.GlobalException; 12 | import lombok.RequiredArgsConstructor; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | @Slf4j 21 | @Service 22 | @RequiredArgsConstructor 23 | public class SysRoleResourceServiceImpl extends ServiceImpl 24 | implements SysRoleResourceService { 25 | 26 | /** 27 | * 绑定角色基础资源信息 28 | * 29 | * @param roleId 角色Id 30 | * @return boolean 31 | */ 32 | @Override 33 | @Transactional(rollbackFor = GlobalException.class) 34 | public Result bindingRoleBasicResource(Long roleId, List longList) { 35 | 36 | log.info("绑定角色基础资源信息 -> 角色Id: {} , 资源信息: {}", roleId, JSONUtil.parse(longList)); 37 | 38 | List sysRoleResourceList = new ArrayList<>(); 39 | 40 | longList.forEach(item -> { 41 | SysRoleResource sysRoleResource = new SysRoleResource(); 42 | sysRoleResource.setRoleId(roleId); 43 | sysRoleResource.setResourceId(item); 44 | sysRoleResource.setIsDeleted(DelStatusEnums.DISABLE.getCode()); 45 | sysRoleResourceList.add(sysRoleResource); 46 | }); 47 | 48 | return Result.success(saveBatch(sysRoleResourceList)); 49 | } 50 | 51 | @Override 52 | public Result> getRoleResourceId(Long roleId) { 53 | 54 | 55 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); 56 | queryWrapper.eq(SysRoleResource::getRoleId, roleId); 57 | queryWrapper.eq(SysRoleResource::getIsDeleted, DelStatusEnums.DISABLE.getCode()); 58 | 59 | List roleResources = list(queryWrapper); 60 | 61 | List longList = new ArrayList<>(); 62 | 63 | for (SysRoleResource roleResource : roleResources) { 64 | longList.add(roleResource.getResourceId()); 65 | } 66 | 67 | return Result.success(longList); 68 | } 69 | 70 | @Override 71 | public Boolean deleteDataByRoleId(Long roleId) { 72 | 73 | log.info("删除角色下所有资源信息 -> 角色ID: {}", roleId); 74 | 75 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); 76 | queryWrapper.eq(SysRoleResource::getRoleId, roleId); 77 | 78 | return remove(queryWrapper); 79 | } 80 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/impl/SysRoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service.impl; 2 | 3 | import cn.hutool.core.util.ObjUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.json.JSONUtil; 6 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 7 | import com.baomidou.mybatisplus.core.metadata.IPage; 8 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 9 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 10 | import com.pea.business.sys.domain.SysRole; 11 | import com.pea.business.sys.mapper.SysRoleMapper; 12 | import com.pea.business.sys.service.SysRoleResourceService; 13 | import com.pea.business.sys.service.SysRoleService; 14 | import com.pea.business.sys.vo.SysRoleVO; 15 | import com.pea.common.api.Result; 16 | import com.pea.common.enums.DelStatusEnums; 17 | import com.pea.common.enums.StatusEnums; 18 | import com.pea.common.exception.GlobalException; 19 | import com.pea.common.exception.GlobalExceptionEnum; 20 | import com.pea.common.utils.JwtTokenUtil; 21 | import lombok.RequiredArgsConstructor; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.springframework.stereotype.Service; 24 | 25 | import java.util.*; 26 | 27 | @Slf4j 28 | @Service 29 | @RequiredArgsConstructor 30 | public class SysRoleServiceImpl extends ServiceImpl 31 | implements SysRoleService { 32 | 33 | final JwtTokenUtil jwtTokenUtil; 34 | 35 | final SysRoleResourceService sysRoleResourceService; 36 | 37 | @Override 38 | public IPage getPage(Map params) { 39 | int pageSize = Integer.parseInt(String.valueOf(params.get("size"))); 40 | int pageNum = Integer.parseInt(String.valueOf(params.get("current"))); 41 | LambdaQueryWrapper wrapper = createWrapper(params); 42 | 43 | return page(new Page<>(pageNum, pageSize), wrapper); 44 | } 45 | 46 | private LambdaQueryWrapper createWrapper(Map params) { 47 | String roleName = (String) params.get("roleName"); 48 | String roleCode = (String) params.get("roleCode"); 49 | String status = (String) params.get("status"); 50 | 51 | LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); 52 | wrapper.like(StrUtil.isNotEmpty(roleName), SysRole::getRoleName, roleName); 53 | wrapper.like(StrUtil.isNotEmpty(roleCode), SysRole::getRoleCode, roleCode); 54 | wrapper.eq(StrUtil.isNotEmpty(status), SysRole::getStatus, status); 55 | wrapper.eq( SysRole::getIsDeleted, DelStatusEnums.DISABLE.getCode()); 56 | 57 | return wrapper; 58 | } 59 | 60 | @Override 61 | public Result> getAllRoles(String authorizationHeader) { 62 | if (authorizationHeader == null || !authorizationHeader.startsWith(jwtTokenUtil.getTokenHead())) { 63 | throw new GlobalException(GlobalExceptionEnum.ERROR_UNAUTHORIZED.getMessage()); 64 | } 65 | 66 | List sysRoleVOS = new ArrayList<>(); 67 | 68 | LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); 69 | wrapper.eq(SysRole::getStatus, StatusEnums.ENABLE.getCode()); 70 | 71 | List list = list(wrapper); 72 | if (!list.isEmpty()) { 73 | for (SysRole sysRole : list) { 74 | SysRoleVO sysRoleVO = new SysRoleVO(); 75 | sysRoleVO.setRoleName(sysRole.getRoleName()); 76 | sysRoleVO.setRoleCode(sysRole.getRoleCode()); 77 | sysRoleVO.setRoleDesc(sysRole.getRoleDesc()); 78 | sysRoleVOS.add(sysRoleVO); 79 | } 80 | return Result.success(sysRoleVOS); 81 | } 82 | return Result.success(); 83 | } 84 | 85 | @Override 86 | public List getUserRole(Long id) { 87 | return baseMapper.getUserRole(id); 88 | } 89 | 90 | @Override 91 | public List queryRoleListByRoleCode(List roleCode) { 92 | 93 | LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); 94 | lambdaQueryWrapper.in(SysRole::getRoleCode, roleCode); 95 | lambdaQueryWrapper.eq(SysRole::getStatus,StatusEnums.ENABLE.getCode()); 96 | 97 | return list(lambdaQueryWrapper); 98 | } 99 | 100 | @Override 101 | public Result add(SysRole sysRole) { 102 | 103 | if (sysRole.getId() > 0) { 104 | log.info("修改角色对象入参: {}", JSONUtil.parse(sysRole)); 105 | return Result.success(updateById(sysRole)); 106 | } else { 107 | 108 | sysRole.setStatus(StatusEnums.ENABLE.getCode()); 109 | sysRole.setIsDeleted(DelStatusEnums.DISABLE.getCode()); 110 | sysRole.setType(1); 111 | log.info("添加角色对象入参: {}", JSONUtil.parse(sysRole)); 112 | 113 | save(sysRole); 114 | 115 | LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); 116 | lambdaQueryWrapper.in(SysRole::getRoleCode, sysRole.getRoleCode()); 117 | lambdaQueryWrapper.eq(SysRole::getIsDeleted, DelStatusEnums.DISABLE.getCode()); 118 | 119 | SysRole sysRole1 = getOne(lambdaQueryWrapper); 120 | 121 | 122 | // 给角色绑定基础资源权限 123 | sysRoleResourceService.bindingRoleBasicResource(sysRole1.getId(), new ArrayList<>(Collections.singletonList(1L))); 124 | 125 | return Result.success(); 126 | } 127 | } 128 | 129 | @Override 130 | public Result> getRoleResourceId(Long roleId) { 131 | return sysRoleResourceService.getRoleResourceId(roleId); 132 | } 133 | 134 | @Override 135 | public Result updateRoleResourceInfo(Long roleId, List resourceId) { 136 | log.info("修改角色资源信息入参: 角色ID: {} , 资源ID: {}", roleId, JSONUtil.parse(resourceId)); 137 | 138 | sysRoleResourceService.deleteDataByRoleId(roleId); 139 | 140 | sysRoleResourceService.bindingRoleBasicResource(roleId, resourceId); 141 | 142 | return Result.success(); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/service/impl/SysUserRoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.service.impl; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.pea.business.sys.domain.SysRole; 7 | import com.pea.business.sys.domain.SysUserRole; 8 | import com.pea.business.sys.mapper.SysUserRoleMapper; 9 | import com.pea.business.sys.service.SysRoleService; 10 | import com.pea.business.sys.service.SysUserRoleService; 11 | import com.pea.common.api.Result; 12 | import com.pea.common.enums.DelStatusEnums; 13 | import com.pea.common.exception.GlobalException; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | @Slf4j 23 | @Service 24 | @RequiredArgsConstructor 25 | public class SysUserRoleServiceImpl extends ServiceImpl 26 | implements SysUserRoleService { 27 | 28 | 29 | final SysRoleService sysRoleService; 30 | 31 | public void delUserRoleInfo(Long userId) { 32 | 33 | log.info("根据用户ID删除用户角色: {}", userId); 34 | 35 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); 36 | queryWrapper.eq(SysUserRole::getUserId, userId); 37 | 38 | baseMapper.delete(queryWrapper); 39 | } 40 | 41 | /** 42 | * 绑定用户角色 43 | * 44 | * @param roleCodes 角色code集合 45 | * @param userId 用户Id 46 | * @return boolean 47 | */ 48 | @Override 49 | @Transactional(rollbackFor = GlobalException.class) 50 | public Result bindingUserRoles(List roleCodes, Long userId) { 51 | 52 | // 先删除 53 | delUserRoleInfo(userId); 54 | 55 | List sysRoles = sysRoleService.queryRoleListByRoleCode(roleCodes); 56 | 57 | if (sysRoles.isEmpty()) { 58 | log.error("bindingUserRoles -> 入参: {}", JSONUtil.parse(roleCodes)); 59 | throw new GlobalException("未找到传入角色CODE信息"); 60 | } 61 | 62 | List userRoles = new ArrayList<>(); 63 | 64 | for (SysRole sysRole : sysRoles) { 65 | SysUserRole sysUserRole = new SysUserRole(); 66 | sysUserRole.setRoleId(sysRole.getId()); 67 | sysUserRole.setUserId(userId); 68 | sysUserRole.setIsDeleted(DelStatusEnums.DISABLE.getCode()); 69 | 70 | userRoles.add(sysUserRole); 71 | } 72 | 73 | log.info("绑定用户角色 -> saveBatch: {}", JSONUtil.parse(userRoles)); 74 | 75 | return Result.success(saveBatch(userRoles)); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/vo/SysMenuTreeVO.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.vo; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.io.Serial; 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | @Schema(description = "菜单VO") 12 | @Data 13 | @EqualsAndHashCode(callSuper = false) 14 | public class SysMenuTreeVO implements Serializable { 15 | 16 | @Serial 17 | private static final long serialVersionUID = 1L; 18 | 19 | @Schema(description = "资源ID") 20 | private Long id; 21 | 22 | @Schema(description = "父节点ID") 23 | private Integer pId; 24 | 25 | @Schema(description = "路由名称") 26 | private String label; 27 | 28 | @Schema(description = "children") 29 | private List children; 30 | 31 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/vo/SysMenuVO.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.vo; 2 | 3 | import com.pea.common.model.Meta; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | import java.io.Serial; 9 | import java.io.Serializable; 10 | import java.util.List; 11 | 12 | @Schema(description = "菜单VO") 13 | @Data 14 | @EqualsAndHashCode(callSuper = true) 15 | public class SysMenuVO extends Meta implements Serializable { 16 | 17 | @Serial 18 | private static final long serialVersionUID = 1L; 19 | 20 | @Schema(description = "资源ID") 21 | private Long id; 22 | 23 | @Schema(description = "父节点ID") 24 | private Integer parentId; 25 | 26 | @Schema(description = "唯一标识路径") 27 | private String uiPath; 28 | 29 | @Schema(description = "资源类型:1-菜单路由,2-资源(按钮等)", example = "1") 30 | private String menuType; 31 | 32 | @Schema(description = "状态:1-可用,2-禁用", example = "1") 33 | private String status; 34 | 35 | @Schema(description = "名称") 36 | private String menuName; 37 | 38 | @Schema(description = "路由名称") 39 | private String routeName; 40 | 41 | @Schema(description = "菜单路由路径或其他资源的唯一标识") 42 | private String routePath; 43 | 44 | @Schema(description = "布局方式") 45 | private String component; 46 | 47 | @Schema(description = "权重顺序") 48 | private Integer weight; 49 | 50 | @Schema(description = "children") 51 | private List children; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/vo/SysRoleVO.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.vo; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.io.Serial; 8 | import java.io.Serializable; 9 | 10 | @Schema(description = "用户角色VO") 11 | @Data 12 | @EqualsAndHashCode(callSuper = false) 13 | public class SysRoleVO implements Serializable { 14 | 15 | @Serial 16 | private static final long serialVersionUID = 1L; 17 | 18 | @Schema(description = "角色名称") 19 | private String roleName; 20 | 21 | @Schema(description = "角色编码") 22 | private String roleCode; 23 | 24 | @Schema(description = "备注信息") 25 | private String roleDesc; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/vo/SysRoutesVO.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.vo; 2 | 3 | import cn.hutool.json.JSON; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | import java.io.Serial; 9 | import java.io.Serializable; 10 | import java.util.List; 11 | 12 | @Schema(description = "路由VO") 13 | @Data 14 | @EqualsAndHashCode(callSuper = false) 15 | public class SysRoutesVO implements Serializable { 16 | 17 | @Serial 18 | private static final long serialVersionUID = 1L; 19 | 20 | @Schema(description = "名称") 21 | private String name; 22 | 23 | @Schema(description = "菜单路由路径") 24 | private String path; 25 | 26 | @Schema(description = "布局方式") 27 | private String component; 28 | 29 | @Schema(description = "元数据") 30 | private JSON meta; 31 | 32 | @Schema(description = "children") 33 | private List children; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/vo/SysUserVO.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.vo; 2 | 3 | import cn.hutool.core.date.DatePattern; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import com.pea.common.mybatis.base.BaseEntity; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.io.Serial; 11 | import java.io.Serializable; 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | 15 | @Schema(description = "用户视图VO") 16 | @Data 17 | @EqualsAndHashCode(callSuper = true) 18 | public class SysUserVO extends BaseEntity implements Serializable { 19 | 20 | @Serial 21 | private static final long serialVersionUID = 1L; 22 | 23 | @Schema(description = "用户ID") 24 | private Long id; 25 | 26 | @Schema(description = "昵称") 27 | private String nickName; 28 | 29 | @Schema(description = "登录用户名") 30 | private String userName; 31 | 32 | @Schema(description = "性别") 33 | private String userGender; 34 | 35 | @Schema(description = "状态:1-可用,2-禁用", example = "1") 36 | private String status; 37 | 38 | @Schema(description = "电话") 39 | private String userPhone; 40 | 41 | @Schema(description = "电子邮箱") 42 | private String userEmail; 43 | 44 | @Schema(description = "最后登录时间") 45 | @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8") 46 | private LocalDateTime lastLoginTime; 47 | 48 | @Schema(description = "最后登录IP") 49 | private String lastLoginIp; 50 | 51 | @Schema(description = "用户角色") 52 | private List userRoles; 53 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/business/sys/vo/UserInfoVO.java: -------------------------------------------------------------------------------- 1 | package com.pea.business.sys.vo; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.io.Serial; 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | @Schema(description = "用户信息VO") 12 | @Data 13 | @EqualsAndHashCode(callSuper = false) 14 | public class UserInfoVO implements Serializable { 15 | 16 | @Serial 17 | private static final long serialVersionUID = 1L; 18 | 19 | @Schema(description = "登录ID") 20 | private Long userId; 21 | 22 | @Schema(description = "登录用户名") 23 | private String userName; 24 | 25 | @Schema(description = "角色") 26 | private List roles; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/annotation/SysLogInterface.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.annotation; 2 | 3 | import com.pea.common.enums.BusinessType; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(ElementType.METHOD) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Documented 10 | public @interface SysLogInterface { 11 | 12 | /** 13 | * 模块 14 | */ 15 | String title() default ""; 16 | 17 | /** 18 | * 功能 19 | */ 20 | BusinessType businessType() default BusinessType.OTHER; 21 | 22 | /** 23 | * 是否保存请求的参数 24 | */ 25 | boolean isSaveRequestData() default true; 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/api/IErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.api; 2 | 3 | /** 4 | * 封装API的错误码 5 | */ 6 | public interface IErrorCode { 7 | 8 | String getCode(); 9 | 10 | String getMessage(); 11 | 12 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/api/Result.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.api; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | import java.io.Serial; 7 | import java.io.Serializable; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | 12 | /** 13 | * 通用返回对象 14 | */ 15 | @Data 16 | public class Result implements Serializable { 17 | 18 | @Serial 19 | private static final long serialVersionUID = 1L; 20 | 21 | @Schema(description = "响应状态码", example = "0000") 22 | private String code; 23 | 24 | @Schema(description = "响应消息") 25 | private String msg; 26 | 27 | @Schema(description = "响应数据") 28 | private T data; 29 | 30 | protected Result(String code, String message, T data) { 31 | this.code = code; 32 | this.msg = message; 33 | this.data = data; 34 | } 35 | 36 | /** 37 | * 成功返回结果 38 | * @param data 获取的数据 39 | */ 40 | public static Result success(T data) { 41 | return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), 42 | data); 43 | } 44 | 45 | /** 46 | * 成功返回结果 47 | */ 48 | public static Result success() { 49 | return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), 50 | null); 51 | } 52 | 53 | /** 54 | * 成功返回结果 55 | * @param data 获取的数据 56 | * @param message 提示信息 57 | */ 58 | public static Result success(T data, String message) { 59 | return new Result<>(ResultCode.SUCCESS.getCode(), message, data); 60 | } 61 | 62 | /** 63 | * 失败返回结果 64 | * @param errorCode 错误码 65 | */ 66 | public static Result failed(IErrorCode errorCode) { 67 | return new Result<>(errorCode.getCode(), errorCode.getMessage(), null); 68 | } 69 | 70 | /** 71 | * 失败返回结果 72 | * @param message 提示信息 73 | */ 74 | public static Result failed(String message) { 75 | return new Result<>(ResultCode.FAILED.getCode(), message, null); 76 | } 77 | 78 | /** 79 | * 失败返回结果 80 | * @param message 提示信息 81 | */ 82 | public static Result failed(String code, String message) { 83 | return new Result<>(code, message, null); 84 | } 85 | 86 | /** 87 | * 失败返回结果 88 | */ 89 | public static Result failed() { 90 | return failed(ResultCode.FAILED); 91 | } 92 | 93 | /** 94 | * 参数验证失败返回结果 95 | */ 96 | public static Result validateFailed() { 97 | return failed(ResultCode.VALIDATE_FAILED); 98 | } 99 | 100 | /** 101 | * 参数验证失败返回结果 102 | * @param message 提示信息 103 | */ 104 | public static Result validateFailed(String message) { 105 | return new Result<>(ResultCode.VALIDATE_FAILED.getCode(), message, null); 106 | } 107 | 108 | /** 109 | * 未登录返回结果 110 | */ 111 | public static Result unauthorized(T data) { 112 | return new Result<>(ResultCode.UNAUTHORIZED.getCode(), 113 | ResultCode.UNAUTHORIZED.getMessage(), data); 114 | } 115 | 116 | /** 117 | * 未授权返回结果 118 | */ 119 | public static Result forbidden(T data) { 120 | return new Result<>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), 121 | data); 122 | } 123 | 124 | public Map toMap() { 125 | Map map = new HashMap<>(4); 126 | map.put("code", code); 127 | map.put("message", msg); 128 | map.put("data", data); 129 | return map; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/api/ResultCode.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.api; 2 | 3 | /** 4 | * 常用API操作码 5 | */ 6 | public enum ResultCode implements IErrorCode { 7 | 8 | /** 9 | * 操作成功 10 | */ 11 | SUCCESS("0000", "操作成功"), 12 | FAILED("500", "操作失败"), 13 | VALIDATE_FAILED("400", "参数检验失败"), 14 | UNAUTHORIZED("8888", "暂未登录或token已经过期"), 15 | FORBIDDEN("8889", "暂未登录或token已经过期,没有相关权限"), 16 | 17 | /** 18 | * 用户相关 19 | */ 20 | ERROR_NAME_REPEAT("8001", "账户名重复"), 21 | ERROR_USER_NAME_REPEAT("8002", "昵称重复"), 22 | 23 | ERROR_CREATE_USER("8005", "创建用户失败"), 24 | 25 | /** 26 | * 资源相关 27 | */ 28 | ERROR_RESOURCE_EXISTENCE("8101", "资源不存在"), 29 | ERROR_PARENT_RESOURCE_DOES_NOT_EXIST("8102", "父资源不存在"), 30 | ERROR_GET_MENU_PERMISSIONS("8103", "获取菜单权限异常"), 31 | ; 32 | private final String code; 33 | 34 | private final String message; 35 | 36 | ResultCode(String code, String message) { 37 | this.code = code; 38 | this.message = message; 39 | } 40 | 41 | @Override 42 | public String getCode() { 43 | return code; 44 | } 45 | 46 | @Override 47 | public String getMessage() { 48 | return message; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/config/CacheConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.config; 2 | 3 | import com.github.benmanes.caffeine.cache.Caffeine; 4 | import org.springframework.cache.CacheManager; 5 | import org.springframework.cache.caffeine.CaffeineCacheManager; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import java.util.concurrent.TimeUnit; 10 | 11 | 12 | @Configuration 13 | public class CacheConfiguration { 14 | 15 | @Bean 16 | public CacheManager cacheManager() { 17 | CaffeineCacheManager cacheManager = new CaffeineCacheManager(); 18 | // 自定义缓存配置 19 | Caffeine caffeine = Caffeine.newBuilder() 20 | .maximumSize(100) // 设置缓存的最大容量 21 | .expireAfterWrite(5, TimeUnit.MINUTES) // 设置缓存过期策略(写入后5分钟过期) 22 | .recordStats(); // 记录缓存统计信息 23 | 24 | cacheManager.setCaffeine(caffeine); 25 | return cacheManager; 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/config/GlobalWebConfig.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | /** 10 | * 全局跨域配置 11 | */ 12 | @Configuration // 导入了Spring MVC的配置注解,该类用于全局Web配置 13 | @EnableWebMvc // 启用Spring MVC的扩展功能 14 | public class GlobalWebConfig implements WebMvcConfigurer { 15 | 16 | // 重写WebMvcConfigurer接口中的addCorsMappings方法,为跨域资源共享(CORS)进行配置 17 | @Override 18 | public void addCorsMappings(CorsRegistry registry) { 19 | // 添加一个全局映射规则,允许所有域名( "*" )访问,并且允许携带认证信息(cookies等),即允许跨域请求包含凭据 20 | registry.addMapping("/**") 21 | .allowedOriginPatterns("*") // 允许任何源发起的请求 22 | .allowCredentials(true) // 允许跨域请求带有验证信息(cookies) 23 | 24 | // 允许所有的请求头通过 25 | .allowedHeaders("*") 26 | 27 | // 允许指定的HTTP方法:GET, POST, PUT, DELETE, OPTIONS 28 | .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") 29 | 30 | // 预检请求的有效期为3600秒,在此期间内浏览器无需再发送预检请求 31 | .maxAge(3600); 32 | } 33 | 34 | // 重写WebMvcConfigurer接口中的addResourceHandlers方法,用于处理静态资源映射 35 | @Override 36 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 37 | // 将"/doc.html"路径映射到"classpath:/META-INF/resources/"下的静态资源文件 38 | registry.addResourceHandler("doc.html") 39 | .addResourceLocations("classpath:/META-INF/resources/"); 40 | 41 | // 将"/webjars/**"路径映射到"classpath:/META-INF/resources/webjars/"下的静态资源文件,通常用于前端依赖库(如jQuery、Bootstrap等) 42 | registry.addResourceHandler("/webjars/**") 43 | .addResourceLocations("classpath:/META-INF/resources/webjars/"); 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/config/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.config; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 5 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 6 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 7 | import com.pea.common.mybatis.config.MybatisPlusMetaObjectHandler; 8 | import org.mybatis.spring.annotation.MapperScan; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.transaction.annotation.EnableTransactionManagement; 12 | 13 | /** 14 | * MybatisPlusConfig 15 | */ 16 | @EnableTransactionManagement 17 | @Configuration 18 | @MapperScan("com.pea.business.sys.mapper") 19 | public class MybatisPlusConfig { 20 | 21 | /** 22 | * 新的分页插件 23 | */ 24 | @Bean 25 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 26 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 27 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 28 | return interceptor; 29 | } 30 | 31 | /** 32 | * 审计字段自动填充 33 | * @return {@link MetaObjectHandler} 34 | */ 35 | @Bean 36 | public MybatisPlusMetaObjectHandler dbFieldFillHandler() { 37 | return new MybatisPlusMetaObjectHandler(); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.config; 2 | 3 | import com.pea.component.security.filter.JwtAuthenticationTokenFilter; 4 | import com.pea.component.security.handle.RestAuthenticationEntryPoint; 5 | import com.pea.component.security.handle.RestfulAccessDeniedHandler; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.authentication.AuthenticationManager; 10 | import org.springframework.security.authentication.ProviderManager; 11 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 12 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 14 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 15 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 16 | import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; 17 | import org.springframework.security.core.userdetails.UserDetailsService; 18 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 19 | import org.springframework.security.crypto.password.PasswordEncoder; 20 | import org.springframework.security.web.SecurityFilterChain; 21 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 22 | 23 | /** 24 | * SecurityConfig 25 | */ 26 | @Configuration 27 | @EnableWebSecurity 28 | @RequiredArgsConstructor 29 | @EnableMethodSecurity 30 | public class SecurityConfig { 31 | 32 | /** 33 | * 自定义用户认证逻辑 34 | */ 35 | final UserDetailsService userDetailsService; 36 | 37 | final RestfulAccessDeniedHandler restfulAccessDeniedHandler; 38 | 39 | final RestAuthenticationEntryPoint restAuthenticationEntryPoint; 40 | 41 | @Bean 42 | protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 43 | http 44 | // 由于使用的是JWT,我们这里不需要csrf 45 | .csrf(AbstractHttpConfigurer::disable) 46 | // 基于token,所以不需要session 47 | .sessionManagement(AbstractHttpConfigurer::disable) 48 | 49 | .authorizeHttpRequests(author -> author 50 | // 允许对于网站静态资源的无授权访问 51 | .requestMatchers( 52 | "/webjars/**", 53 | "/doc.html", 54 | "/swagger-resources/**", 55 | "/v3/api-docs/**", 56 | "/swagger-ui/**", 57 | "/swagger-resources", 58 | "/swagger-ui.html") 59 | .permitAll() 60 | 61 | // 允许匿名访问 62 | .requestMatchers("/auth/login", "/route/getConstantRoutes").permitAll() 63 | 64 | .anyRequest().authenticated()); 65 | 66 | // 禁用缓存 67 | http.headers(headers -> headers 68 | .cacheControl(HeadersConfigurer.CacheControlConfig::disable) 69 | ); 70 | 71 | http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); 72 | 73 | // 添加自定义未授权和未登录结果返回 74 | http.exceptionHandling(exceptionHandling -> exceptionHandling 75 | .accessDeniedHandler(restfulAccessDeniedHandler) 76 | .authenticationEntryPoint(restAuthenticationEntryPoint) 77 | ); 78 | 79 | // 注入authenticationManager 80 | http.authenticationManager(authenticationManager()); 81 | 82 | return http.build(); 83 | } 84 | 85 | /** 86 | * 身份认证接口 构造一个AuthenticationManager,使用自定义的userDetailsService和passwordEncoder 87 | */ 88 | @Bean 89 | public AuthenticationManager authenticationManager() { 90 | DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); 91 | authenticationProvider.setUserDetailsService(userDetailsService); 92 | authenticationProvider.setPasswordEncoder(passwordEncoder()); 93 | 94 | return new ProviderManager(authenticationProvider); 95 | } 96 | 97 | /** 98 | * 强散列哈希加密实现 99 | */ 100 | @Bean 101 | public PasswordEncoder passwordEncoder() { 102 | return new BCryptPasswordEncoder(); 103 | } 104 | 105 | @Bean 106 | public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() { 107 | return new JwtAuthenticationTokenFilter(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.config; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | import io.swagger.v3.oas.models.Components; 5 | import io.swagger.v3.oas.models.OpenAPI; 6 | import io.swagger.v3.oas.models.info.Info; 7 | import io.swagger.v3.oas.models.security.SecurityScheme; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springdoc.core.customizers.GlobalOpenApiCustomizer; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.http.HttpHeaders; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * Swagger 配置 19 | */ 20 | @Configuration 21 | @Slf4j 22 | public class SwaggerConfig { 23 | 24 | /** 25 | * 接口信息 26 | */ 27 | @Bean 28 | public OpenAPI openApi() { 29 | 30 | return new OpenAPI() 31 | .info(new Info() 32 | .title("Pea系统接口文档") 33 | .version("1.0.0") 34 | ) 35 | // 配置全局鉴权参数-Authorize 36 | .components(new Components() 37 | .addSecuritySchemes(HttpHeaders.AUTHORIZATION, 38 | new SecurityScheme() 39 | .name(HttpHeaders.AUTHORIZATION) 40 | .type(SecurityScheme.Type.APIKEY) 41 | .in(SecurityScheme.In.HEADER) 42 | .scheme("Bearer") 43 | .bearerFormat("JWT") 44 | ) 45 | ); 46 | } 47 | 48 | 49 | /** 50 | * 全局自定义扩展 51 | *

52 | * 在OpenAPI规范中,Operation 是一个表示 API 端点(Endpoint)或操作的对象。 53 | * 每个路径(Path)对象可以包含一个或多个 Operation 对象,用于描述与该路径相关联的不同 HTTP 方法(例如 GET、POST、PUT 等)。 54 | */ 55 | @Bean 56 | public GlobalOpenApiCustomizer globalOpenApiCustomizer() { 57 | return openApi -> { 58 | if (openApi.getTags() != null){ 59 | openApi.getTags().forEach(tag -> { 60 | Map map=new HashMap<>(); 61 | map.put("x-order", RandomUtil.randomInt(0,100)); 62 | tag.setExtensions(map); 63 | }); 64 | } 65 | if(openApi.getPaths() != null){ 66 | openApi.addExtension("x-test123","333"); 67 | openApi.getPaths().addExtension("x-abb",RandomUtil.randomInt(1,100)); 68 | } 69 | 70 | }; 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/config/ThreadPoolConfig.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.config; 2 | 3 | import com.pea.common.utils.Threads; 4 | import org.apache.commons.lang3.concurrent.BasicThreadFactory; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 | 9 | import java.util.concurrent.ScheduledExecutorService; 10 | import java.util.concurrent.ScheduledThreadPoolExecutor; 11 | import java.util.concurrent.ThreadPoolExecutor; 12 | 13 | @Configuration 14 | public class ThreadPoolConfig { 15 | 16 | /** 17 | * 核心线程池大小 18 | */ 19 | private final int corePoolSize = 50; 20 | 21 | /** 22 | * 最大可创建的线程数 23 | */ 24 | private final int maxPoolSize = 200; 25 | 26 | /** 27 | * 队列最大长度 28 | */ 29 | private final int queueCapacity = 1000; 30 | 31 | /** 32 | * 线程池维护线程所允许的空闲时间 33 | */ 34 | private final int keepAliveSeconds = 300; 35 | 36 | @Bean(name = "threadPoolTaskExecutor") 37 | public ThreadPoolTaskExecutor threadPoolTaskExecutor() { 38 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 39 | executor.setMaxPoolSize(maxPoolSize); 40 | executor.setCorePoolSize(corePoolSize); 41 | executor.setQueueCapacity(queueCapacity); 42 | executor.setKeepAliveSeconds(keepAliveSeconds); 43 | // 线程池对拒绝任务(无线程可用)的处理策略 44 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 45 | return executor; 46 | } 47 | 48 | /** 49 | * 执行周期性或定时任务 50 | */ 51 | @Bean(name = "scheduledExecutorService") 52 | protected ScheduledExecutorService scheduledExecutorService() { 53 | return new ScheduledThreadPoolExecutor(corePoolSize, 54 | new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d") 55 | .daemon(true).build()) { 56 | @Override 57 | protected void afterExecute(Runnable r, Throwable t) { 58 | super.afterExecute(r, t); 59 | Threads.printException(r, t); 60 | } 61 | }; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/enums/BusinessType.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum BusinessType { 9 | 10 | /** 11 | * 其它 12 | */ 13 | OTHER(0, "其它"), 14 | 15 | /** 16 | * 新增 17 | */ 18 | INSERT(1, "新增"), 19 | 20 | /** 21 | * 修改 22 | */ 23 | UPDATE(2, "修改"), 24 | 25 | /** 26 | * 删除 27 | */ 28 | DELETE(3, "删除"), 29 | 30 | /** 31 | * 授权 32 | */ 33 | GRANT(4, "授权"), 34 | 35 | /** 36 | * 导出 37 | */ 38 | EXPORT(5, "导出"), 39 | 40 | /** 41 | * 导入 42 | */ 43 | IMPORT(6, "导入"), 44 | 45 | /** 46 | * 强退 47 | */ 48 | FORCE(7, "强退"), 49 | 50 | /** 51 | * 生成代码 52 | */ 53 | GENERATE_CODE(8, "生成代码"), 54 | 55 | /** 56 | * 清空数据 57 | */ 58 | CLEAN(9, "清空数据"); 59 | 60 | private final Integer code; 61 | 62 | private final String name; 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/enums/CacheConstants.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.enums; 2 | 3 | /** 4 | * 缓存的key 常量 5 | */ 6 | public interface CacheConstants { 7 | 8 | /** 9 | * 验证码前缀 10 | */ 11 | String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY:"; 12 | 13 | /** 14 | * 菜单信息缓存 15 | */ 16 | String MENU_DETAILS = "menu_details"; 17 | 18 | /** 19 | * 用户信息缓存 20 | */ 21 | String USER_DETAILS = "user_details"; 22 | 23 | /** 24 | * 字典信息缓存 25 | */ 26 | String DICT_DETAILS = "dict_details"; 27 | 28 | /** 29 | * 参数缓存 30 | */ 31 | String PARAMS_DETAILS = "params_details"; 32 | 33 | /** 34 | * 手机验证码是否存在标记 35 | */ 36 | String SMS_CODE_EXIST = "sms_code_exist:"; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/enums/DelStatusEnums.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * ClassName: DelStatusEnums 8 | * Description: 删除状态 9 | */ 10 | @Getter 11 | @AllArgsConstructor 12 | public enum DelStatusEnums { 13 | 14 | /** 15 | * 未删除 16 | */ 17 | DISABLE(0, "未删除"), 18 | /** 19 | * 已删除 20 | */ 21 | ENABLE(1, "已删除"),; 22 | 23 | private final Integer code; 24 | 25 | private final String name; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/enums/MenuTypeEnums.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * ClassName: MenuTypeEnums 8 | * Description: 资源分类 9 | */ 10 | @Getter 11 | @AllArgsConstructor 12 | public enum MenuTypeEnums { 13 | 14 | /** 15 | * 菜单 16 | */ 17 | MENU("1", "菜单"), 18 | /** 19 | * 目录 20 | */ 21 | CATALOGUE("2", "目录"), 22 | /** 23 | * 目录 24 | */ 25 | BUTTON("3", "按钮"), 26 | /** 27 | * 基础菜单 28 | */ 29 | BASIC_MENU("4", "基础菜单"),; 30 | 31 | private final String code; 32 | 33 | private final String name; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/enums/StatusEnums.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * ClassName: StatusEnums 8 | * Description: 用户状态 9 | */ 10 | @Getter 11 | @AllArgsConstructor 12 | public enum StatusEnums { 13 | 14 | /** 15 | * 禁用 16 | */ 17 | DISABLE("2", "禁用"), 18 | /** 19 | * 启用 20 | */ 21 | ENABLE("1", "启用"),; 22 | 23 | private final String code; 24 | 25 | private final String name; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/exception/GlobalException.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.exception; 2 | 3 | import com.pea.common.api.IErrorCode; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.io.Serial; 8 | 9 | /** 10 | * 自定义异常 11 | */ 12 | @EqualsAndHashCode(callSuper = true) 13 | @Data 14 | public class GlobalException extends RuntimeException { 15 | 16 | @Serial 17 | private static final long serialVersionUID = 1L; 18 | 19 | private String code; 20 | 21 | private String message; 22 | 23 | private Boolean json; 24 | 25 | public GlobalException(GlobalExceptionMap exception, Boolean json) { 26 | super(exception.getMessage()); 27 | this.code = exception.getCode(); 28 | this.message = exception.getMessage(); 29 | this.json = json; 30 | } 31 | 32 | public GlobalException(String code, String message) { 33 | super(message); 34 | this.code = code; 35 | this.message = message; 36 | this.json = true; 37 | } 38 | 39 | public GlobalException(String message) { 40 | super(message); 41 | this.code = "500"; 42 | this.message = message; 43 | this.json = false; 44 | } 45 | 46 | public GlobalException(IErrorCode exceptionEnum) { 47 | super(exceptionEnum.getMessage()); 48 | this.code = exceptionEnum.getCode(); 49 | this.message = exceptionEnum.getMessage(); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/exception/GlobalExceptionEnum.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.exception; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 全局异常枚举 7 | */ 8 | @Getter 9 | public enum GlobalExceptionEnum implements GlobalExceptionMap { 10 | 11 | ERROR_USER_NOT_EXIST("100", "用户不存在"), 12 | USER_DISABLED("1002", "您的账号已被停用"), 13 | ERROR_PARAM("400", "请求参数有误"), 14 | ERROR_UNAUTHORIZED("401", "用户未授权"), 15 | ERROR_TOKEN_EXPIRED("402", "token过期"), 16 | ERROR_FORBIDDEN("403", "资源被禁止访问"), 17 | ERROR_SERVER("500", "系统异常"), 18 | ERROR_TIME_OUT("502", "超时操作"), 19 | ERROR_VERIFY_CODE_WRONG("600", "验证码不正确"), 20 | ERROR_NOT_LOGIN_TO_COMMENT("601", "请先登录"), 21 | ERROR_USER_STATE_NOT_VALID("602", "账户异常,请联系管理员"), 22 | ERROR_UNABLE_GET_USER("603", "无法获取用户"), 23 | ERROR_GAIN_RESOURCE("604", "获取资源信息异常"), 24 | ERROR_CAN_NOT_DELETE_RESOURCE("900", "默认资源无法删除"), 25 | ERROR_IN_BLACKLIST("901", "检测到你进行非法操作,已被列入黑名单!"); 26 | 27 | private final String code; 28 | 29 | private final String message; 30 | 31 | GlobalExceptionEnum(String code, String message) { 32 | this.code = code; 33 | this.message = message; 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.exception; 2 | 3 | import com.pea.common.api.Result; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.RestControllerAdvice; 7 | 8 | /** 9 | * 异常处理器 10 | */ 11 | @Slf4j 12 | @RestControllerAdvice 13 | public class GlobalExceptionHandler { 14 | 15 | /** 16 | * 处理自定义异常 17 | */ 18 | @ExceptionHandler(GlobalException.class) 19 | public Result handleApiException(GlobalException e) { 20 | log.error("自定义GlobalException 抛出:", e); 21 | return Result.failed(e.getCode(), e.getMessage()); 22 | } 23 | 24 | @ExceptionHandler(Exception.class) 25 | public Result handleException(Exception e) { 26 | log.error(e.getMessage(), e); 27 | return Result.failed(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/exception/GlobalExceptionMap.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.exception; 2 | 3 | public interface GlobalExceptionMap { 4 | 5 | /** 6 | * 返回 code 7 | */ 8 | String getCode(); 9 | 10 | /** 11 | * 返回消息 12 | */ 13 | String getMessage(); 14 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/manager/AsyncManager.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.manager; 2 | 3 | import com.pea.common.utils.SpringUtils; 4 | import com.pea.common.utils.Threads; 5 | 6 | import java.util.TimerTask; 7 | import java.util.concurrent.ScheduledExecutorService; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * AsyncManager 类主要用于管理异步操作任务的调度与执行 12 | */ 13 | public class AsyncManager { 14 | 15 | /** 16 | * 定义一个常量,表示所有异步操作默认会有一个10毫秒的延迟 17 | */ 18 | private final int OPERATE_DELAY_TIME = 10; 19 | 20 | /** 21 | * 使用Spring框架的SpringUtils工具类从IoC容器中获取一个ScheduledExecutorService实例, 22 | * 这个线程池将会用来调度和执行异步任务 23 | */ 24 | private final ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); 25 | 26 | /** 27 | * 实现单例模式,保证在整个应用中AsyncManager只有一个实例 28 | */ 29 | private AsyncManager() { 30 | } 31 | 32 | /** 33 | * 静态内部持有唯一实例,并提供公共静态方法me()来获取这个单例 34 | */ 35 | private static final AsyncManager me = new AsyncManager(); 36 | 37 | public static AsyncManager me() { 38 | return me; 39 | } 40 | 41 | /** 42 | * 提供一个execute方法,接受一个TimerTask类型的参数,该方法用于调度任务在OPERATE_DELAY_TIME毫秒后执行 43 | * 注意这里实际上是使用ScheduledExecutorService的schedule方法来实现定时任务的功能 44 | * @param task 45 | */ 46 | public void execute(TimerTask task) { 47 | executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); 48 | } 49 | 50 | /** 51 | * 提供一个shutdown方法,用于停止调度器及其相关线程池的运行 52 | * 调用Threads工具类的shutdownAndAwaitTermination方法确保线程池被正确关闭并且等待所有任务完成或超时 53 | */ 54 | public void shutdown() { 55 | Threads.shutdownAndAwaitTermination(executor); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/manager/factory/AsyncFactory.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.manager.factory; 2 | 3 | import com.pea.business.sys.domain.SysOperationLog; 4 | import com.pea.business.sys.service.SysOperationLogService; 5 | import com.pea.common.utils.IpUtil; 6 | import com.pea.common.utils.SpringUtils; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.util.TimerTask; 10 | 11 | @Slf4j 12 | public class AsyncFactory { 13 | 14 | /** 15 | * 操作日志记录 16 | * @param operLog 操作日志信息 17 | * @return 任务task 18 | */ 19 | public static TimerTask recordOper(final SysOperationLog operLog) { 20 | return new TimerTask() { 21 | @Override 22 | public void run() { 23 | String address = IpUtil.getAddress(operLog.getReqIp()); 24 | // 远程查询操作地点 25 | operLog.setOperLocation(address); 26 | SysOperationLogService operationLogService = SpringUtils.getBean(SysOperationLogService.class); 27 | operationLogService.save(operLog); 28 | } 29 | }; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/model/Meta.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.model; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.pea.common.mybatis.base.BaseEntity; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | @Schema(description = "Meta元数据视图对象") 14 | @Data 15 | @EqualsAndHashCode(callSuper = true) 16 | public class Meta extends BaseEntity { 17 | 18 | @Schema(description = "页面标题") 19 | private String title; 20 | 21 | @Schema(description = "国际化键") 22 | private String i18nKey; 23 | 24 | @Schema(description = "图标") 25 | private String icon; 26 | 27 | @Schema(description = "图标类型") 28 | private String iconType; 29 | 30 | @Schema(description = "顺序") 31 | private Integer order; 32 | 33 | @Schema(description = "角色列表,逗号分隔") 34 | private List roles; 35 | 36 | @Schema(description = "是否缓存") 37 | private Boolean keepAlive; 38 | 39 | @Schema(description = "是否为常量路由") 40 | private Boolean constant; 41 | 42 | @Schema(description = "本地图标路径") 43 | private String localIcon; 44 | 45 | @Schema(description = "外部链接") 46 | private String href; 47 | 48 | @Schema(description = "是否在菜单中隐藏") 49 | private Boolean hideInMenu; 50 | 51 | @Schema(description = "激活的菜单键") 52 | private String activeMenu; 53 | 54 | @Schema(description = "是否开启多标签页模式") 55 | private Boolean multiTab; 56 | 57 | @Schema(description = "在标签页中固定索引位置") 58 | private Integer fixedIndexInTab; 59 | 60 | @Schema(description = "路由查询参数") 61 | private List query; 62 | 63 | // 添加无参构造函数 64 | public Meta() {} 65 | 66 | public Meta(String metaJson) { 67 | JSONObject jsonObject = JSONUtil.parseObj(metaJson); 68 | 69 | setTitle(jsonObject.getStr("title", "")); 70 | setI18nKey(jsonObject.getStr("i18nKey", "")); 71 | setIcon(jsonObject.getStr("icon", "")); 72 | setIconType(jsonObject.getStr("iconType", "")); 73 | 74 | // 对其他字段做同样处理 75 | setOrder(jsonObject.getInt("order", null)); // 如果order字段可为空,则设为null,否则提供一个默认值 76 | if (!jsonObject.containsKey("roles")) { 77 | roles = Collections.emptyList(); // 或者自定义一个默认空集合 78 | } else { 79 | roles = jsonObject.getJSONArray("roles").toList(String.class); // 假设roles是一个字符串数组 80 | } 81 | setKeepAlive(jsonObject.getBool("keepAlive", false)); 82 | setConstant(jsonObject.getBool("constant", null)); 83 | setLocalIcon(jsonObject.getStr("localIcon", "")); 84 | setHref(jsonObject.getStr("href", "")); 85 | setHideInMenu(jsonObject.getBool("hideInMenu", null)); 86 | setActiveMenu(jsonObject.getStr("activeMenu", "")); 87 | setMultiTab(jsonObject.getBool("multiTab", false)); 88 | setFixedIndexInTab(jsonObject.getInt("fixedIndexInTab", null)); 89 | if (!jsonObject.containsKey("query")) { 90 | roles = Collections.emptyList(); 91 | } else { 92 | roles = jsonObject.getJSONArray("query").toList(String.class); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/mybatis/base/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.mybatis.base; 2 | 3 | import cn.hutool.core.date.DatePattern; 4 | import com.baomidou.mybatisplus.annotation.FieldFill; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.fasterxml.jackson.annotation.JsonFormat; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | 11 | import java.io.Serializable; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | * 基础实体 16 | */ 17 | @Getter 18 | @Setter 19 | public class BaseEntity implements Serializable { 20 | 21 | @Schema(description = "创建者ID") 22 | @TableField(fill = FieldFill.INSERT) 23 | private Long createId; 24 | 25 | @Schema(description = "创建者名称") 26 | @TableField(fill = FieldFill.INSERT) 27 | private String createBy; 28 | 29 | @Schema(description = "创建时间") 30 | @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8") 31 | @TableField(fill = FieldFill.INSERT) 32 | private LocalDateTime createTime; 33 | 34 | @Schema(description = "修改者ID") 35 | @TableField(fill = FieldFill.UPDATE) 36 | private Long updateId; 37 | 38 | @Schema(description = "修改者名称") 39 | @TableField(fill = FieldFill.UPDATE) 40 | private String updateBy; 41 | 42 | @Schema(description = "更新时间") 43 | @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8") 44 | @TableField(fill = FieldFill.UPDATE) 45 | private LocalDateTime updateTime; 46 | 47 | @Schema(description = "是否已删除:0->未删除;1->已删除") 48 | private Integer isDeleted; 49 | 50 | @Schema(description = "删除时间") 51 | @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8") 52 | private LocalDateTime deleteTime; 53 | 54 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/mybatis/config/MybatisPlusMetaObjectHandler.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.mybatis.config; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 6 | import com.pea.business.sys.domain.SysUser; 7 | import com.pea.common.utils.SecurityUtil; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.ibatis.reflection.MetaObject; 10 | import org.springframework.context.annotation.Primary; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.util.ClassUtils; 13 | 14 | import java.nio.charset.Charset; 15 | import java.time.LocalDateTime; 16 | 17 | /** 18 | * MybatisPlus 自动填充配置 19 | */ 20 | @Slf4j 21 | @Primary 22 | @Component 23 | public class MybatisPlusMetaObjectHandler implements MetaObjectHandler { 24 | @Override 25 | public void insertFill(MetaObject metaObject) { 26 | log.debug("mybatis plus start insert fill ...."); 27 | LocalDateTime localDateTime = DateUtil.date().toLocalDateTime(); 28 | SysUser sysUser = getUser(); 29 | 30 | this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, localDateTime); 31 | this.strictInsertFill(metaObject, "createId", Long.class, sysUser.getId()); 32 | this.strictInsertFill(metaObject, "createBy", String.class, sysUser.getUserName()); 33 | } 34 | 35 | @Override 36 | public void updateFill(MetaObject metaObject) { 37 | log.debug("mybatis plus start update fill ...."); 38 | LocalDateTime localDateTime = DateUtil.date().toLocalDateTime(); 39 | SysUser sysUser = getUser(); 40 | 41 | this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, localDateTime); 42 | this.strictInsertFill(metaObject, "updateId", Long.class, sysUser.getId()); 43 | this.strictInsertFill(metaObject, "updateBy", String.class, sysUser.getUserName()); 44 | } 45 | 46 | /** 47 | * 填充值,先判断是否有手动设置,优先手动设置的值,例如:job必须手动设置 48 | * @param fieldName 属性名 49 | * @param fieldVal 属性值 50 | * @param metaObject MetaObject 51 | * @param isCover 是否覆盖原有值,避免更新操作手动入参 52 | */ 53 | private static void fillValIfNullByName(String fieldName, Object fieldVal, 54 | MetaObject metaObject, boolean isCover) { 55 | // 1. 没有 set 方法 56 | if (!metaObject.hasSetter(fieldName)) { 57 | return; 58 | } 59 | // 2. 如果用户有手动设置的值 60 | Object userSetValue = metaObject.getValue(fieldName); 61 | String setValueStr = StrUtil.str(userSetValue, Charset.defaultCharset()); 62 | if (StrUtil.isNotBlank(setValueStr) && !isCover) { 63 | return; 64 | } 65 | // 3. field 类型相同时设置 66 | Class getterType = metaObject.getGetterType(fieldName); 67 | if (ClassUtils.isAssignableValue(getterType, fieldVal)) { 68 | metaObject.setValue(fieldName, fieldVal); 69 | } 70 | } 71 | 72 | /** 73 | * 获取 spring security 当前的用户 74 | * @return 当前用户 75 | */ 76 | private SysUser getUser() { 77 | return SecurityUtil.getSysUser(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/utils/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.utils; 2 | 3 | import com.pea.common.exception.GlobalException; 4 | import com.pea.common.exception.GlobalExceptionMap; 5 | 6 | /** 7 | * description: 全局异常抛出 8 | */ 9 | public class ExceptionUtil { 10 | 11 | private ExceptionUtil() {} 12 | 13 | /** 14 | * 抛异常 15 | */ 16 | public static void throwEx(GlobalExceptionMap globalExceptionMap) { 17 | throw new GlobalException(globalExceptionMap, true); 18 | } 19 | 20 | /** 21 | * 抛异常 22 | */ 23 | public static void throwEx(String code, String message) { 24 | throw new GlobalException(code, message); 25 | } 26 | 27 | /** 28 | * 抛异常到异常页面 29 | */ 30 | public static void throwExToPage(GlobalExceptionMap globalExceptionMap) { 31 | throw new GlobalException(globalExceptionMap, false); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/common/utils/IpUtil.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.utils; 2 | 3 | import cn.hutool.core.io.IoUtil; 4 | import eu.bitwalker.useragentutils.Browser; 5 | import eu.bitwalker.useragentutils.UserAgent; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.lionsoul.ip2region.xdb.Searcher; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.net.Inet4Address; 13 | import java.net.InetAddress; 14 | import java.net.NetworkInterface; 15 | import java.net.UnknownHostException; 16 | import java.util.Enumeration; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | /** 20 | * description: ip 地址工具类 21 | */ 22 | @SuppressWarnings("ALL") 23 | @Slf4j 24 | public class IpUtil { 25 | 26 | private static final String IP_V4 = "127.0.0.1"; 27 | 28 | private static final String IP_V6 = "0:0:0:0:0:0:0:1"; 29 | 30 | private static final String UNKNOWN = "unknown"; 31 | 32 | /** 33 | * 用于IP定位转换 34 | */ 35 | private static final String REGION = "内网IP|内网IP"; 36 | 37 | private IpUtil() {} 38 | 39 | public static String getHostIp() { 40 | try { 41 | Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces(); 42 | while (allNetInterfaces.hasMoreElements()) { 43 | NetworkInterface netInterface = allNetInterfaces.nextElement(); 44 | Enumeration addresses = netInterface.getInetAddresses(); 45 | while (addresses.hasMoreElements()) { 46 | InetAddress ip = addresses.nextElement(); 47 | if (ip instanceof Inet4Address 48 | && !ip.isLoopbackAddress() 49 | && !ip.getHostAddress().contains(":")){ 50 | return ip.getHostAddress(); 51 | } 52 | } 53 | } 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } 57 | return IP_V4; 58 | } 59 | 60 | 61 | public static String getIp() { 62 | return getIp(ServletUtils.getRequest()); 63 | } 64 | 65 | public static String getIp(HttpServletRequest request) { 66 | String ip = request.getHeader("x-forwarded-for"); 67 | if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { 68 | ip = request.getHeader("Proxy-Client-IP"); 69 | } 70 | if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { 71 | ip = request.getHeader("WL-Proxy-Client-IP"); 72 | } 73 | if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { 74 | ip = request.getRemoteAddr(); 75 | } 76 | String comma = ","; 77 | if (ip.contains(comma)) { 78 | ip = ip.split(",")[0]; 79 | } 80 | if (IP_V4.equals(ip)) { 81 | // 获取本机真正的ip地址 82 | try { 83 | ip = InetAddress.getLocalHost().getHostAddress(); 84 | } 85 | catch (UnknownHostException e) { 86 | log.error(e.getMessage()); 87 | } 88 | } 89 | return ip; 90 | } 91 | 92 | /** 93 | * 根据ip获取详细地址 94 | */ 95 | public static String getAddress(String ip) { 96 | // 1、创建 searcher 对象 97 | String dbPath = "ip2region/ip2region.xdb"; 98 | // 获取ip2region.db文件的位置 99 | InputStream inputStream = IpUtil.class.getClassLoader() 100 | .getResourceAsStream(dbPath); 101 | Searcher searcher = null; 102 | try { 103 | if ("0:0:0:0:0:0:0:1".equals(ip)) { 104 | return REGION; 105 | } 106 | 107 | long sTime = System.nanoTime(); 108 | searcher = Searcher.newWithBuffer(IoUtil.readBytes(inputStream)); 109 | // 2、查询 110 | String region = searcher.search(ip); 111 | long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime); 112 | log.info("region: {}, ioCount: {}, took: {} μs", region, 113 | searcher.getIOCount(), cost); 114 | return region; 115 | } 116 | catch (Exception e) { 117 | log.error("failed to create searcher with {}", dbPath, e); 118 | } 119 | finally { 120 | if (searcher != null) { 121 | try { 122 | searcher.close(); 123 | } 124 | catch (IOException ignored) { 125 | 126 | } 127 | } 128 | } 129 | return REGION; 130 | } 131 | 132 | /** 133 | * 获取浏览器类型 134 | * @param request 135 | * @return 136 | */ 137 | public static String getBrowser(HttpServletRequest request) { 138 | UserAgent userAgent = UserAgent 139 | .parseUserAgentString(request.getHeader("User-Agent")); 140 | Browser browser = userAgent.getBrowser(); 141 | return browser.getName(); 142 | } 143 | 144 | /** 145 | * 操作系统 146 | */ 147 | public static String getOs(HttpServletRequest request) { 148 | UserAgent userAgent = UserAgent 149 | .parseUserAgentString(request.getHeader("User-Agent")); 150 | return userAgent.getOperatingSystem().getName(); 151 | } 152 | 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/utils/JwtTokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.utils; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.stereotype.Component; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.util.*; 15 | 16 | /** 17 | * JwtToken生成的工具类 18 | */ 19 | @Slf4j 20 | @Data 21 | @Component 22 | @ConfigurationProperties(prefix = "jwt") 23 | public class JwtTokenUtil { 24 | 25 | private static final String CLAIM_KEY_USERNAME = "username"; 26 | 27 | private static final String CLAIM_KEY_CREATED = "created"; 28 | 29 | @Value("secret") 30 | private String secret; 31 | 32 | @Value("expiration") 33 | private String expiration; 34 | 35 | @Value("tokenHeader") 36 | private String tokenHeader; 37 | 38 | @Value("tokenHead") 39 | private String tokenHead; 40 | 41 | public static String getUUID() { 42 | return UUID.randomUUID().toString().replaceAll("-", ""); 43 | } 44 | 45 | /** 46 | * 根据负责生成JWT的token 47 | */ 48 | private String generateToken(Map claims) { 49 | long nowMillis = System.currentTimeMillis(); 50 | Date now = new Date(nowMillis); 51 | 52 | return Jwts.builder() 53 | .setId(getUUID()) //唯一的ID 54 | .setClaims(claims) // 主题 可以是JSON数据 55 | .setIssuer("pea") // 签发者 56 | .setIssuedAt(now) // 签发时间 57 | .signWith(SignatureAlgorithm.HS512, secret) //使用HS512对称加密算法签名, 第二个参数为秘钥 58 | .setExpiration(generateExpirationDate()).compact(); 59 | } 60 | 61 | /** 62 | * 从token中获取JWT中的负载 63 | */ 64 | private Claims getClaimsFromToken(String token) { 65 | Claims claims = null; 66 | try { 67 | claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); 68 | } catch (Exception e) { 69 | log.error("JWT格式验证失败:{}", token); 70 | } 71 | return claims; 72 | } 73 | 74 | /** 75 | * 生成token的过期时间 76 | */ 77 | private Date generateExpirationDate() { 78 | Calendar calendar = Calendar.getInstance(); 79 | calendar.add(Calendar.SECOND, Integer.parseInt(expiration)); // 添加指定秒数 80 | return calendar.getTime(); 81 | } 82 | 83 | /** 84 | * 从token中获取登录用户名 85 | */ 86 | public String getUserNameFromToken(String token) { 87 | String username; 88 | try { 89 | Claims claims = getClaimsFromToken(token); 90 | username = claims.get(CLAIM_KEY_USERNAME, String.class); 91 | } catch (Exception e) { 92 | username = null; 93 | } 94 | return username; 95 | } 96 | 97 | /** 98 | * 验证token是否还有效 99 | * 100 | * @param token 客户端传入的token 101 | * @param userDetails 从数据库中查询出来的用户信息 102 | */ 103 | public boolean validateToken(String token, UserDetails userDetails) { 104 | String username = getUserNameFromToken(token); 105 | return username.equals(userDetails.getUsername()) && isTokenExpired(token); 106 | } 107 | 108 | /** 109 | * 判断token是否已经失效 110 | */ 111 | private boolean isTokenExpired(String token) { 112 | Date expiredDate = getExpiredDateFromToken(token); 113 | return !expiredDate.before(new Date()); 114 | } 115 | 116 | /** 117 | * 从token中获取过期时间 118 | */ 119 | private Date getExpiredDateFromToken(String token) { 120 | Claims claims = getClaimsFromToken(token); 121 | return claims.getExpiration(); 122 | } 123 | 124 | /** 125 | * 根据用户信息生成token 126 | * 127 | * @return str 128 | */ 129 | public String generateToken(String username) { 130 | Map claims = new HashMap<>(); 131 | claims.put(CLAIM_KEY_USERNAME, username); 132 | claims.put(CLAIM_KEY_CREATED, new Date()); 133 | return generateToken(claims); 134 | } 135 | 136 | /** 137 | * 判断token是否可以被刷新 138 | */ 139 | public boolean canRefresh(String token) { 140 | return isTokenExpired(token); 141 | } 142 | 143 | /** 144 | * 刷新token 145 | */ 146 | public String refreshToken(String token) { 147 | Claims claims = getClaimsFromToken(token); 148 | claims.put(CLAIM_KEY_CREATED, new Date()); 149 | return generateToken(claims); 150 | } 151 | 152 | public String getToken(HttpServletRequest request) { 153 | final String requestHeader = request.getHeader(tokenHeader); 154 | if (requestHeader != null && requestHeader.startsWith(tokenHead)) { 155 | return requestHeader.substring(7); 156 | } 157 | return null; 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/utils/SecurityUtil.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.utils; 2 | 3 | import com.pea.business.sys.domain.SysUser; 4 | import com.pea.common.api.ResultCode; 5 | import com.pea.common.exception.GlobalException; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.context.SecurityContext; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | 11 | /** 12 | * SecurityUtil 13 | */ 14 | public class SecurityUtil { 15 | 16 | public static SysUser getSysUser() { 17 | try { 18 | SecurityContext ctx = SecurityContextHolder.getContext(); 19 | Authentication auth = ctx.getAuthentication(); 20 | SysUserDetail sysUserDetail = (SysUserDetail) auth.getPrincipal(); 21 | return sysUserDetail.getSysUser(); 22 | } catch (Exception e) { 23 | throw new GlobalException(ResultCode.UNAUTHORIZED); 24 | } 25 | } 26 | 27 | public static UserDetails getUserDetails() { 28 | UserDetails userDetails; 29 | try { 30 | userDetails = (UserDetails) SecurityContextHolder.getContext() 31 | .getAuthentication().getPrincipal(); 32 | } catch (Exception e) { 33 | throw new GlobalException(ResultCode.UNAUTHORIZED); 34 | } 35 | return userDetails; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/utils/ServletUtils.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.utils; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import org.springframework.web.context.request.RequestAttributes; 6 | import org.springframework.web.context.request.RequestContextHolder; 7 | import org.springframework.web.context.request.ServletRequestAttributes; 8 | 9 | 10 | public class ServletUtils { 11 | 12 | public static ServletRequestAttributes getRequestAttributes() { 13 | RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); 14 | return (ServletRequestAttributes) attributes; 15 | } 16 | 17 | /** 18 | * 获取request 19 | */ 20 | public static HttpServletRequest getRequest() { 21 | return getRequestAttributes().getRequest(); 22 | } 23 | 24 | /** 25 | * 获取response 26 | */ 27 | public static HttpServletResponse getResponse() { 28 | return getRequestAttributes().getResponse(); 29 | } 30 | 31 | /** 32 | * 获取请求方法(GET、POST等) 33 | * @return 请求方法字符串 34 | */ 35 | public static String getRequestMethod() { 36 | return getRequest().getMethod(); 37 | } 38 | 39 | /** 40 | * 获取请求URI 41 | * @return 请求URI字符串 42 | */ 43 | public static String getRequestURI() { 44 | return getRequest().getRequestURI(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/utils/SpringUtils.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.utils; 2 | 3 | import org.springframework.aop.framework.AopContext; 4 | import org.springframework.beans.BeansException; 5 | import org.springframework.beans.factory.NoSuchBeanDefinitionException; 6 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 7 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class SpringUtils implements BeanFactoryPostProcessor { 12 | 13 | /** 14 | * Spring应用上下文环境 15 | */ 16 | private static ConfigurableListableBeanFactory beanFactory; 17 | 18 | /** 19 | * 获取对象 20 | * 21 | * @param name 22 | * @return Object 一个以所给名字注册的bean的实例 23 | * @throws org.springframework.beans.BeansException 24 | */ 25 | @SuppressWarnings("unchecked") 26 | public static T getBean(String name) throws BeansException { 27 | return (T) beanFactory.getBean(name); 28 | } 29 | 30 | /** 31 | * 获取类型为requiredType的对象 32 | * 33 | * @param clz 34 | * @return 35 | * @throws org.springframework.beans.BeansException 36 | */ 37 | public static T getBean(Class clz) throws BeansException { 38 | return beanFactory.getBean(clz); 39 | } 40 | 41 | /** 42 | * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true 43 | * 44 | * @param name 45 | * @return boolean 46 | */ 47 | public static boolean containsBean(String name) { 48 | return beanFactory.containsBean(name); 49 | } 50 | 51 | /** 52 | * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 53 | * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) 54 | * 55 | * @param name 56 | * @return boolean 57 | * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException 58 | */ 59 | public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { 60 | return beanFactory.isSingleton(name); 61 | } 62 | 63 | /** 64 | * @param name 65 | * @return Class 注册对象的类型 66 | * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException 67 | */ 68 | public static Class getType(String name) throws NoSuchBeanDefinitionException { 69 | return beanFactory.getType(name); 70 | } 71 | 72 | /** 73 | * 如果给定的bean名字在bean定义中有别名,则返回这些别名 74 | * 75 | * @param name 76 | * @return 77 | * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException 78 | */ 79 | public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { 80 | return beanFactory.getAliases(name); 81 | } 82 | 83 | /** 84 | * 获取aop代理对象 85 | * 86 | * @param invoker 87 | * @return 88 | */ 89 | @SuppressWarnings("unchecked") 90 | public static T getAopProxy(T invoker) { 91 | return (T) AopContext.currentProxy(); 92 | } 93 | 94 | @Override 95 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 96 | throws BeansException { 97 | SpringUtils.beanFactory = beanFactory; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/utils/SysUserDetail.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.utils; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.pea.business.sys.domain.SysUser; 5 | import com.pea.common.enums.StatusEnums; 6 | import lombok.Getter; 7 | import org.springframework.security.core.GrantedAuthority; 8 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * SpringSecurity 需要的用户详情 17 | */ 18 | @Getter 19 | public class SysUserDetail implements UserDetails { 20 | 21 | private final SysUser sysUser; 22 | 23 | private final List permissions; 24 | 25 | private final List roles; 26 | 27 | public SysUserDetail(SysUser sysUser, List permissions, List roles) { 28 | this.sysUser = sysUser; 29 | this.permissions = permissions; 30 | this.roles = roles; 31 | } 32 | 33 | @Override 34 | public Collection getAuthorities() { 35 | // 返回当前用户的权限 36 | return permissions.stream().filter(StrUtil::isNotEmpty) 37 | .map(SimpleGrantedAuthority::new).collect(Collectors.toList()); 38 | } 39 | 40 | @Override 41 | public String getPassword() { 42 | return sysUser.getPassword(); 43 | } 44 | 45 | @Override 46 | public String getUsername() { 47 | return sysUser.getUserName(); 48 | } 49 | 50 | /** 51 | * 账户是否未过期,过期无法验证 52 | */ 53 | @Override 54 | public boolean isAccountNonExpired() { 55 | return true; 56 | } 57 | 58 | /** 59 | * 指定用户是否解锁,锁定的用户无法进行身份验证 60 | */ 61 | @Override 62 | public boolean isAccountNonLocked() { 63 | return true; 64 | } 65 | 66 | /** 67 | * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 68 | */ 69 | @Override 70 | public boolean isCredentialsNonExpired() { 71 | return true; 72 | } 73 | 74 | /** 75 | * 是否可用 ,禁用的用户不能身份验证 76 | */ 77 | @Override 78 | public boolean isEnabled() { 79 | return StatusEnums.ENABLE.getCode().equals(sysUser.getStatus()); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/pea/common/utils/Threads.java: -------------------------------------------------------------------------------- 1 | package com.pea.common.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.concurrent.*; 6 | 7 | @Slf4j 8 | public class Threads { 9 | 10 | /** 11 | * sleep等待,单位为毫秒 12 | */ 13 | public static void sleep(long milliseconds) { 14 | try { 15 | Thread.sleep(milliseconds); 16 | } 17 | catch (InterruptedException e) { 18 | log.error("Threads.sleep: {}", e.getMessage()); 19 | } 20 | } 21 | 22 | /** 23 | * 停止线程池 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. 如果超时, 则调用shutdownNow, 24 | * 取消在workQueue中Pending的任务,并中断所有阻塞函数. 如果仍人超時,則強制退出. 另对在shutdown时线程本身被调用中断做了处理. 25 | */ 26 | public static void shutdownAndAwaitTermination(ExecutorService pool) { 27 | if (pool != null && !pool.isShutdown()) { 28 | pool.shutdown(); 29 | try { 30 | if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { 31 | pool.shutdownNow(); 32 | if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { 33 | log.info("Pool did not terminate"); 34 | } 35 | } 36 | } 37 | catch (InterruptedException ie) { 38 | pool.shutdownNow(); 39 | Thread.currentThread().interrupt(); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * 打印线程异常信息 46 | */ 47 | public static void printException(Runnable r, Throwable t) { 48 | if (t == null && r instanceof Future) { 49 | try { 50 | Future future = (Future) r; 51 | if (future.isDone()) { 52 | future.get(); 53 | } 54 | } 55 | catch (CancellationException ce) { 56 | t = ce; 57 | } 58 | catch (ExecutionException ee) { 59 | t = ee.getCause(); 60 | } 61 | catch (InterruptedException ie) { 62 | Thread.currentThread().interrupt(); 63 | } 64 | } 65 | if (t != null) { 66 | log.error(t.getMessage(), t); 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/main/java/com/pea/component/aspect/WebLogAspect.java: -------------------------------------------------------------------------------- 1 | package com.pea.component.aspect; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.pea.business.sys.domain.SysOperationLog; 5 | import com.pea.business.sys.domain.SysUser; 6 | import com.pea.business.sys.service.SysOperationLogService; 7 | import com.pea.common.annotation.SysLogInterface; 8 | import com.pea.common.enums.DelStatusEnums; 9 | import com.pea.common.enums.StatusEnums; 10 | import com.pea.common.utils.IpUtil; 11 | import com.pea.common.utils.SecurityUtil; 12 | import com.pea.common.utils.ServletUtils; 13 | import jakarta.servlet.http.HttpServletRequest; 14 | import jakarta.servlet.http.HttpServletResponse; 15 | import lombok.RequiredArgsConstructor; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.apache.commons.lang3.StringUtils; 18 | import org.aspectj.lang.JoinPoint; 19 | import org.aspectj.lang.Signature; 20 | import org.aspectj.lang.annotation.AfterReturning; 21 | import org.aspectj.lang.annotation.AfterThrowing; 22 | import org.aspectj.lang.annotation.Aspect; 23 | import org.aspectj.lang.annotation.Pointcut; 24 | import org.aspectj.lang.reflect.MethodSignature; 25 | import org.springframework.http.HttpMethod; 26 | import org.springframework.stereotype.Component; 27 | import org.springframework.web.multipart.MultipartFile; 28 | import org.springframework.web.servlet.HandlerMapping; 29 | 30 | import java.lang.reflect.Method; 31 | import java.util.Map; 32 | 33 | @Slf4j 34 | @Aspect 35 | @Component 36 | @RequiredArgsConstructor 37 | public class WebLogAspect { 38 | 39 | private final SysOperationLogService sysOperationLogService; 40 | 41 | /** 42 | * 配置织入点 43 | */ 44 | @Pointcut("@annotation(com.pea.common.annotation.SysLogInterface)") 45 | public void logPointCut() { 46 | 47 | } 48 | 49 | /** 50 | * 处理完请求后执行 51 | * @param joinPoint 切点 52 | */ 53 | @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult") 54 | public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) { 55 | handleLog(joinPoint, null, jsonResult); 56 | } 57 | 58 | /** 59 | * 拦截异常操作 60 | * @param joinPoint 切点 61 | * @param e 异常 62 | */ 63 | @AfterThrowing(value = "logPointCut()", throwing = "e") 64 | public void doAfterThrowing(JoinPoint joinPoint, Exception e) { 65 | handleLog(joinPoint, e, null); 66 | } 67 | 68 | protected void handleLog(final JoinPoint joinPoint, final Exception e, 69 | Object jsonResult) { 70 | try { 71 | // 获得注解 72 | SysLogInterface controllerLog = getAnnotationLog(joinPoint); 73 | if (controllerLog == null) { 74 | return; 75 | } 76 | 77 | // 获取当前的用户 78 | SysUser loginUser = SecurityUtil.getSysUser(); 79 | 80 | // *========数据库日志=========*// 81 | SysOperationLog operLog = new SysOperationLog(); 82 | operLog.setStatus(StatusEnums.ENABLE.getCode()); 83 | operLog.setIsDeleted(DelStatusEnums.DISABLE.getCode()); 84 | // 请求的地址 85 | String ip = IpUtil.getIp(ServletUtils.getRequest()); 86 | operLog.setReqIp(ip); 87 | // 返回参数 88 | operLog.setResp(JSONUtil.toJsonStr(jsonResult)); 89 | 90 | if (loginUser != null) { 91 | operLog.setOperName(loginUser.getUserName()); 92 | } 93 | 94 | if (e != null) { 95 | operLog.setStatus(StatusEnums.DISABLE.getCode()); 96 | operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); 97 | } 98 | // 设置方法名称 99 | String className = joinPoint.getTarget().getClass().getName(); 100 | String methodName = joinPoint.getSignature().getName(); 101 | operLog.setMethod(className + "." + methodName + "()"); 102 | // 设置请求方式 103 | operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); 104 | // 处理设置注解上的参数 105 | getControllerMethodDescription(joinPoint, controllerLog, operLog); 106 | // TODO 保存数据库 需要处理成异步 107 | // AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); 108 | sysOperationLogService.save(operLog); 109 | } 110 | catch (Exception exp) { 111 | // 记录本地异常日志 112 | log.error("==前置通知异常=="); 113 | log.error("异常信息:{}", exp.getMessage()); 114 | } 115 | } 116 | 117 | /** 118 | * 获取注解中对方法的描述信息 用于Controller层注解 119 | * @param log 日志 120 | * @param sysOperationLog 操作日志 121 | */ 122 | public void getControllerMethodDescription(JoinPoint joinPoint, SysLogInterface log, 123 | SysOperationLog sysOperationLog) { 124 | // 设置action动作 125 | sysOperationLog.setBusinessType(log.businessType().getCode()); 126 | // 设置标题 127 | sysOperationLog.setDescription(log.title()); 128 | // 是否需要保存request,参数和值 129 | if (log.isSaveRequestData()) { 130 | // 获取参数的信息,传入到数据库中。 131 | setRequestValue(joinPoint, sysOperationLog); 132 | } 133 | } 134 | 135 | /** 136 | * 获取请求的参数,放到log中 137 | * @param sysOperationLog 操作日志 138 | */ 139 | private void setRequestValue(JoinPoint joinPoint, SysOperationLog sysOperationLog) { 140 | String requestMethod = sysOperationLog.getRequestMethod(); 141 | if (HttpMethod.PUT.name().equals(requestMethod) 142 | || HttpMethod.POST.name().equals(requestMethod)) { 143 | String params = argsArrayToString(joinPoint.getArgs()); 144 | sysOperationLog.setReqParam(StringUtils.substring(params, 0, 2000)); 145 | } 146 | else { 147 | Map paramsMap = (Map) ServletUtils.getRequest() 148 | .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); 149 | sysOperationLog.setReqParam(StringUtils.substring(paramsMap.toString(), 0, 2000)); 150 | } 151 | } 152 | 153 | /** 154 | * 是否存在注解,如果存在就获取 155 | */ 156 | private SysLogInterface getAnnotationLog(JoinPoint joinPoint) { 157 | Signature signature = joinPoint.getSignature(); 158 | MethodSignature methodSignature = (MethodSignature) signature; 159 | Method method = methodSignature.getMethod(); 160 | 161 | if (method != null) { 162 | return method.getAnnotation(SysLogInterface.class); 163 | } 164 | return null; 165 | } 166 | 167 | /** 168 | * 参数拼装 169 | */ 170 | private String argsArrayToString(Object[] paramsArray) { 171 | StringBuilder params = new StringBuilder(); 172 | if (paramsArray != null) { 173 | for (Object o : paramsArray) { 174 | if (!isFilterObject(o)) { 175 | Object jsonObj = JSONUtil.toJsonStr(o); 176 | params.append(jsonObj.toString()).append(" "); 177 | } 178 | } 179 | } 180 | return params.toString().trim(); 181 | } 182 | 183 | /** 184 | * 判断是否需要过滤的对象。 185 | * 186 | * @param o 对象信息。 187 | * @return 如果是需要过滤的对象,则返回true;否则返回false。 188 | */ 189 | public boolean isFilterObject(final Object o) { 190 | return o instanceof MultipartFile || o instanceof HttpServletRequest 191 | || o instanceof HttpServletResponse; 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/com/pea/component/security/filter/JwtAuthenticationTokenFilter.java: -------------------------------------------------------------------------------- 1 | package com.pea.component.security.filter; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import com.pea.common.exception.GlobalExceptionEnum; 5 | import com.pea.common.utils.ExceptionUtil; 6 | import com.pea.common.utils.IpUtil; 7 | import com.pea.common.utils.JwtTokenUtil; 8 | import jakarta.servlet.FilterChain; 9 | import jakarta.servlet.ServletException; 10 | import jakarta.servlet.http.HttpServletRequest; 11 | import jakarta.servlet.http.HttpServletResponse; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.context.SecurityContextHolder; 16 | import org.springframework.security.core.userdetails.UserDetails; 17 | import org.springframework.security.core.userdetails.UserDetailsService; 18 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 19 | import org.springframework.stereotype.Component; 20 | import org.springframework.web.filter.OncePerRequestFilter; 21 | 22 | import java.io.IOException; 23 | import java.util.Date; 24 | 25 | /** 26 | * Jwt 过滤器 27 | */ 28 | @Slf4j 29 | @Component 30 | public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { 31 | 32 | @Autowired 33 | private UserDetailsService userDetailsService; 34 | 35 | @Autowired 36 | private JwtTokenUtil jwtTokenUtil; 37 | 38 | @Override 39 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 40 | throws ServletException, IOException { 41 | 42 | 43 | long startTime, endTime; 44 | 45 | String authHeader = request.getHeader(jwtTokenUtil.getTokenHeader()); 46 | String username = null; 47 | 48 | if (authHeader != null && authHeader.startsWith(jwtTokenUtil.getTokenHead())) { 49 | // The part after "Bearer " 50 | String authToken = authHeader.substring(jwtTokenUtil.getTokenHead().length()); 51 | // 页面上传过来的token 解析出来的username 52 | username = jwtTokenUtil.getUserNameFromToken(authToken); 53 | 54 | if (username != null 55 | && SecurityContextHolder.getContext().getAuthentication() == null) { 56 | // 从数据库中获取用户信息 57 | UserDetails userDetails = this.userDetailsService 58 | .loadUserByUsername(username); 59 | if (jwtTokenUtil.validateToken(authToken, userDetails)) { 60 | 61 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( 62 | userDetails, null, userDetails.getAuthorities()); 63 | 64 | authentication.setDetails( 65 | new WebAuthenticationDetailsSource().buildDetails(request)); 66 | SecurityContextHolder.getContext().setAuthentication(authentication); 67 | } else { 68 | ExceptionUtil.throwEx(GlobalExceptionEnum.ERROR_TOKEN_EXPIRED); 69 | } 70 | } 71 | } 72 | 73 | startTime = System.currentTimeMillis(); 74 | 75 | filterChain.doFilter(request, response); 76 | 77 | endTime = System.currentTimeMillis(); 78 | 79 | String fullUrl = request.getRequestURL().toString(); 80 | String requestType = request.getMethod(); 81 | formMapKey(username, fullUrl, requestType, IpUtil.getIp(request), authHeader, 82 | endTime, startTime); 83 | 84 | } 85 | 86 | /** 87 | * @param methodName 请求接口 88 | * @param requestType 请求类型 89 | * @param ip 请求IP 90 | * @param token 请求token 91 | */ 92 | private void formMapKey(String username, String methodName, String requestType, 93 | String ip, String token, long endTime, long startTime) { 94 | String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss SSS"); 95 | log.debug( 96 | "接口信息:time:{} , url:{} , type:{} , ip:{} ,username:{} token:{} , cost:{}ms\n", 97 | time, methodName, requestType, ip, username, token, 98 | (endTime - startTime)); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/pea/component/security/handle/RestAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.pea.component.security.handle; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.pea.common.api.Result; 5 | import org.springframework.security.core.AuthenticationException; 6 | import org.springframework.security.web.AuthenticationEntryPoint; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * 当未登录或者token失效访问接口时,自定义的返回结果 13 | */ 14 | @Component 15 | public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { 16 | 17 | @Override 18 | public void commence(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, 19 | AuthenticationException authException) throws IOException { 20 | response.setCharacterEncoding("UTF-8"); 21 | response.setContentType("application/json"); 22 | response.getWriter().println( 23 | JSONUtil.parse(Result.unauthorized(authException.getMessage()))); 24 | response.getWriter().flush(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/pea/component/security/handle/RestfulAccessDeniedHandler.java: -------------------------------------------------------------------------------- 1 | package com.pea.component.security.handle; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.pea.common.api.Result; 5 | import org.springframework.security.access.AccessDeniedException; 6 | import org.springframework.security.web.access.AccessDeniedHandler; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * 当访问接口没有权限时,自定义的返回结果 13 | */ 14 | @Component 15 | public class RestfulAccessDeniedHandler implements AccessDeniedHandler { 16 | 17 | @Override 18 | public void handle(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, 19 | AccessDeniedException accessDeniedException) throws IOException { 20 | response.setCharacterEncoding("UTF-8"); 21 | response.setContentType("application/json"); 22 | response.getWriter().println(JSONUtil.parse(Result.forbidden(accessDeniedException.getMessage()))); 23 | response.getWriter().flush(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/pea/component/security/service/PermissionService.java: -------------------------------------------------------------------------------- 1 | package com.pea.component.security.service; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import com.pea.common.utils.SecurityUtil; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | /** 12 | * 权限处理器 13 | */ 14 | @Service("pre") 15 | public class PermissionService { 16 | 17 | /** 18 | * 所有权限标识 19 | */ 20 | private static final String ALL_PERMISSION = "*:*:*"; 21 | 22 | public Boolean hasPermission(String... permissions) { 23 | if (ObjectUtil.isEmpty(permissions)) { 24 | return false; 25 | } 26 | // 获取当前用户的所有权限 27 | List perms = SecurityUtil.getUserDetails().getAuthorities().stream() 28 | .map(GrantedAuthority::getAuthority).toList(); 29 | 30 | // 判断当前用户的所有权限是否包含接口上定义的权限 31 | return perms.contains(ALL_PERMISSION) 32 | || Arrays.stream(permissions).anyMatch(perms::contains); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/pea/component/security/service/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pea.component.security.service; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 5 | import com.pea.business.sys.domain.SysUser; 6 | import com.pea.business.sys.mapper.SysResourceMapper; 7 | import com.pea.business.sys.mapper.SysRoleMapper; 8 | import com.pea.business.sys.mapper.SysUserMapper; 9 | import com.pea.common.enums.CacheConstants; 10 | import com.pea.common.enums.StatusEnums; 11 | import com.pea.common.exception.GlobalExceptionEnum; 12 | import com.pea.common.utils.ExceptionUtil; 13 | import com.pea.common.utils.SysUserDetail; 14 | import lombok.RequiredArgsConstructor; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.cache.Cache; 17 | import org.springframework.cache.CacheManager; 18 | import org.springframework.security.core.userdetails.UserDetails; 19 | import org.springframework.security.core.userdetails.UserDetailsService; 20 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 21 | import org.springframework.stereotype.Service; 22 | 23 | import java.util.List; 24 | import java.util.Objects; 25 | 26 | @Slf4j 27 | @RequiredArgsConstructor 28 | @Service 29 | public class UserDetailsServiceImpl implements UserDetailsService { 30 | 31 | private final CacheManager cacheManager; 32 | 33 | private final SysUserMapper sysUserMapper; 34 | 35 | private final SysRoleMapper sysRoleMapper; 36 | 37 | private final SysResourceMapper sysResourceMapper; 38 | 39 | @Override 40 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 41 | 42 | Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS); 43 | if (cache != null && cache.get(username) != null) { 44 | return (SysUserDetail) Objects.requireNonNull(cache.get(username)).get(); 45 | } 46 | 47 | // 获取登录用户信息 48 | SysUser user = sysUserMapper.selectOne( 49 | Wrappers.lambdaQuery().eq(SysUser::getUserName, username)); 50 | 51 | // 用户不存在 52 | if (ObjectUtil.isEmpty(user)) { 53 | ExceptionUtil.throwEx(GlobalExceptionEnum.ERROR_USER_NOT_EXIST); 54 | } 55 | 56 | // 用户停用 57 | if (StatusEnums.DISABLE.getCode().equals(user.getStatus())) { 58 | ExceptionUtil.throwEx(GlobalExceptionEnum.USER_DISABLED); 59 | } 60 | 61 | // 获取用户菜单权限标识 62 | List permissions = sysResourceMapper.getUserPermissions(user.getId()); 63 | 64 | // 获取用户角色 65 | List userRole = sysRoleMapper.getUserRole(user.getId()); 66 | 67 | SysUserDetail userDetails = new SysUserDetail(user, permissions, userRole); 68 | 69 | if (cache != null) { 70 | cache.put(username, userDetails); 71 | } 72 | return userDetails; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/resources/application-local.yaml.bak: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | type: com.zaxxer.hikari.HikariDataSource 4 | driver-class-name: com.mysql.cj.jdbc.Driver 5 | url: jdbc:mysql://ip:port/pea?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false 6 | username: root 7 | password: '密码' 8 | hikari: 9 | #连接池名 10 | pool-name: DataHikari 11 | #最小空闲连接数 12 | minimum-idle: 10 13 | #空闲连接存活最大时间,默认600000《10分钟) 14 | idle-timeout: 600000 15 | # 连接池最大连接数,默认是10 16 | maximum-pool-size: 60 17 | # 此属性控制从池返回的连接的默认自动提交行为,默认值: true 18 | auto-commit: true 19 | #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟 20 | max-lifetime: 1800000 21 | # 数据库连接超时时间,默认30秒,即30000 22 | connection-timeout: 30000 23 | connection-test-query: SELECT 1 24 | cache: 25 | type: 26 | 27 | # 日志配置 28 | logging: 29 | config: classpath:logback-spring.xml 30 | file: 31 | path: logs/pea 32 | level: 33 | root: info 34 | com.pea: debug 35 | -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | tomcat: 3 | uri-encoding: UTF-8 4 | port: 9528 5 | 6 | spring: 7 | profiles: 8 | active: local 9 | 10 | jwt: 11 | tokenHeader: Authorization #JWT存储的请求头 12 | secret: BfHiGpMfztFYwAEKBRGB #JWT加解密使用的密钥 13 | expiration: 43200 #JWT的超期限时间(12*60*60) 12小时 14 | tokenHead: 'Bearer ' #JWT负载中拿到开头 15 | 16 | #mybatis 17 | mybatis-plus: 18 | mapper-locations: classpath*:/mapper/**/*.xml 19 | typeAliasesPackage: com.pea.business.*.*.domain 20 | global-config: 21 | db-config: 22 | id-type: AUTO 23 | logic-delete-value: 0 24 | logic-not-delete-value: 1 25 | banner: false 26 | configuration: 27 | map-underscore-to-camel-case: true 28 | cache-enabled: false 29 | call-setters-on-nulls: true 30 | jdbc-type-for-null: 'null' 31 | 32 | springdoc: 33 | swagger-ui: 34 | path: /swagger-ui.html 35 | tags-sorter: alpha 36 | api-docs: 37 | path: /v3/api-docs 38 | group-configs: 39 | - group: 'default' 40 | display-name: '系统管理' 41 | paths-to-match: '/**' 42 | packages-to-scan: com.pea.business.sys 43 | - group: 'auth' 44 | display-name: '认证模块' 45 | paths-to-match: '/**' 46 | packages-to-scan: com.pea.business.auth 47 | default-flat-param-object: true 48 | 49 | knife4j: 50 | enable: true 51 | setting: 52 | language: zh_cn -------------------------------------------------------------------------------- /src/main/resources/ip2region/ip2region.xdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haitang1894/pea/a751adb98f592958d08321e690e87a3654ae5264/src/main/resources/ip2region/ip2region.xdb -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ${LOG_PATH}/debug.log 10 | 11 | DEBUG 12 | ACCEPT 13 | DENY 14 | 15 | 16 | ${LOG_PATH}/debug-%d{yyyy-MM-dd}-%i.log 17 | 19 | 60MB 20 | 21 | 10 22 | 23 | 24 | 25 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [${HOSTNAME}] [%thread] %level %logger{36}@%method:%line - %msg%n 26 | 27 | 28 | 29 | 30 | ${LOG_PATH}/info.log 31 | 32 | INFO 33 | ACCEPT 34 | DENY 35 | 36 | 37 | ${LOG_PATH}/info-%d{yyyy-MM-dd}-%i.log 38 | 40 | 60MB 41 | 42 | 10 43 | 44 | 45 | 46 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [${HOSTNAME}] [%thread] %level %logger{36}@%method:%line - %msg%n 47 | 48 | 49 | 50 | 51 | ${LOG_PATH}/warn.log 52 | 53 | WARN 54 | ACCEPT 55 | DENY 56 | 57 | 58 | ${LOG_PATH}/warn-%d{yyyy-MM-dd}-%i.log 59 | 61 | 60MB 62 | 63 | 10 64 | 65 | 66 | 67 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [${HOSTNAME}] [%thread] %level %logger{36}@%method:%line - %msg%n 68 | 69 | 70 | 71 | 72 | ${LOG_PATH}/error.log 73 | 74 | ERROR 75 | ACCEPT 76 | DENY 77 | 78 | 79 | ${LOG_PATH}/error-%d{yyyy-MM-dd}-%i.log 80 | 82 | 60MB 83 | 84 | 10 85 | 86 | 87 | 88 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [${HOSTNAME}] [%thread] %level %logger{36}@%method:%line - %msg%n 89 | 90 | 91 | 92 | 93 | 94 | 95 | ${CONSOLE_LOG_PATTERN} 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/resources/mapper/sys/SysOperationLogMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | t.id, 29 | t.type, 30 | t.method, 31 | t.request_method, 32 | t.description, 33 | t.req_ip, 34 | t.req_param, 35 | t.resp, 36 | t.error_msg, 37 | t.create_id, 38 | t.create_by, 39 | t.create_time, 40 | t.update_id, 41 | t.update_by, 42 | t.update_time, 43 | t.is_deleted, 44 | t.delete_time 45 | 46 | 47 | 48 | truncate table t_sys_operation_log 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/main/resources/mapper/sys/SysResourceMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | t.id, 31 | t.parent_id, 32 | t.ui_path, 33 | t.menu_type, 34 | t.status, 35 | t.menu_name, 36 | t.route_name, 37 | t.route_path, 38 | t.component, 39 | t.meta, 40 | t.weight, 41 | t.create_id, 42 | t.create_by, 43 | t.update_id, 44 | t.update_by, 45 | t.create_time, 46 | t.update_time, 47 | t.is_deleted, 48 | t.delete_time 49 | 50 | 51 | 90 | 91 | 121 | 122 | -------------------------------------------------------------------------------- /src/main/resources/mapper/sys/SysRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | t.id, 26 | t.role_name, 27 | t.role_code, 28 | t.status, 29 | t.role_desc, 30 | t.type, 31 | t.create_id, 32 | t.create_by, 33 | t.update_id, 34 | t.update_by, 35 | t.create_time, 36 | t.update_time, 37 | t.is_deleted, 38 | t.delete_time 39 | 40 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/resources/mapper/sys/SysUserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | t.id, 31 | t.nick_name, 32 | t.user_name, 33 | t.password, 34 | t.status, 35 | t.otp_secret, 36 | t.user_gender, 37 | t.user_phone, 38 | t.user_email, 39 | t.last_login_time, 40 | t.last_login_ip, 41 | t.create_id, 42 | t.create_by, 43 | t.update_id, 44 | t.update_by, 45 | t.create_time, 46 | t.update_time, 47 | t.is_deleted, 48 | t.delete_time 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/test/java/com/pea/PeaApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.pea; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class PeaApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/.env: -------------------------------------------------------------------------------- 1 | VITE_BASE_URL=/ 2 | 3 | VITE_APP_TITLE=SoybeanAdmin 4 | 5 | VITE_APP_DESC=SoybeanAdmin is a fresh and elegant admin template 6 | 7 | # the prefix of the icon name 8 | VITE_ICON_PREFIX=icon 9 | 10 | # the prefix of the local svg icon component, must include VITE_ICON_PREFIX 11 | # format {VITE_ICON_PREFIX}-{local icon name} 12 | VITE_ICON_LOCAL_PREFIX=icon-local 13 | 14 | # auth route mode: static | dynamic 15 | VITE_AUTH_ROUTE_MODE=dynamic 16 | 17 | # static auth route home 18 | VITE_ROUTE_HOME=home 19 | 20 | # default menu icon 21 | VITE_MENU_ICON=mdi:menu 22 | 23 | # whether to enable http proxy when is dev mode 24 | VITE_HTTP_PROXY=Y 25 | 26 | # vue-router mode: hash | history | memory 27 | VITE_ROUTER_HISTORY_MODE=history 28 | 29 | # success code of backend service, when the code is received, the request is successful 30 | VITE_SERVICE_SUCCESS_CODE=0000 31 | 32 | # logout codes of backend service, when the code is received, the user will be logged out and redirected to login page 33 | VITE_SERVICE_LOGOUT_CODES=8888,8889 34 | 35 | # modal logout codes of backend service, when the code is received, the user will be logged out by displaying a modal 36 | VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778 37 | 38 | # token expired codes of backend service, when the code is received, it will refresh the token and resend the request 39 | VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998 40 | 41 | # when the route mode is static, the defined super role 42 | VITE_STATIC_SUPER_ROLE=R_SUPER 43 | 44 | # sourcemap 45 | VITE_SOURCE_MAP=N 46 | 47 | # Used to differentiate storage across different domains 48 | VITE_STORAGE_PREFIX=SOY_ 49 | -------------------------------------------------------------------------------- /web/.env.test: -------------------------------------------------------------------------------- 1 | # backend service base url, test environment 2 | VITE_SERVICE_BASE_URL=http://localhost:9528 3 | 4 | # other backend service base url, test environment 5 | VITE_OTHER_SERVICE_BASE_URL= `{ 6 | "demo": "http://localhost:9528" 7 | }` 8 | -------------------------------------------------------------------------------- /web/manage/menu/modules/shared.ts: -------------------------------------------------------------------------------- 1 | const LAYOUT_PREFIX = 'layout.'; 2 | const VIEW_PREFIX = 'view.'; 3 | const FIRST_LEVEL_ROUTE_COMPONENT_SPLIT = '$'; 4 | 5 | export function getLayoutAndPage(component?: string | null) { 6 | let layout = ''; 7 | let page = ''; 8 | 9 | const [layoutOrPage, pageItem] = component?.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT) || []; 10 | 11 | layout = getLayout(layoutOrPage); 12 | page = getPage(pageItem || layoutOrPage); 13 | 14 | return { layout, page }; 15 | } 16 | 17 | function getLayout(layout: string) { 18 | return layout.startsWith(LAYOUT_PREFIX) ? layout.replace(LAYOUT_PREFIX, '') : ''; 19 | } 20 | 21 | function getPage(page: string) { 22 | return page.startsWith(VIEW_PREFIX) ? page.replace(VIEW_PREFIX, '') : ''; 23 | } 24 | 25 | export function transformLayoutAndPageToComponent(layout: string, page: string) { 26 | const hasLayout = Boolean(layout); 27 | const hasPage = Boolean(page); 28 | 29 | if (hasLayout && hasPage) { 30 | return `${LAYOUT_PREFIX}${layout}${FIRST_LEVEL_ROUTE_COMPONENT_SPLIT}${VIEW_PREFIX}${page}`; 31 | } 32 | 33 | if (hasLayout) { 34 | return `${LAYOUT_PREFIX}${layout}`; 35 | } 36 | 37 | if (hasPage) { 38 | return `${VIEW_PREFIX}${page}`; 39 | } 40 | 41 | return ''; 42 | } 43 | 44 | /** 45 | * Get route name by route path 46 | * 47 | * @param routeName 48 | */ 49 | export function getRoutePathByRouteName(routeName: string) { 50 | return `/${routeName.replace(/_/g, '/')}`; 51 | } 52 | 53 | /** 54 | * Get path param from route path 55 | * 56 | * @param routePath route path 57 | */ 58 | export function getPathParamFromRoutePath(routePath: string) { 59 | const [path, param = ''] = routePath.split('/:'); 60 | 61 | return { 62 | path, 63 | param 64 | }; 65 | } 66 | 67 | /** 68 | * Get route path with param 69 | * 70 | * @param routePath route path 71 | * @param param path param 72 | */ 73 | export function getRoutePathWithParam(routePath: string, param: string) { 74 | if (param.trim()) { 75 | return `${routePath}/:${param}`; 76 | } 77 | 78 | return routePath; 79 | } 80 | -------------------------------------------------------------------------------- /web/manage/role/index.vue: -------------------------------------------------------------------------------- 1 | 129 | 130 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /web/manage/role/modules/button-auth-modal.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /web/manage/role/modules/menu-auth-modal.vue: -------------------------------------------------------------------------------- 1 | 102 | 103 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /web/manage/role/modules/role-operate-drawer.vue: -------------------------------------------------------------------------------- 1 | 113 | 114 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /web/manage/role/modules/role-search.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /web/manage/user-detail/[id].vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/manage/user/index.vue: -------------------------------------------------------------------------------- 1 | 160 | 161 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /web/manage/user/modules/user-operate-drawer.vue: -------------------------------------------------------------------------------- 1 | 152 | 153 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /web/manage/user/modules/user-search.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /web/service/api/auth.ts: -------------------------------------------------------------------------------- 1 | import { request } from '../request'; 2 | 3 | /** 4 | * Login 5 | * 6 | * @param userName User name 7 | * @param password Password 8 | */ 9 | export function fetchLogin(userName: string, password: string) { 10 | return request({ 11 | url: '/auth/login', 12 | method: 'post', 13 | data: { 14 | userName, 15 | password 16 | } 17 | }); 18 | } 19 | 20 | /** Get user info */ 21 | export function fetchGetUserInfo() { 22 | return request({ url: '/auth/getUserInfo' }); 23 | } 24 | 25 | /** 26 | * Refresh token 27 | * 28 | * @param refreshToken Refresh token 29 | */ 30 | export function fetchRefreshToken(refreshToken: string) { 31 | return request({ 32 | url: '/auth/refreshToken', 33 | method: 'post', 34 | data: { 35 | refreshToken 36 | } 37 | }); 38 | } 39 | 40 | /** 41 | * return custom backend error 42 | * 43 | * @param code error code 44 | * @param msg error message 45 | */ 46 | export function fetchCustomBackendError(code: string, msg: string) { 47 | return request({ url: '/auth/error', params: { code, msg } }); 48 | } 49 | -------------------------------------------------------------------------------- /web/service/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | export * from './route'; 3 | export * from './system-manage'; 4 | -------------------------------------------------------------------------------- /web/service/api/route.ts: -------------------------------------------------------------------------------- 1 | import { request } from '../request'; 2 | 3 | /** get constant routes */ 4 | export function fetchGetConstantRoutes() { 5 | return request({ url: '/route/getConstantRoutes' }); 6 | } 7 | 8 | /** get user routes */ 9 | export function fetchGetUserRoutes() { 10 | return request({ url: '/route/getUserRoutes' }); 11 | } 12 | 13 | /** 14 | * whether the route is exist 15 | * 16 | * @param routeName route name 17 | */ 18 | export function fetchIsRouteExist(routeName: string) { 19 | return request({ url: '/route/isRouteExist', params: { routeName } }); 20 | } 21 | -------------------------------------------------------------------------------- /web/service/api/system-manage.ts: -------------------------------------------------------------------------------- 1 | import { request } from '../request'; 2 | 3 | /** get role list */ 4 | export function fetchGetRoleList(params?: Api.SystemManage.RoleSearchParams) { 5 | return request({ 6 | url: '/systemManage/getRoleList', 7 | method: 'get', 8 | params 9 | }); 10 | } 11 | 12 | /** 13 | * get all roles 14 | * 15 | * these roles are all enabled 16 | */ 17 | export function fetchGetAllRoles() { 18 | return request({ 19 | url: '/systemManage/getAllRoles', 20 | method: 'get' 21 | }); 22 | } 23 | 24 | /** 25 | * add Role 26 | * 27 | * @param sysRole role info 28 | */ 29 | export function addRole(sysRole: Api.SystemManage.Role) { 30 | return request({ 31 | url: '/systemManage/addRole', 32 | method: 'post', 33 | data: sysRole 34 | }); 35 | } 36 | 37 | /** get user list */ 38 | export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) { 39 | return request({ 40 | url: '/systemManage/getUserList', 41 | method: 'get', 42 | params 43 | }); 44 | } 45 | 46 | /** 47 | * create user 48 | * 49 | * @param sysRole role info 50 | */ 51 | export function createUser(user: Api.SystemManage.User) { 52 | return request({ 53 | url: '/systemManage/createUser', 54 | method: 'post', 55 | data: user 56 | }); 57 | } 58 | 59 | /** 60 | * update user 61 | * 62 | * @param sysRole role info 63 | */ 64 | export function updateUser(user: Api.SystemManage.User) { 65 | return request({ 66 | url: '/systemManage/update', 67 | method: 'post', 68 | data: user 69 | }); 70 | } 71 | 72 | /** add menu */ 73 | export function addResource(params?: Api.SystemManage.Menu) { 74 | return request({ 75 | url: '/sys/resource/add', 76 | method: 'post', 77 | data: params 78 | }); 79 | } 80 | 81 | /** get menu list */ 82 | export function fetchGetMenuList() { 83 | return request({ 84 | url: '/systemManage/getMenuList/v2', 85 | method: 'get' 86 | }); 87 | } 88 | 89 | /** get all pages */ 90 | export function fetchGetAllPages() { 91 | return request({ 92 | url: '/systemManage/getAllPages', 93 | method: 'get' 94 | }); 95 | } 96 | 97 | /** get menu tree */ 98 | export function fetchGetMenuTree() { 99 | return request({ 100 | url: '/systemManage/getMenuTree', 101 | method: 'get' 102 | }); 103 | } 104 | 105 | /** get role resource id */ 106 | export function getRoleResourceId(roleId: number) { 107 | return request({ 108 | url: '/systemManage/getRoleResourceId', 109 | method: 'post', 110 | data: roleId 111 | }); 112 | } 113 | 114 | /** update role resource info */ 115 | export function updateRoleResourceInfo(roleId: number, resourceId: number[]) { 116 | return request({ 117 | url: '/systemManage/updateRoleResourceInfo', 118 | method: 'post', 119 | data: { 120 | roleId, 121 | resourceId 122 | } 123 | }); 124 | } 125 | -------------------------------------------------------------------------------- /web/service/request/index.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosResponse } from 'axios'; 2 | import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios'; 3 | import { useAuthStore } from '@/store/modules/auth'; 4 | import { $t } from '@/locales'; 5 | import { localStg } from '@/utils/storage'; 6 | import { getServiceBaseURL } from '@/utils/service'; 7 | import { handleRefreshToken, showErrorMsg } from './shared'; 8 | import type { RequestInstanceState } from './type'; 9 | 10 | const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; 11 | const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); 12 | 13 | export const request = createFlatRequest( 14 | { 15 | baseURL 16 | }, 17 | { 18 | async onRequest(config) { 19 | const { headers } = config; 20 | 21 | // set token 22 | const token = localStg.get('token'); 23 | const Authorization = token ? `Bearer ${token}` : null; 24 | Object.assign(headers, { Authorization }); 25 | 26 | return config; 27 | }, 28 | isBackendSuccess(response) { 29 | // when the backend response code is "0000"(default), it means the request is success 30 | // to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file 31 | return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE; 32 | }, 33 | async onBackendFail(response, instance) { 34 | const authStore = useAuthStore(); 35 | 36 | function handleLogout() { 37 | authStore.resetStore(); 38 | } 39 | 40 | function logoutAndCleanup() { 41 | handleLogout(); 42 | window.removeEventListener('beforeunload', handleLogout); 43 | 44 | request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg); 45 | } 46 | 47 | // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page 48 | const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []; 49 | if (logoutCodes.includes(response.data.code)) { 50 | handleLogout(); 51 | return null; 52 | } 53 | 54 | // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal 55 | const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; 56 | if (modalLogoutCodes.includes(response.data.code) && !request.state.errMsgStack?.includes(response.data.msg)) { 57 | request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg]; 58 | 59 | // prevent the user from refreshing the page 60 | window.addEventListener('beforeunload', handleLogout); 61 | 62 | window.$dialog?.error({ 63 | title: 'Error', 64 | content: response.data.code, 65 | positiveText: $t('common.confirm'), 66 | maskClosable: false, 67 | onPositiveClick() { 68 | logoutAndCleanup(); 69 | }, 70 | onClose() { 71 | logoutAndCleanup(); 72 | } 73 | }); 74 | 75 | return null; 76 | } 77 | 78 | // when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token 79 | // the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes` 80 | const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; 81 | if (expiredTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) { 82 | request.state.isRefreshingToken = true; 83 | 84 | const refreshConfig = await handleRefreshToken(response.config); 85 | 86 | request.state.isRefreshingToken = false; 87 | 88 | if (refreshConfig) { 89 | return instance.request(refreshConfig) as Promise; 90 | } 91 | } 92 | 93 | return null; 94 | }, 95 | transformBackendResponse(response) { 96 | return response.data.data; 97 | }, 98 | onError(error) { 99 | // when the request is fail, you can show error message 100 | 101 | let message = error.message; 102 | let backendErrorCode = ''; 103 | 104 | // get backend error message and code 105 | if (error.code === BACKEND_ERROR_CODE) { 106 | message = error.response?.data?.msg || message; 107 | backendErrorCode = error.response?.data?.code || ''; 108 | } 109 | 110 | // the error message is displayed in the modal 111 | const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; 112 | if (modalLogoutCodes.includes(backendErrorCode)) { 113 | return; 114 | } 115 | 116 | // when the token is expired, refresh token and retry request, so no need to show error message 117 | const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; 118 | if (expiredTokenCodes.includes(backendErrorCode)) { 119 | return; 120 | } 121 | 122 | showErrorMsg(request.state, message); 123 | } 124 | } 125 | ); 126 | 127 | export const demoRequest = createRequest( 128 | { 129 | baseURL: otherBaseURL.demo 130 | }, 131 | { 132 | async onRequest(config) { 133 | const { headers } = config; 134 | 135 | // set token 136 | const token = localStg.get('token'); 137 | const Authorization = token ? `Bearer ${token}` : null; 138 | Object.assign(headers, { Authorization }); 139 | 140 | return config; 141 | }, 142 | isBackendSuccess(response) { 143 | // when the backend response code is "200", it means the request is success 144 | // you can change this logic by yourself 145 | return response.data.status === '200'; 146 | }, 147 | async onBackendFail(_response) { 148 | // when the backend response code is not "200", it means the request is fail 149 | // for example: the token is expired, refresh token and retry request 150 | }, 151 | transformBackendResponse(response) { 152 | return response.data.result; 153 | }, 154 | onError(error) { 155 | // when the request is fail, you can show error message 156 | 157 | let message = error.message; 158 | 159 | // show backend error message 160 | if (error.code === BACKEND_ERROR_CODE) { 161 | message = error.response?.data?.message || message; 162 | } 163 | 164 | window.$message?.error(message); 165 | } 166 | } 167 | ); 168 | -------------------------------------------------------------------------------- /web/service/request/shared.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosRequestConfig } from 'axios'; 2 | import { useAuthStore } from '@/store/modules/auth'; 3 | import { localStg } from '@/utils/storage'; 4 | import { fetchRefreshToken } from '../api'; 5 | import type { RequestInstanceState } from './type'; 6 | 7 | /** 8 | * refresh token 9 | * 10 | * @param axiosConfig - request config when the token is expired 11 | */ 12 | export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) { 13 | const { resetStore } = useAuthStore(); 14 | 15 | const refreshToken = localStg.get('refreshToken') || ''; 16 | const { error, data } = await fetchRefreshToken(refreshToken); 17 | if (!error) { 18 | localStg.set('token', data.token); 19 | localStg.set('refreshToken', data.refreshToken); 20 | 21 | const config = { ...axiosConfig }; 22 | if (config.headers) { 23 | config.headers.Authorization = data.token; 24 | } 25 | 26 | return config; 27 | } 28 | 29 | resetStore(); 30 | 31 | return null; 32 | } 33 | 34 | export function showErrorMsg(state: RequestInstanceState, message: string) { 35 | if (!state.errMsgStack?.length) { 36 | state.errMsgStack = []; 37 | } 38 | 39 | const isExist = state.errMsgStack.includes(message); 40 | 41 | if (!isExist) { 42 | state.errMsgStack.push(message); 43 | 44 | window.$message?.error(message, { 45 | onLeave: () => { 46 | state.errMsgStack = state.errMsgStack.filter(msg => msg !== message); 47 | 48 | setTimeout(() => { 49 | state.errMsgStack = []; 50 | }, 5000); 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /web/service/request/type.ts: -------------------------------------------------------------------------------- 1 | export interface RequestInstanceState { 2 | /** whether the request is refreshing token */ 3 | isRefreshingToken: boolean; 4 | /** the request error message stack */ 5 | errMsgStack: string[]; 6 | } 7 | -------------------------------------------------------------------------------- /web/typings/api.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Namespace Api 3 | * 4 | * All backend api type 5 | */ 6 | declare namespace Api { 7 | namespace Common { 8 | /** common params of paginating */ 9 | interface PaginatingCommonParams { 10 | /** current page number */ 11 | current: number; 12 | /** page size */ 13 | size: number; 14 | /** total count */ 15 | total: number; 16 | } 17 | 18 | /** common params of paginating query list data */ 19 | interface PaginatingQueryRecord extends PaginatingCommonParams { 20 | records: T[]; 21 | } 22 | 23 | /** 24 | * enable status 25 | * 26 | * - "1": enabled 27 | * - "2": disabled 28 | */ 29 | type EnableStatus = '1' | '2'; 30 | 31 | /** common record */ 32 | type CommonRecord = { 33 | /** record id */ 34 | id: number; 35 | /** record creator */ 36 | createBy: string; 37 | /** record create time */ 38 | createTime: string; 39 | /** record updater */ 40 | updateBy: string; 41 | /** record update time */ 42 | updateTime: string; 43 | /** record status */ 44 | status: EnableStatus | null; 45 | } & T; 46 | } 47 | 48 | /** 49 | * namespace Auth 50 | * 51 | * backend api module: "auth" 52 | */ 53 | namespace Auth { 54 | interface LoginToken { 55 | token: string; 56 | refreshToken: string; 57 | } 58 | 59 | interface UserInfo { 60 | userId: string; 61 | userName: string; 62 | roles: string[]; 63 | buttons: string[]; 64 | } 65 | } 66 | 67 | /** 68 | * namespace Route 69 | * 70 | * backend api module: "route" 71 | */ 72 | namespace Route { 73 | type ElegantConstRoute = import('@elegant-router/types').ElegantConstRoute; 74 | 75 | interface MenuRoute extends ElegantConstRoute { 76 | id: string; 77 | } 78 | 79 | interface UserRoute { 80 | routes: MenuRoute[]; 81 | home: import('@elegant-router/types').LastLevelRouteKey; 82 | } 83 | } 84 | 85 | /** 86 | * namespace SystemManage 87 | * 88 | * backend api module: "systemManage" 89 | */ 90 | namespace SystemManage { 91 | type CommonSearchParams = Pick; 92 | 93 | /** role */ 94 | type Role = Common.CommonRecord<{ 95 | /** role name */ 96 | roleName: string; 97 | /** role code */ 98 | roleCode: string; 99 | /** role description */ 100 | roleDesc: string; 101 | }>; 102 | 103 | /** role search params */ 104 | type RoleSearchParams = CommonType.RecordNullable< 105 | Pick & CommonSearchParams 106 | >; 107 | 108 | /** role list */ 109 | type RoleList = Common.PaginatingQueryRecord; 110 | 111 | /** all role */ 112 | type AllRole = Pick; 113 | 114 | /** 115 | * user gender 116 | * 117 | * - "1": "male" 118 | * - "2": "female" 119 | */ 120 | type UserGender = '1' | '2'; 121 | 122 | /** user */ 123 | type User = Common.CommonRecord<{ 124 | /** user name */ 125 | userName: string; 126 | /** user gender */ 127 | userGender: UserGender | null; 128 | /** user nick name */ 129 | nickName: string; 130 | /** user phone */ 131 | userPhone: string; 132 | /** user email */ 133 | userEmail: string; 134 | /** user role code collection */ 135 | userRoles: string[]; 136 | 137 | lastLoginIp: string; 138 | 139 | lastLoginTime: string; 140 | }>; 141 | 142 | /** user search params */ 143 | type UserSearchParams = CommonType.RecordNullable< 144 | Pick & 145 | CommonSearchParams 146 | >; 147 | 148 | /** user list */ 149 | type UserList = Common.PaginatingQueryRecord; 150 | 151 | /** 152 | * menu type 153 | * 154 | * - "1": directory 155 | * - "2": menu 156 | */ 157 | type MenuType = '1' | '2'; 158 | 159 | type MenuButton = { 160 | /** 161 | * button code 162 | * 163 | * it can be used to control the button permission 164 | */ 165 | code: string; 166 | /** button description */ 167 | desc: string; 168 | }; 169 | 170 | /** 171 | * icon type 172 | * 173 | * - "1": iconify icon 174 | * - "2": local icon 175 | */ 176 | type IconType = '1' | '2'; 177 | 178 | type MenuPropsOfRoute = Pick< 179 | import('vue-router').RouteMeta, 180 | | 'i18nKey' 181 | | 'keepAlive' 182 | | 'constant' 183 | | 'order' 184 | | 'href' 185 | | 'hideInMenu' 186 | | 'activeMenu' 187 | | 'multiTab' 188 | | 'fixedIndexInTab' 189 | | 'query' 190 | >; 191 | 192 | type Menu = Common.CommonRecord<{ 193 | /** parent menu id */ 194 | parentId: number; 195 | /** menu type */ 196 | menuType: MenuType; 197 | /** menu name */ 198 | menuName: string; 199 | /** route name */ 200 | routeName: string; 201 | /** route path */ 202 | routePath: string; 203 | /** component */ 204 | component?: string; 205 | /** iconify icon name or local icon name */ 206 | icon: string; 207 | /** icon type */ 208 | iconType: IconType; 209 | /** buttons */ 210 | buttons?: MenuButton[] | null; 211 | /** children menu */ 212 | children?: Menu[] | null; 213 | }> & 214 | MenuPropsOfRoute; 215 | 216 | /** menu list */ 217 | type MenuList = Common.PaginatingQueryRecord; 218 | 219 | type MenuTree = { 220 | id: number; 221 | label: string; 222 | pId: number; 223 | children?: MenuTree[]; 224 | }; 225 | } 226 | } 227 | --------------------------------------------------------------------------------