diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..fbcab77 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: http://doc.ruoyi.vip/ruoyi-vue/other/donate.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e6bfe3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml +/agileboot-admin/src/main/resources/application-dev.yml diff --git a/GoogleStyle.xml b/GoogleStyle.xml new file mode 100644 index 0000000..c2e1d68 --- /dev/null +++ b/GoogleStyle.xml @@ -0,0 +1,567 @@ + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3ab959 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +

+ logo +

+

RuoYi v3.8.2

+

基于SpringBoot+Vue前后端分离的Java快速开发框架

+

+ + + +

+ +## 平台简介 + +AgileBoot是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 + +* 前端采用Vue、Element UI。 +* 后端采用Spring Boot、Spring Security、Redis & Jwt。 +* 权限认证使用Jwt,支持多终端认证系统。 +* 支持加载动态权限菜单,多方式轻松权限控制。 +* 高效率开发,使用代码生成器可以一键生成前后端代码。 +* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。 +* 提供了单应用版本[RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast),Oracle版本[RuoYi-Vue-Oracle](https://github.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。 +* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) +* 特别鸣谢:[element](https://github.com/ElemeFE/element),[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://github.com/elunez/eladmin-web)。 +* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)   +* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)   + +## 内置功能 + +1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +3. 岗位管理:配置系统用户所属担任职务。 +4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7. 参数管理:对系统动态配置常用参数。 +8. 通知公告:系统通知公告信息发布维护。 +9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +10. 登录日志:系统登录日志记录查询包含登录异常。 +11. 在线用户:当前系统中活跃用户状态监控。 +12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 +14. 系统接口:根据业务代码自动生成相关的api接口文档。 +15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 +16. 缓存监控:对系统的缓存信息查询,命令统计等。 +17. 在线构建器:拖动表单元素生成相应的HTML代码。 +18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 + +## 在线体验 + +- admin/admin123 +- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 + +演示地址:http://vue.ruoyi.vip +文档地址:http://doc.ruoyi.vip + +## 演示图 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +## AG前后端分离交流群 + +QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/已满-264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [![加入QQ群](https://img.shields.io/badge/167385320-blue.svg)](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) 点击按钮入群。 + +TODO +plan to do encrypt request and response. reference:https://github.com/ishuibo/rsa-encrypt-body-spring-boot + + +### 如果老是出现项目中能找到包 但是编译的时候却找不到 可以运行 mvn clean install diff --git a/agileboot-admin/pom.xml b/agileboot-admin/pom.xml new file mode 100644 index 0000000..ce2e89d --- /dev/null +++ b/agileboot-admin/pom.xml @@ -0,0 +1,58 @@ + + + + agileboot + com.agileboot + 1.0.0 + + 4.0.0 + + agileboot-admin + + + web服务入口 + + + + + + + + + + + + mysql + mysql-connector-java + + + + + com.agileboot + agileboot-infrastructure + + + + + com.agileboot + agileboot-api + + + + + com.agileboot + agileboot-domain + + + + + + + diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java new file mode 100644 index 0000000..bbb7250 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java @@ -0,0 +1,123 @@ +package com.agileboot.admin.controller.common; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.StrUtil; +import com.agileboot.admin.response.UploadDTO; +import com.agileboot.common.config.AgileBootConfig; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.utils.ServletHolderUtil; +import com.agileboot.common.utils.file.FileUploadUtils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * 通用请求处理 + * + * @author valarchie + */ +@RestController +@RequestMapping("/file") +@Slf4j +public class FileController { + + + /** + * 通用下载请求 + * + * @param fileName 文件名称 + */ + @GetMapping("/download") + public ResponseEntity fileDownload(String fileName, HttpServletResponse response) { + try { + if (!FileUploadUtils.isAllowDownload(fileName)) { + throw new Exception(StrUtil.format("文件名称({})非法,不允许下载。 ", fileName)); + } + + String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); + String filePath = AgileBootConfig.getDownloadPath() + fileName; + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Disposition", String.format("attachment;filename=%s", realFileName)); + return new ResponseEntity<>(FileUtil.readBytes(filePath), headers, HttpStatus.OK); + } catch (Exception e) { + log.error("下载文件失败", e); + return null; + } + } + + /** + * 通用上传请求(单个) + */ + @PostMapping("/upload") + public ResponseDTO uploadFile(MultipartFile file) throws IOException { + if (file == null) { + throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY); + } + + // 上传文件路径 + String filePath = AgileBootConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + + String url = ServletHolderUtil.getContextUrl() + fileName; + + UploadDTO uploadDTO = UploadDTO.builder() + .url(url) + .fileName(fileName) + .newFileName(FileNameUtil.getName(fileName)) + .originalFilename(file.getOriginalFilename()).build(); + + return ResponseDTO.ok(uploadDTO); + } + + /** + * 通用上传请求(多个) + */ + @PostMapping("/uploads") + public ResponseDTO> uploadFiles(List files) throws Exception { + if (CollUtil.isEmpty(files)) { + throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY); + } + + // 上传文件路径 + String filePath = AgileBootConfig.getUploadPath(); + + List uploads = new ArrayList<>(); + + for (MultipartFile file : files) { + if (file != null) { + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = ServletHolderUtil.getContextUrl() + fileName; + UploadDTO uploadDTO = UploadDTO.builder() + .url(url) + .fileName(fileName) + .newFileName(FileNameUtil.getName(fileName)) + .originalFilename(file.getOriginalFilename()).build(); + + uploads.add(uploadDTO); + + } + } + return ResponseDTO.ok(uploads); + } + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java new file mode 100644 index 0000000..a50b2d2 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java @@ -0,0 +1,120 @@ +package com.agileboot.admin.controller.common; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.agileboot.admin.request.LoginDTO; +import com.agileboot.admin.request.RegisterDTO; +import com.agileboot.admin.response.UserPermissionDTO; +import com.agileboot.common.config.AgileBootConfig; +import com.agileboot.common.constant.Constants.Token; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.error.ErrorCode.Business; +import com.agileboot.domain.system.menu.MenuDomainService; +import com.agileboot.domain.system.menu.RouterVo; +import com.agileboot.domain.system.user.UserDTO; +import com.agileboot.infrastructure.cache.map.MapCache; +import com.agileboot.infrastructure.web.domain.login.CaptchaDTO; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.service.LoginService; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import java.util.List; +import java.util.Map; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 首页 + * + * @author valarchie + */ +@RestController +public class LoginController { + + private final LoginService loginService; + + private final MenuDomainService menuDomainService; + /** + * 系统基础配置 + */ + private final AgileBootConfig agileBootConfig; + + public LoginController(LoginService loginService, + MenuDomainService menuDomainService, AgileBootConfig agileBootConfig) { + this.loginService = loginService; + this.menuDomainService = menuDomainService; + this.agileBootConfig = agileBootConfig; + } + + /** + * 访问首页,提示语 + */ + @RequestMapping("/") + public String index() { + return StrUtil.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", + agileBootConfig.getName(), agileBootConfig.getVersion()); + } + + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public ResponseDTO getCaptchaImg() { + CaptchaDTO captchaImg = loginService.getCaptchaImg(); + return ResponseDTO.ok(captchaImg); + } + + /** + * 登录方法 + * + * @param loginDTO 登录信息 + * @return 结果 + */ + @PostMapping("/login") + public ResponseDTO login(@RequestBody LoginDTO loginDTO) { + // 生成令牌 + String token = loginService.login(loginDTO.getUsername(), loginDTO.getPassword(), loginDTO.getCode(), + loginDTO.getUuid()); + + return ResponseDTO.ok(MapUtil.of(Token.TOKEN_FIELD, token)); + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @GetMapping("/getLoginUserInfo") + public ResponseDTO getLoginUserInfo() { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + + UserPermissionDTO permissionDTO = new UserPermissionDTO(); + permissionDTO.setUser(new UserDTO(loginUser.getEntity())); + permissionDTO.setRoleKey(loginUser.getRoleKey()); + permissionDTO.setPermissions(loginUser.getMenuPermissions()); + permissionDTO.setDictTypes(MapCache.dictionaryCache()); + + return ResponseDTO.ok(permissionDTO); + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @GetMapping("/getRouters") + public ResponseDTO> getRouters() { + Long userId = AuthenticationUtils.getUserId(); + List routerTree = menuDomainService.getRouterTree(userId); + return ResponseDTO.ok(routerTree); + } + + + @PostMapping("/register") + public ResponseDTO register(@RequestBody RegisterDTO user) { + return ResponseDTO.fail(Business.UNSUPPORTED_OPERATION); + } + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/MonitorController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/MonitorController.java new file mode 100644 index 0000000..5703e23 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/MonitorController.java @@ -0,0 +1,77 @@ +package com.agileboot.admin.controller.monitor; + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.domain.system.monitor.MonitorDomainService; +import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.cache.redis.RedisCacheService; +import com.agileboot.infrastructure.web.domain.OnlineUser; +import com.agileboot.infrastructure.web.domain.server.ServerInfo; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 缓存监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor") +public class MonitorController extends BaseController { + + @Autowired + private MonitorDomainService monitorDomainService; + + @Autowired + private RedisCacheService redisCacheService; + + @PreAuthorize("@ss.hasPerm('monitor:cache:list')") + @GetMapping("/cacheInfo") + public ResponseDTO getRedisCacheInfo() { + RedisCacheInfoDTO redisCacheInfo = monitorDomainService.getRedisCacheInfo(); + return ResponseDTO.ok(redisCacheInfo); + } + + + @PreAuthorize("@ss.hasPerm('monitor:server:list')") + @GetMapping("/serverInfo") + public ResponseDTO getServerInfo() { + ServerInfo serverInfo = monitorDomainService.getServerInfo(); + return ResponseDTO.ok(serverInfo); + } + + /** + * 获取在线用户列表 + * @param ipaddr + * @param userName + * @return + */ + @PreAuthorize("@ss.hasPerm('monitor:online:list')") + @GetMapping("/onlineUser/list") + public ResponseDTO list(String ipaddr, String userName) { + List onlineUserList = monitorDomainService.getOnlineUserList(userName, ipaddr); + return ResponseDTO.ok(new PageDTO(onlineUserList)); + } + + /** + * 强退用户 + */ + @PreAuthorize("@ss.hasPerm('monitor:online:forceLogout')") + @AccessLog(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/onlineUser/{tokenId}") + public ResponseDTO forceLogout(@PathVariable String tokenId) { + redisCacheService.loginUserCache.delete(tokenId); + return ResponseDTO.ok(); + } + + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java new file mode 100644 index 0000000..91b81de --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java @@ -0,0 +1,98 @@ +package com.agileboot.admin.controller.system; + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.domain.system.config.ConfigDTO; +import com.agileboot.domain.system.config.ConfigDomainService; +import com.agileboot.domain.system.config.ConfigQuery; +import com.agileboot.domain.system.config.ConfigUpdateCommand; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.cache.guava.GuavaCacheService; +import com.agileboot.infrastructure.cache.map.MapCache; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.enums.BusinessType; +import com.agileboot.orm.result.DictionaryData; +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 参数配置 信息操作处理 + * @author valarchie + */ +@RestController +@RequestMapping("/system/config") +@Validated +public class SysConfigController extends BaseController { + + @Autowired + private ConfigDomainService configDomainService; + + @Autowired + private GuavaCacheService guavaCacheService; + + /** + * 获取参数配置列表 + */ + @PreAuthorize("@ss.hasPerm('system:config:list')") + @GetMapping("/list") + public ResponseDTO list(ConfigQuery query) { + PageDTO page = configDomainService.getConfigList(query); + return ResponseDTO.ok(page); + } + + /** + * 根据字典类型查询字典数据信息 + * 换成用Enum + */ + @GetMapping(value = "/dict/{dictType}") + public ResponseDTO> dictType(@PathVariable String dictType) { + List dictionaryData = MapCache.dictionaryCache().get(dictType); + return ResponseDTO.ok(dictionaryData); + } + + + /** + * 根据参数编号获取详细信息 + */ + @PreAuthorize("@ss.hasPerm('system:config:query')") + @GetMapping(value = "/{configId}") + public ResponseDTO getInfo(@NotNull @Positive @PathVariable Long configId) { + ConfigDTO config = configDomainService.getConfigInfo(configId); + return ResponseDTO.ok(config); + } + + + /** + * 修改参数配置 + */ + @PreAuthorize("@ss.hasPerm('system:config:edit')") + @AccessLog(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public ResponseDTO edit(@RequestBody ConfigUpdateCommand config) { + configDomainService.updateConfig(config, AuthenticationUtils.getLoginUser()); + return ResponseDTO.ok(); + } + + /** + * 刷新参数缓存 + */ + @PreAuthorize("@ss.hasPerm('system:config:remove')") + @AccessLog(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public ResponseDTO refreshCache() { + guavaCacheService.configCache.invalidateAll(); + return ResponseDTO.ok(); + } +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysDeptController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysDeptController.java new file mode 100644 index 0000000..8224e8b --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysDeptController.java @@ -0,0 +1,126 @@ +package com.agileboot.admin.controller.system; + +import cn.hutool.core.lang.tree.Tree; +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.domain.system.TreeSelectedDTO; +import com.agileboot.domain.system.dept.AddDeptCommand; +import com.agileboot.domain.system.dept.DeptDTO; +import com.agileboot.domain.system.dept.DeptDomainService; +import com.agileboot.domain.system.dept.DeptQuery; +import com.agileboot.domain.system.dept.UpdateDeptCommand; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import javax.validation.constraints.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 部门信息 + * + * @author valarchie + */ +@RestController +@RequestMapping("/system/dept") +@Validated +public class SysDeptController extends BaseController { + + @Autowired + private DeptDomainService deptDomainService; + + /** + * 获取部门列表 + */ + @PreAuthorize("@ss.hasPerm('system:dept:list')") + @GetMapping("/list") + public ResponseDTO list(DeptQuery query) { + List deptList = deptDomainService.getDeptList(query); + return ResponseDTO.ok(deptList); + } + + /** + * 查询部门列表(排除当前部门,比如在修改部门的上级部门的时候,需要排除自身当前的部门,因为上级部门不能选自己) + */ + @PreAuthorize("@ss.hasPerm('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public ResponseDTO excludeCurrentDeptItself(@PathVariable(value = "deptId", required = false) Long deptId) { + DeptQuery query = new DeptQuery(); + query.setDeptId(deptId); + query.setExcludeCurrentDept(true); + + List deptList = deptDomainService.getDeptList(query); + return ResponseDTO.ok(deptList); + } + + /** + * 根据部门编号获取详细信息 + */ + @PreAuthorize("@ss.hasPerm('system:dept:query')") + @GetMapping(value = "/{deptId}") + public ResponseDTO getInfo(@PathVariable Long deptId) { + DeptDTO dept = deptDomainService.getDeptInfo(deptId); + return ResponseDTO.ok(dept); + } + + /** + * 获取部门下拉树列表 + */ + @GetMapping("/dropdownList") + public ResponseDTO dropdownList() { + List> deptTree = deptDomainService.getDeptTree(); + return ResponseDTO.ok(deptTree); + } + + /** + * 加载对应角色部门列表树 + */ + @GetMapping(value = "/dropdownList/role/{roleId}") + public ResponseDTO dropdownListForRole(@PathVariable("roleId") Long roleId) { + TreeSelectedDTO deptTreeForRole = deptDomainService.getDeptTreeForRole(roleId); + return ResponseDTO.ok(deptTreeForRole); + } + + /** + * 新增部门 + */ + @PreAuthorize("@ss.hasPerm('system:dept:add')") + @AccessLog(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public ResponseDTO add(@RequestBody AddDeptCommand addCommand) { + deptDomainService.addDept(addCommand, AuthenticationUtils.getLoginUser()); + return ResponseDTO.ok(); + } + + /** + * 修改部门 + */ + @PreAuthorize("@ss.hasPerm('system:dept:edit') AND @ss.checkDataScopeWithDeptId(#updateCommand.deptId)") + @AccessLog(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public ResponseDTO edit(@RequestBody UpdateDeptCommand updateCommand) { + deptDomainService.updateDept(updateCommand, AuthenticationUtils.getLoginUser()); + return ResponseDTO.ok(); + } + + /** + * 删除部门 + */ + @PreAuthorize("@ss.hasPerm('system:dept:remove') AND @ss.checkDataScopeWithDeptId(#deptId)") + @AccessLog(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public ResponseDTO remove(@PathVariable @NotNull Long deptId) { + deptDomainService.removeDept(deptId); + return ResponseDTO.ok(); + } +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLoginInfoController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLoginInfoController.java new file mode 100644 index 0000000..65961cd --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLoginInfoController.java @@ -0,0 +1,71 @@ +package com.agileboot.admin.controller.system; + + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.utils.poi.CustomExcelUtil; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.domain.system.loginInfo.LoginInfoDTO; +import com.agileboot.domain.system.loginInfo.LoginInfoDomainService; +import com.agileboot.domain.system.loginInfo.LoginInfoQuery; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 系统访问记录 + * + * @author valarchie + */ +@RestController +@RequestMapping("/loginInfo") +@Validated +public class SysLoginInfoController extends BaseController { + + @Autowired + private LoginInfoDomainService loginInfoDomainService; + + @PreAuthorize("@ss.hasPerm('monitor:logininfor:list')") + @GetMapping("/list") + public ResponseDTO list(LoginInfoQuery query) { + PageDTO pageDTO = loginInfoDomainService.getLoginInfoList(query); + return ResponseDTO.ok(pageDTO); + } + + @AccessLog(title = "登录日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPerm('monitor:logininfor:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, LoginInfoQuery query) { + PageDTO pageDTO = loginInfoDomainService.getLoginInfoList(query); + CustomExcelUtil.writeToResponse(pageDTO.getRows(), LoginInfoDTO.class, response); + } + + @PreAuthorize("@ss.hasPerm('monitor:logininfor:remove')") + @AccessLog(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public ResponseDTO remove(@PathVariable @NotNull @NotEmpty List infoIds) { + loginInfoDomainService.deleteLoginInfo(new BulkOperationCommand<>(infoIds)); + return ResponseDTO.ok(); + } + + @PreAuthorize("@ss.hasPerm('monitor:logininfor:remove')") + @AccessLog(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public ResponseDTO clean() { + return ResponseDTO.fail(ErrorCode.Business.UNSUPPORTED_OPERATION); + } +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java new file mode 100644 index 0000000..d101179 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java @@ -0,0 +1,120 @@ +package com.agileboot.admin.controller.system; + +import cn.hutool.core.lang.tree.Tree; +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.domain.system.TreeSelectedDTO; +import com.agileboot.domain.system.menu.AddMenuCommand; +import com.agileboot.domain.system.menu.MenuDTO; +import com.agileboot.domain.system.menu.MenuDomainService; +import com.agileboot.domain.system.menu.MenuQuery; +import com.agileboot.domain.system.menu.UpdateMenuCommand; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.PositiveOrZero; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 菜单信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/menu") +@Validated +public class SysMenuController extends BaseController { + + @Autowired + MenuDomainService menuDomainService; + + /** + * 获取菜单列表 + */ + @PreAuthorize("@ss.hasPerm('system:menu:list')") + @GetMapping("/list") + public ResponseDTO list(MenuQuery query) { + List menuList = menuDomainService.getMenuList(query); + return ResponseDTO.ok(menuList); + } + + /** + * 根据菜单编号获取详细信息 + */ + @PreAuthorize("@ss.hasPerm('system:menu:query')") + @GetMapping(value = "/{menuId}") + public ResponseDTO getInfo(@PathVariable @NotNull @PositiveOrZero Long menuId) { + MenuDTO menu = menuDomainService.getMenuInfo(menuId); + return ResponseDTO.ok(menu); + } + + /** + * 获取菜单下拉树列表 + */ + @GetMapping("/dropdownList") + public ResponseDTO dropdownList() { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + List> dropdownList = menuDomainService.getDropdownList(loginUser); + return ResponseDTO.ok(dropdownList); + } + + /** + * 加载对应角色菜单列表树 + */ + @GetMapping(value = "/roleMenuTreeSelect/{roleId}") + public ResponseDTO roleMenuTreeSelect(@PathVariable("roleId") Long roleId) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + TreeSelectedDTO roleDropdownList = menuDomainService.getRoleDropdownList(loginUser, roleId); + return ResponseDTO.ok(roleDropdownList); + } + + /** + * 新增菜单 + */ + @PreAuthorize("@ss.hasPerm('system:menu:add')") + @AccessLog(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public ResponseDTO add(@RequestBody AddMenuCommand addCommand) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + menuDomainService.addMenu(addCommand, loginUser); + return ResponseDTO.ok(); + } + + /** + * 修改菜单 + */ + @PreAuthorize("@ss.hasPerm('system:menu:edit')") + @AccessLog(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public ResponseDTO edit(@RequestBody UpdateMenuCommand updateCommand) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + menuDomainService.updateMenu(updateCommand, loginUser); + return ResponseDTO.ok(); + } + + /** + * 删除菜单 + */ + @PreAuthorize("@ss.hasPerm('system:menu:remove')") + @AccessLog(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public ResponseDTO remove(@PathVariable("menuId") Long menuId) { + menuDomainService.remove(menuId); + return ResponseDTO.ok(); + } + + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java new file mode 100644 index 0000000..a123cfd --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java @@ -0,0 +1,97 @@ +package com.agileboot.admin.controller.system; + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.domain.system.notice.NoticeAddCommand; +import com.agileboot.domain.system.notice.NoticeDTO; +import com.agileboot.domain.system.notice.NoticeDomainService; +import com.agileboot.domain.system.notice.NoticeQuery; +import com.agileboot.domain.system.notice.NoticeUpdateCommand; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 公告 信息操作处理 + * + * @author valarchie + */ +@RestController +@RequestMapping("/system/notice") +@Validated +public class SysNoticeController extends BaseController { + + @Autowired + private NoticeDomainService noticeDomainService; + + /** + * 获取通知公告列表 + */ + @PreAuthorize("@ss.hasPerm('system:notice:list')") + @GetMapping("/list") + public ResponseDTO list(NoticeQuery query) { + PageDTO pageDTO = noticeDomainService.getNoticeList(query); + return ResponseDTO.ok(pageDTO); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @PreAuthorize("@ss.hasPerm('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public ResponseDTO getInfo(@PathVariable @NotNull @Positive Long noticeId) { + return ResponseDTO.ok(noticeDomainService.getNoticeInfo(noticeId)); + } + + /** + * 新增通知公告 + */ + @PreAuthorize("@ss.hasPerm('system:notice:add')") + @AccessLog(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public ResponseDTO add(@RequestBody NoticeAddCommand addCommand) { + noticeDomainService.addNotice(addCommand, AuthenticationUtils.getLoginUser()); + return ResponseDTO.ok(); + } + + /** + * 修改通知公告 + */ + @PreAuthorize("@ss.hasPerm('system:notice:edit')") + @AccessLog(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public ResponseDTO edit(@RequestBody NoticeUpdateCommand updateCommand) { + noticeDomainService.updateNotice(updateCommand, AuthenticationUtils.getLoginUser()); + return ResponseDTO.ok(); + } + + /** + * 删除通知公告 + */ + @PreAuthorize("@ss.hasPerm('system:notice:remove')") + @AccessLog(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public ResponseDTO remove(@PathVariable List noticeIds) { + noticeDomainService.deleteNotice(new BulkOperationCommand<>(noticeIds)); + return ResponseDTO.ok(); + } + + + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysOperationLogController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysOperationLogController.java new file mode 100644 index 0000000..dd0b5d8 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysOperationLogController.java @@ -0,0 +1,67 @@ +package com.agileboot.admin.controller.system; + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.utils.poi.CustomExcelUtil; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.domain.system.operationLog.OperationLogDTO; +import com.agileboot.domain.system.operationLog.OperationLogDomainService; +import com.agileboot.domain.system.operationLog.OperationLogQuery; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 操作日志记录 + * + * @author valarchie + */ +@RestController +@RequestMapping("/operationLog") +public class SysOperationLogController extends BaseController { + + @Autowired + private OperationLogDomainService operationLogDomainService; + + @PreAuthorize("@ss.hasPerm('monitor:operlog:list')") + @GetMapping("/list") + public ResponseDTO list(OperationLogQuery query, HttpServletRequest request) { + PageDTO pageDTO = operationLogDomainService.getOperationLogList(query); + return ResponseDTO.ok(pageDTO); + } + + @AccessLog(title = "操作日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPerm('monitor:operlog:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, OperationLogQuery query) { + PageDTO pageDTO = operationLogDomainService.getOperationLogList(query); + CustomExcelUtil.writeToResponse(pageDTO.getRows(), OperationLogDTO.class, response); + } + + @AccessLog(title = "操作日志", businessType = BusinessType.DELETE) + @PreAuthorize("@ss.hasPerm('monitor:operlog:remove')") + @DeleteMapping("/{operationIds}") + public ResponseDTO remove(@PathVariable List operationIds) { + operationLogDomainService.deleteOperationLog(new BulkOperationCommand<>(operationIds)); + return ResponseDTO.ok(); + } + + @AccessLog(title = "操作日志", businessType = BusinessType.CLEAN) + @PreAuthorize("@ss.hasPerm('monitor:operlog:remove')") + @DeleteMapping("/clean") + public ResponseDTO clean() { + return ResponseDTO.fail(ErrorCode.Business.UNSUPPORTED_OPERATION); + } +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysPostController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysPostController.java new file mode 100644 index 0000000..16af203 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysPostController.java @@ -0,0 +1,104 @@ +package com.agileboot.admin.controller.system; + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.utils.poi.CustomExcelUtil; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.domain.system.post.AddPostCommand; +import com.agileboot.domain.system.post.PostDTO; +import com.agileboot.domain.system.post.PostDomainService; +import com.agileboot.domain.system.post.PostQuery; +import com.agileboot.domain.system.post.UpdatePostCommand; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 岗位信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/post") +@Validated +public class SysPostController extends BaseController { + + @Autowired + private PostDomainService postDomainService; + + /** + * 获取岗位列表 + */ + @PreAuthorize("@ss.hasPerm('system:post:list')") + @GetMapping("/list") + public ResponseDTO list(PostQuery query) { + PageDTO pageDTO = postDomainService.getPostList(query); + return ResponseDTO.ok(pageDTO); + } + + @AccessLog(title = "岗位管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPerm('system:post:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, PostQuery query) { + PageDTO pageDTO = postDomainService.getPostList(query); + CustomExcelUtil.writeToResponse(pageDTO.getRows(), PostDTO.class, response); + } + + /** + * 根据岗位编号获取详细信息 + */ + @PreAuthorize("@ss.hasPerm('system:post:query')") + @GetMapping(value = "/{postId}") + public ResponseDTO getInfo(@PathVariable Long postId) { + PostDTO post = postDomainService.getPostInfo(postId); + return ResponseDTO.ok(post); + } + + /** + * 新增岗位 + */ + @PreAuthorize("@ss.hasPerm('system:post:add')") + @AccessLog(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public ResponseDTO add(@RequestBody AddPostCommand addCommand) { + postDomainService.addPost(addCommand, AuthenticationUtils.getLoginUser()); + return ResponseDTO.ok(); + } + + /** + * 修改岗位 + */ + @PreAuthorize("@ss.hasPerm('system:post:edit')") + @AccessLog(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public ResponseDTO edit(@RequestBody UpdatePostCommand updateCommand) { + postDomainService.updatePost(updateCommand, AuthenticationUtils.getLoginUser()); + return ResponseDTO.ok(); + } + + /** + * 删除岗位 + */ + @PreAuthorize("@ss.hasPerm('system:post:remove')") + @AccessLog(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public ResponseDTO remove(@PathVariable List postIds) { + postDomainService.deletePost(new BulkOperationCommand<>(postIds)); + return ResponseDTO.ok(); + } + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java new file mode 100644 index 0000000..9a871f9 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java @@ -0,0 +1,91 @@ +package com.agileboot.admin.controller.system; + +import com.agileboot.common.config.AgileBootConfig; +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.utils.file.FileUploadUtils; +import com.agileboot.domain.common.UploadFileDTO; +import com.agileboot.domain.system.user.UserDomainService; +import com.agileboot.domain.system.user.UserProfileDTO; +import com.agileboot.domain.system.user.command.UpdateProfileCommand; +import com.agileboot.domain.system.user.command.UpdateUserAvatarCommand; +import com.agileboot.domain.system.user.command.UpdateUserPasswordCommand; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.enums.BusinessType; +import java.io.IOException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * 个人信息 业务处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController { + + @Autowired + private UserDomainService userDomainService; + + /** + * 个人信息 + */ + @GetMapping + public ResponseDTO profile() { + LoginUser user = AuthenticationUtils.getLoginUser(); + UserProfileDTO userProfile = userDomainService.getUserProfile(user.getUserId()); + return ResponseDTO.ok(userProfile); + } + + /** + * 修改用户 + */ + @AccessLog(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public ResponseDTO updateProfile(@RequestBody UpdateProfileCommand command) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + command.setUserId(loginUser.getUserId()); + userDomainService.updateUserProfile(command, loginUser); + return ResponseDTO.ok(); + } + + /** + * 重置密码 + */ + @AccessLog(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/password") + public ResponseDTO updatePassword(@RequestBody UpdateUserPasswordCommand command) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + command.setUserId(loginUser.getUserId()); + userDomainService.updateUserPassword(loginUser, command); + return ResponseDTO.ok(); + } + + /** + * 头像上传 + */ + @AccessLog(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public ResponseDTO avatar(@RequestParam("avatarfile") MultipartFile file) throws IOException { + if (file.isEmpty()) { + throw new ApiException(ErrorCode.Business.USER_UPLOAD_FILE_FAILED); + } + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + String avatar = FileUploadUtils.upload(AgileBootConfig.getAvatarPath(), file); + + userDomainService.updateUserAvatar(loginUser, new UpdateUserAvatarCommand(loginUser.getUserId(), avatar)); + return ResponseDTO.ok(new UploadFileDTO(avatar)); + } +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java new file mode 100644 index 0000000..ee82db2 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java @@ -0,0 +1,192 @@ +package com.agileboot.admin.controller.system; + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.utils.poi.CustomExcelUtil; +import com.agileboot.domain.system.role.AddRoleCommand; +import com.agileboot.domain.system.role.AllocatedRoleQuery; +import com.agileboot.domain.system.role.RoleDTO; +import com.agileboot.domain.system.role.RoleDomainService; +import com.agileboot.domain.system.role.RoleQuery; +import com.agileboot.domain.system.role.UnallocatedRoleQuery; +import com.agileboot.domain.system.role.UpdateDataScopeCommand; +import com.agileboot.domain.system.role.UpdateRoleCommand; +import com.agileboot.domain.system.role.UpdateStatusCommand; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 角色信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/role") +@Validated +public class SysRoleController extends BaseController { + + @Autowired + private RoleDomainService roleDomainService; + + @PreAuthorize("@ss.hasPerm('system:role:list')") + @GetMapping("/list") + public ResponseDTO list(RoleQuery query) { + PageDTO pageDTO = roleDomainService.getRoleList(query); + return ResponseDTO.ok(pageDTO); + } + + @AccessLog(title = "角色管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPerm('system:role:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, RoleQuery query) { + PageDTO pageDTO = roleDomainService.getRoleList(query); + CustomExcelUtil.writeToResponse(pageDTO.getRows(), RoleDTO.class, response); + } + + /** + * 根据角色编号获取详细信息 + */ + @PreAuthorize("@ss.hasPerm('system:role:query')") + @GetMapping(value = "/{roleId}") + public ResponseDTO getInfo(@PathVariable @NotNull Long roleId) { + RoleDTO roleInfo = roleDomainService.getRoleInfo(roleId); + return ResponseDTO.ok(roleInfo); + } + + /** + * 新增角色 + */ + @PreAuthorize("@ss.hasPerm('system:role:add')") + @AccessLog(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping + public ResponseDTO add(@RequestBody AddRoleCommand addCommand) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + roleDomainService.addRole(addCommand, loginUser); + return ResponseDTO.ok(); + } + + /** + * 新增角色 + */ + @PreAuthorize("@ss.hasPerm('system:role:remove')") + @AccessLog(title = "角色管理", businessType = BusinessType.INSERT) + @DeleteMapping(value = "/{roleId}") + public ResponseDTO remove(@PathVariable("roleId")List roleIds) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + roleDomainService.deleteRoleByBulk(roleIds, loginUser); + return ResponseDTO.ok(); + } + + /** + * 修改保存角色 + */ + @PreAuthorize("@ss.hasPerm('system:role:edit')") + @AccessLog(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping + public ResponseDTO edit(@Validated @RequestBody UpdateRoleCommand updateCommand) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + roleDomainService.updateRole(updateCommand, loginUser); + return ResponseDTO.ok(); + } + + /** + * 修改保存数据权限 + */ + @PreAuthorize("@ss.hasPerm('system:role:edit')") + @AccessLog(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/{roleId}/dataScope") + public ResponseDTO dataScope(@PathVariable("roleId")Long roleId, @RequestBody UpdateDataScopeCommand command) { + command.setRoleId(roleId); + + roleDomainService.updateDataScope(command); + return ResponseDTO.ok(); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPerm('system:role:edit')") + @AccessLog(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/{roleId}/status") + public ResponseDTO changeStatus(@PathVariable("roleId")Long roleId, @RequestBody UpdateStatusCommand command) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + command.setRoleId(roleId); + + roleDomainService.updateStatus(command, loginUser); + return ResponseDTO.ok(); + } + + + /** + * 查询已分配用户角色列表 + */ + @PreAuthorize("@ss.hasPerm('system:role:list')") + @GetMapping("/{roleId}/allocated/list") + public ResponseDTO allocatedUserList(@PathVariable("roleId")Long roleId, AllocatedRoleQuery query) { + query.setRoleId(roleId); + PageDTO page = roleDomainService.getAllocatedUserList(query); + return ResponseDTO.ok(page); + } + + /** + * 查询未分配用户角色列表 + */ + @PreAuthorize("@ss.hasPerm('system:role:list')") + @GetMapping("/{roleId}/unallocated/list") + public ResponseDTO unallocatedUserList(@PathVariable("roleId")Long roleId, UnallocatedRoleQuery query) { + query.setRoleId(roleId); + PageDTO page = roleDomainService.getUnallocatedUserList(query); + return ResponseDTO.ok(page); + } + + /** + * 取消授权用户 + */ + @PreAuthorize("@ss.hasPerm('system:role:edit')") + @AccessLog(title = "角色管理", businessType = BusinessType.GRANT) + @DeleteMapping("/{roleId}/user/grant") + public ResponseDTO deleteRoleOfUser(@PathVariable("roleId")Long roleId, @RequestBody Long userId) { + roleDomainService.deleteRoleOfUser(userId); + return ResponseDTO.ok(); + } + + /** + * 批量取消授权用户 + */ + @PreAuthorize("@ss.hasPerm('system:role:edit')") + @AccessLog(title = "角色管理", businessType = BusinessType.GRANT) + @DeleteMapping("/users/{userIds}/grant/bulk") + public ResponseDTO deleteRoleOfUserByBulk(@PathVariable("userIds") List userIds) { + roleDomainService.deleteRoleOfUserByBulk(userIds); + return ResponseDTO.ok(); + } + + /** + * 批量选择用户授权 + */ + @PreAuthorize("@ss.hasPerm('system:role:edit')") + @AccessLog(title = "角色管理", businessType = BusinessType.GRANT) + @PostMapping("/{roleId}/users/{userIds}/grant/bulk") + public ResponseDTO addRoleForUserByBulk(@PathVariable("roleId") Long roleId, + @PathVariable("userIds") List userIds) { + roleDomainService.addRoleOfUserByBulk(roleId, userIds); + return ResponseDTO.ok(); + } +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java new file mode 100644 index 0000000..7760ea9 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java @@ -0,0 +1,171 @@ +package com.agileboot.admin.controller.system; + +import cn.hutool.core.collection.ListUtil; +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.utils.poi.CustomExcelUtil; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.domain.system.loginInfo.SearchUserQuery; +import com.agileboot.domain.system.user.UserDTO; +import com.agileboot.domain.system.user.UserDetailDTO; +import com.agileboot.domain.system.user.UserDomainService; +import com.agileboot.domain.system.user.UserInfoDTO; +import com.agileboot.domain.system.user.command.AddUserCommand; +import com.agileboot.domain.system.user.command.ChangeStatusCommand; +import com.agileboot.domain.system.user.command.ResetPasswordCommand; +import com.agileboot.domain.system.user.command.UpdateUserCommand; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.enums.BusinessType; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * 用户信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController { + + @Autowired + private UserDomainService userDomainService; + + /** + * 获取用户列表 + */ + @PreAuthorize("@ss.hasPerm('system:user:list') AND @ss.checkDataScopeWithDeptId(#query.deptId)") + @GetMapping("/list") + public ResponseDTO list(SearchUserQuery query) { + PageDTO page = userDomainService.getUserList(query); + return ResponseDTO.ok(page); + } + + @AccessLog(title = "用户管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPerm('system:user:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SearchUserQuery query) { + PageDTO userList = userDomainService.getUserList(query); + CustomExcelUtil.writeToResponse(userList.getRows(), UserDTO.class, response); + } + + @AccessLog(title = "用户管理", businessType = BusinessType.IMPORT) + @PreAuthorize("@ss.hasPerm('system:user:import')") + @PostMapping("/importData") + public ResponseDTO importData(MultipartFile file) { + List commands = CustomExcelUtil.readFromResponse(AddUserCommand.class, file); + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + + for (Object command : commands) { + AddUserCommand addUserCommand = (AddUserCommand) command; + userDomainService.addUser(loginUser, addUserCommand); + } + return ResponseDTO.ok(); + } + + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + CustomExcelUtil.writeToResponse(ListUtil.toList(new AddUserCommand()), AddUserCommand.class, response); + } + + /** + * 根据用户编号获取详细信息 + */ + @PreAuthorize("@ss.hasPerm('system:user:query')") + @GetMapping(value = {"/", "/{userId}"}) + public ResponseDTO getUserDetailInfo(@PathVariable(value = "userId", required = false) Long userId) { + UserDetailDTO userDetailInfo = userDomainService.getUserDetailInfo(userId); + return ResponseDTO.ok(userDetailInfo); + } + + /** + * 新增用户 + */ + @PreAuthorize("@ss.hasPerm('system:user:add') AND @ss.checkDataScopeWithDeptId(#command.deptId)") + @AccessLog(title = "用户管理", businessType = BusinessType.INSERT) + @PostMapping + public ResponseDTO add(@Validated @RequestBody AddUserCommand command) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + userDomainService.addUser(loginUser, command); + return ResponseDTO.ok(); + } + + /** + * 修改用户 + */ + @PreAuthorize("@ss.hasPerm('system:user:edit') AND @ss.checkDataScopeWithUserId(#command.userId)") + @AccessLog(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping + public ResponseDTO edit(@Validated @RequestBody UpdateUserCommand command) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + userDomainService.updateUser(loginUser, command); + return ResponseDTO.ok(); + } + + /** + * 删除用户 + */ + @PreAuthorize("@ss.hasPerm('system:user:remove') AND @ss.checkDataScopeWithUserIds(#userIds)") + @AccessLog(title = "用户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{userIds}") + public ResponseDTO remove(@PathVariable List userIds) { + BulkOperationCommand bulkDeleteCommand = new BulkOperationCommand(userIds); + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + userDomainService.deleteUsers(loginUser, bulkDeleteCommand); + return ResponseDTO.ok(); + } + + /** + * 重置密码 + */ + @PreAuthorize("@ss.hasPerm('system:user:resetPwd') AND @ss.checkDataScopeWithUserId(#userId)") + @AccessLog(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/{userId}/password/reset") + public ResponseDTO resetPassword(@PathVariable Long userId, @RequestBody ResetPasswordCommand command) { + command.setUserId(userId); + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + userDomainService.resetUserPassword(loginUser, command); + return ResponseDTO.ok(); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPerm('system:user:edit') AND @ss.checkDataScopeWithUserId(#command.userId)") + @AccessLog(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/{userId}/status") + public ResponseDTO changeStatus(@PathVariable Long userId, @RequestBody ChangeStatusCommand command) { + command.setUserId(userId); + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + userDomainService.changeUserStatus(loginUser, command); + return ResponseDTO.ok(); + } + + /** + * 根据用户编号获取授权角色 + */ + @PreAuthorize("@ss.hasPerm('system:user:query')") + @GetMapping("/{userId}/role") + public ResponseDTO getRoleOfUser(@PathVariable("userId") Long userId) { + UserInfoDTO userWithRole = userDomainService.getUserWithRole(userId); + return ResponseDTO.ok(userWithRole); + } + + + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerController.java new file mode 100644 index 0000000..a085d99 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerController.java @@ -0,0 +1,23 @@ +package com.agileboot.admin.controller.tool; + +import com.agileboot.common.core.base.BaseController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * swagger 接口 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/tool/swagger") +public class SwaggerController extends BaseController { + + @PreAuthorize("@ss.hasPerm('tool:swagger:view')") + @GetMapping() + public String index() { + return redirect("/swagger-ui.html"); + } +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerTemplateController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerTemplateController.java new file mode 100644 index 0000000..703b1ce --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerTemplateController.java @@ -0,0 +1,126 @@ +package com.agileboot.admin.controller.tool; + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.error.ErrorCode; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController + @RequestMapping("/test/user") +/** + * swagger 用户测试方法 + * + * @author valarchie + */ +@Api("用户信息管理") +public class SwaggerTemplateController extends BaseController { + + private final static Map USER_ENTITY_MAP = new LinkedHashMap<>(); + + static { + USER_ENTITY_MAP.put(1, new UserEntity(1, "admin", "admin123", "15888888888")); + USER_ENTITY_MAP.put(2, new UserEntity(2, "agileBoot", "admin123", "15666666666")); + } + + @ApiOperation("获取用户列表") + @GetMapping("/list") + public ResponseDTO> userList() { + List userList = new ArrayList<>(USER_ENTITY_MAP.values()); + return ResponseDTO.ok(userList); + } + + @ApiOperation("获取用户详细") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", + dataTypeClass = Integer.class) + @GetMapping("/{userId}") + public ResponseDTO getUser(@PathVariable Integer userId) { + if (!USER_ENTITY_MAP.isEmpty() && USER_ENTITY_MAP.containsKey(userId)) { + return ResponseDTO.ok(USER_ENTITY_MAP.get(userId)); + } else { + return ResponseDTO.fail(ErrorCode.Business.USER_NON_EXIST); + } + } + + @ApiOperation("新增用户") + @ApiImplicitParams({ + @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class), + @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class) + }) + @PostMapping("/save") + public ResponseDTO save(UserEntity user) { + if (user == null || user.getUserId() == null) { + return ResponseDTO.fail("用户ID不能为空"); + } + USER_ENTITY_MAP.put(user.getUserId(), user); + return ResponseDTO.ok(); + } + + @ApiOperation("更新用户") + @PutMapping("/update") + public ResponseDTO update(@RequestBody UserEntity user) { + if (user == null || user.getUserId() == null) { + return ResponseDTO.fail("用户ID不能为空"); + } + if (USER_ENTITY_MAP.isEmpty() || !USER_ENTITY_MAP.containsKey(user.getUserId())) { + return ResponseDTO.fail("用户不存在"); + } + USER_ENTITY_MAP.remove(user.getUserId()); + USER_ENTITY_MAP.put(user.getUserId(), user); + return ResponseDTO.ok(); + } + + @ApiOperation("删除用户信息") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", + dataTypeClass = Integer.class) + @DeleteMapping("/{userId}") + public ResponseDTO delete(@PathVariable Integer userId) { + if (!USER_ENTITY_MAP.isEmpty() && USER_ENTITY_MAP.containsKey(userId)) { + USER_ENTITY_MAP.remove(userId); + return ResponseDTO.ok(); + } else { + return ResponseDTO.fail("用户不存在"); + } + } + + @ApiModel(value = "UserEntity", description = "用户实体") + @Data + @AllArgsConstructor + @NoArgsConstructor + static class UserEntity { + + @ApiModelProperty("用户ID") + private Integer userId; + + @ApiModelProperty("用户名称") + private String username; + + @ApiModelProperty("用户密码") + private String password; + + @ApiModelProperty("用户手机") + private String mobile; + } +} + diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/request/LoginDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/request/LoginDTO.java new file mode 100644 index 0000000..cb97f27 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/request/LoginDTO.java @@ -0,0 +1,33 @@ +package com.agileboot.admin.request; + +import lombok.Data; + +/** + * 用户登录对象 + * + * @author valarchie + */ +@Data +public class LoginDTO { + + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 + */ + private String uuid; + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/request/RegisterDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/request/RegisterDTO.java new file mode 100644 index 0000000..4f5e359 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/request/RegisterDTO.java @@ -0,0 +1,27 @@ +package com.agileboot.admin.request; + +import com.agileboot.domain.system.user.RegisterUserModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户注册对象 + * + * @author valarchie + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class RegisterDTO extends LoginDTO { + + public RegisterUserModel toModel() { + RegisterUserModel model = new RegisterUserModel(); + + model.setCode(this.getCode()); + model.setUuid(this.getUuid()); + model.setUsername(this.getUsername()); + model.setPassword(this.getPassword()); + + return model; + } + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/response/UploadDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/response/UploadDTO.java new file mode 100644 index 0000000..a4a6c92 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/response/UploadDTO.java @@ -0,0 +1,18 @@ +package com.agileboot.admin.response; + +import lombok.Builder; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +@Builder +public class UploadDTO { + + private String url; + private String fileName; + private String newFileName; + private String originalFilename; + +} diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/response/UserPermissionDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/response/UserPermissionDTO.java new file mode 100644 index 0000000..a3f3ee0 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/response/UserPermissionDTO.java @@ -0,0 +1,16 @@ +package com.agileboot.admin.response; + +import com.agileboot.domain.system.user.UserDTO; +import java.util.Map; +import java.util.Set; +import lombok.Data; + +@Data +public class UserPermissionDTO { + + private UserDTO user; + private String roleKey; + private Set permissions; + private Map dictTypes; + +} diff --git a/agileboot-api/pom.xml b/agileboot-api/pom.xml new file mode 100644 index 0000000..4f17bfb --- /dev/null +++ b/agileboot-api/pom.xml @@ -0,0 +1,40 @@ + + + + agileboot + com.agileboot + 1.0.0 + + 4.0.0 + + agileboot-api + + + quartz定时任务 + + + + + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + + + + + + + com.agileboot + agileboot-common + + + + + diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java new file mode 100644 index 0000000..0864de2 --- /dev/null +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java @@ -0,0 +1,16 @@ +package com.agileboot.api.controller; + +import com.agileboot.common.core.base.BaseController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 调度日志操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/api/order") +public class OrderController extends BaseController { + +} diff --git a/agileboot-common/pom.xml b/agileboot-common/pom.xml new file mode 100644 index 0000000..d4b0064 --- /dev/null +++ b/agileboot-common/pom.xml @@ -0,0 +1,166 @@ + + + + agileboot + com.agileboot + 1.0.0 + + 4.0.0 + + agileboot-common + + + common通用工具 + + + + + + + org.springframework + spring-context-support + + + + + org.springframework + spring-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + + commons-io + commons-io + + + + + commons-fileupload + commons-fileupload + + + + + org.apache.poi + poi-ooxml + + + + + org.yaml + snakeyaml + + + + + io.jsonwebtoken + jjwt + + + + + javax.xml.bind + jaxb-api + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + eu.bitwalker + UserAgentUtils + + + + + javax.servlet + javax.servlet-api + + + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.module + jackson-module-parameter-names + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + io.springfox + springfox-boot-starter + + + + org.lionsoul + ip2region + 2.6.5 + + + + + io.swagger + swagger-models + 1.6.2 + + + + + diff --git a/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelColumn.java b/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelColumn.java new file mode 100644 index 0000000..17f1a5c --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelColumn.java @@ -0,0 +1,19 @@ +package com.agileboot.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义导出Excel数据注解 + * + * @author valarchie + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ExcelColumn { + + String name() default ""; + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelSheet.java b/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelSheet.java new file mode 100644 index 0000000..af3f00f --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelSheet.java @@ -0,0 +1,20 @@ +package com.agileboot.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author valarchie + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ExcelSheet { + + /** + * sheet名称 + */ + String name() default ""; + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/config/AgileBootConfig.java b/agileboot-common/src/main/java/com/agileboot/common/config/AgileBootConfig.java new file mode 100644 index 0000000..757b4b5 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/config/AgileBootConfig.java @@ -0,0 +1,146 @@ +package com.agileboot.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "agileboot") +public class AgileBootConfig { + + /** + * 项目名称 + */ + private String name; + + /** + * 版本 + */ + private String version; + + /** + * 版权年份 + */ + private String copyrightYear; + + /** + * 实例演示开关 + */ + private boolean demoEnabled; + + /** + * 上传路径 + */ + private static String profile; + + /** + * 获取地址开关 + */ + private static boolean addressEnabled; + + /** + * 验证码类型 + */ + private static String captchaType; + + /** + * rsa private key 静态属性的注入!! set方法一定不能是static 方法 + */ + private static String rsaPrivateKey; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getCopyrightYear() { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) { + this.copyrightYear = copyrightYear; + } + + public boolean isDemoEnabled() { + return demoEnabled; + } + + public void setDemoEnabled(boolean demoEnabled) { + this.demoEnabled = demoEnabled; + } + + public static String getProfile() { + return profile; + } + + public void setProfile(String profile) { + AgileBootConfig.profile = profile; + } + + public static boolean isAddressEnabled() { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) { + AgileBootConfig.addressEnabled = addressEnabled; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + AgileBootConfig.captchaType = captchaType; + } + + public static String getRsaPrivateKey() { + return rsaPrivateKey; + } + + public void setRsaPrivateKey(String rsaPrivateKey) { + AgileBootConfig.rsaPrivateKey = rsaPrivateKey; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() { + return getProfile() + "/upload"; + } +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/constant/Constants.java b/agileboot-common/src/main/java/com/agileboot/common/constant/Constants.java new file mode 100644 index 0000000..a88d95b --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/constant/Constants.java @@ -0,0 +1,66 @@ +package com.agileboot.common.constant; + + +/** + * 通用常量信息 + * + * @author valarchie + */ +public class Constants { + + public static final int KB = 1024; + + public static final int MB = KB * 1024; + + public static final int GB = MB * 1024; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + + public static class Token { + /** + * 令牌 + */ + public static final String TOKEN_FIELD = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + } + + public static class Captcha { + /** + * 令牌 + */ + public static final String MATH_TYPE = "math"; + + /** + * 令牌前缀 + */ + public static final String CHAR_TYPE = "char"; + + } + + + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseController.java b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseController.java new file mode 100644 index 0000000..9fa9953 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseController.java @@ -0,0 +1,40 @@ +package com.agileboot.common.core.base; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import java.beans.PropertyEditorSupport; +import java.util.Date; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; + +/** + * @author valarchie + */ +@Slf4j +public class BaseController { + + /** + * + * 将前台传递过来的日期格式的字符串,自动转化为Date类型 + */ + @InitBinder + public void initBinder(WebDataBinder binder) { + // Date 类型转换 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { + @Override + public void setAsText(String text) { + setValue(DateUtil.parseDate(text)); + } + }); + } + + /** + * 页面跳转 + */ + public String redirect(String url) { + return StrUtil.format("redirect:{}", url); + } + + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java new file mode 100644 index 0000000..77f8557 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java @@ -0,0 +1,62 @@ +package com.agileboot.common.core.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.annotations.ApiModelProperty; +import java.util.Date; +import lombok.Data; + +/** + * Entity基类 + * + * @author valarchie + */ +@Data +public class BaseEntity> extends Model { + + @ApiModelProperty("创建者ID") + @TableField("creator_id") + private Long creatorId; + + @ApiModelProperty("创建者") + @TableField("creator_name") + private String creatorName; + + @ApiModelProperty("创建时间") + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Date createTime; + + @ApiModelProperty("更新者ID") + @TableField("updater_id") + private Long updaterId; + + @ApiModelProperty("更新者") + @TableField("updater_name") + private String updaterName; + + @ApiModelProperty("更新时间") + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + @ApiModelProperty("删除标志(0代表存在 1代表删除)") + @TableField("deleted") + @TableLogic + private Boolean deleted; + + public void logCreator(BaseUser user) { + if (user != null) { + this.creatorId = user.getUserId(); + this.creatorName = user.getUsername(); + } + } + + public void logUpdater(BaseUser user) { + if (user != null) { + this.updaterId = user.getUserId(); + this.updaterName = user.getUsername(); + } + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseUser.java b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseUser.java new file mode 100644 index 0000000..0aa5935 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseUser.java @@ -0,0 +1,13 @@ +package com.agileboot.common.core.base; + +import lombok.Data; + +@Data +public class BaseUser { + + private Long userId; + private String username; + private Long deptId; + private Long roleId; + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/dto/ResponseDTO.java b/agileboot-common/src/main/java/com/agileboot/common/core/dto/ResponseDTO.java new file mode 100644 index 0000000..360cb32 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/core/dto/ResponseDTO.java @@ -0,0 +1,64 @@ +package com.agileboot.common.core.dto; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.exception.error.ErrorCodeInterface; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 响应信息主体 + * + * @author valarchie + */ +@Data +@AllArgsConstructor +public class ResponseDTO { + + private Integer code; + + private String msg; + + private T data; + + public static ResponseDTO ok() { + return build(null, ErrorCode.SUCCESS); + } + + public static ResponseDTO ok(T data) { + return build(data, ErrorCode.SUCCESS); + } + + public static ResponseDTO fail() { + return build(null, ErrorCode.FAIL); + } + + public static ResponseDTO fail(ErrorCodeInterface code) { + return build(null, code); + } + + public static ResponseDTO fail(ApiException exception) { + return new ResponseDTO<>(exception.getErrorCode().code(), exception.getMessage(), null); + } + + public static ResponseDTO fail(ErrorCodeInterface code, Object... args) { + return build( code, args); + } + + public static ResponseDTO fail(T data) { return build(ErrorCode.FAIL, data); } + + public static ResponseDTO build(T data, ErrorCodeInterface code) { + return new ResponseDTO<>(code.code(), code.message(), data); + } + + public static ResponseDTO build(ErrorCodeInterface code, Object... args) { + return new ResponseDTO<>(code.code(), StrUtil.format(code.message(), args), null); + } + + public static ResponseDTO build(T data, ErrorCodeInterface code, Object... args) { + return new ResponseDTO<>(code.code(), StrUtil.format(code.message(), args), data); + } + +} + diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/page/PageDTO.java b/agileboot-common/src/main/java/com/agileboot/common/core/page/PageDTO.java new file mode 100644 index 0000000..72a083e --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/core/page/PageDTO.java @@ -0,0 +1,37 @@ +package com.agileboot.common.core.page; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +public class PageDTO { + /** + * 总记录数 + */ + private Long total; + + /** + * 列表数据 + */ + private List rows; + + public PageDTO(List list) { + this.rows = list; + this.total = (long) list.size(); + } + + public PageDTO(Page page) { + this.rows = page.getRecords(); + this.total = page.getTotal(); + } + + public PageDTO(List list, Long count) { + this.rows = list; + this.total = count; + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/enums/DataSourceType.java b/agileboot-common/src/main/java/com/agileboot/common/enums/DataSourceType.java new file mode 100644 index 0000000..8701608 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/enums/DataSourceType.java @@ -0,0 +1,18 @@ +package com.agileboot.common.enums; + +/** + * 数据源 + * + * @author ruoyi + */ +public enum DataSourceType { + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/enums/LimitType.java b/agileboot-common/src/main/java/com/agileboot/common/enums/LimitType.java new file mode 100644 index 0000000..8f91948 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/enums/LimitType.java @@ -0,0 +1,19 @@ +package com.agileboot.common.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType { + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/ApiException.java b/agileboot-common/src/main/java/com/agileboot/common/exception/ApiException.java new file mode 100644 index 0000000..cf7380c --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/exception/ApiException.java @@ -0,0 +1,76 @@ +package com.agileboot.common.exception; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.exception.error.ErrorCodeInterface; +import com.agileboot.common.utils.i18n.MessageUtils; +import lombok.extern.slf4j.Slf4j; + +/** + * @author valarchie + */ +@Slf4j +public class ApiException extends RuntimeException{ + + + protected ErrorCodeInterface errorCode; + + protected String message; + + protected Object[] args; + + protected String formattedMessage; + protected String i18nFormattedMessage; + + public ApiException(Throwable e, ErrorCodeInterface errorCode, Object... args) { + super(e); + fillErrorCode(errorCode, args); + } + + public ApiException(Throwable e, ErrorCodeInterface errorCode) { + super(e); + fillErrorCode(errorCode); + } + + public ApiException(ErrorCodeInterface errorCode, Object... args) { + fillErrorCode(errorCode, args); + } + + public ApiException(ErrorCodeInterface errorCode) { + fillErrorCode(errorCode); + } + + private void fillErrorCode(ErrorCodeInterface errorCode, Object... args) { + this.errorCode = errorCode; + this.message = errorCode.message(); + this.args = args; + + this.formattedMessage = StrUtil.format(this.message, args); + + try { + this.i18nFormattedMessage = MessageUtils.message(errorCode.i18n(), args); + } catch (Exception e) { + log.error("could not found i18n error i18nMessage entry : " + e.getMessage()); + } + + } + + + public ErrorCodeInterface getErrorCode() { + return errorCode; + } + + public void setErrorCode(ErrorCodeInterface errorCode) { + this.errorCode = errorCode; + } + + @Override + public String getMessage() { + return i18nFormattedMessage != null ? i18nFormattedMessage : formattedMessage; + } + + @Override + public String getLocalizedMessage() { + return i18nFormattedMessage; + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java new file mode 100644 index 0000000..0e52497 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java @@ -0,0 +1,322 @@ +package com.agileboot.common.exception.error; + +/** + * 常用错误码 以及 保留错误码 + * @author valarchie + */ +public enum ErrorCode implements ErrorCodeInterface { + + /** + * 错误码集合 + * 1~9999 为保留错误码 或者 常用错误码 + * 10000~19999 为内部错误码 + * 20000~29999 客户端错误码 (客户端异常调用之类的错误) + * 30000~39999 为第三方错误码 (代码正常,但是第三方异常) + * 40000~49999 为业务逻辑 错误码 (无异常,代码正常流转,并返回提示给用户) + * 由于系统内的错误码都是独一无二的,所以错误码应该放在common包集中管理 + */ + // -------------- 普通错误码 及保留错误码 --------------- + SUCCESS(0,"操作成功"), + FAIL(9999, "操作失败"), + + + UNKNOWN_ERROR(99999,"未知错误"); + + private final int code; + private final String msg; + + + ErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + } + + @Override + public int code() { + return this.code; + } + + @Override + public String message() { + return this.msg; + } + + /** + * 40000~49999 为业务逻辑 错误码 (无代码异常,代码正常流转,并返回提示给用户) + */ + public enum Business implements ErrorCodeInterface{ + + // ----------------------------- Common ----------------------------------------- + + OBJECT_NOT_FOUND(Module.COMMON, 1, "找不到ID为%s 的%s"), + + UNSUPPORTED_OPERATION(Module.COMMON, 2, "不支持的操作"), + + BULK_DELETE_IDS_IS_INVALID(Module.COMMON, 3, "批量参数ID列表为空"), + + // ----------------------------- Permission ----------------------------------------- + + FORBIDDEN_TO_MODIFY_ADMIN(Module.PERMISSION, 1, "不允许修改管理员的信息"), + + NO_PERMISSION_TO_OPERATE(Module.PERMISSION, 2, "没有权限进行此操作,请联系管理员"), + + // ----------------------------- Login ----------------------------------------- + + LOGIN_WRONG_USER_PASSWORD(Module.LOGIN, 1, "用户密码错误,请重输"), + + LOGIN_ERROR(Module.LOGIN, 2, "登录失败:{}"), + + CAPTCHA_CODE_WRONG(Module.LOGIN, 3, "验证码错误"), + + CAPTCHA_CODE_EXPIRE(Module.LOGIN, 4, "验证码过期"), + + CAPTCHA_CODE_NULL(Module.LOGIN, 5, "验证码为空"), + + +// ----------------------------- Upload ----------------------------------------- + + UPLOAD_FILE_TYPE_NOT_ALLOWED(Module.UPLOAD, 1, "不允许上传的文件类型,仅允许:%s"), + + UPLOAD_FILE_NAME_EXCEED_MAX_LENGTH(Module.UPLOAD, 2, "文件名长度超过:%s "), + + UPLOAD_FILE_SIZE_EXCEED_MAX_SIZE(Module.UPLOAD, 3, "文件名大小超过:%s MB"), + + UPLOAD_IMPORT_EXCEL_FAILED(Module.UPLOAD, 4, "导入excel失败:%s"), + + UPLOAD_FILE_IS_EMPTY(Module.UPLOAD, 5, "上传文件为空"), + + // ----------------------------- Config ----------------------------------------- + + CONFIG_VALUE_IS_NOT_ALLOW_TO_EMPTY(Module.CONFIG, 1, "参数键值不允许为空"), + + CONFIG_VALUE_IS_NOT_IN_OPTIONS(Module.CONFIG, 2, "参数键值不存在列表中"), + + // ----------------------------- Post ----------------------------------------- + + POST_NAME_IS_NOT_UNIQUE(Module.POST, 1, "岗位名称:%s, 已存在"), + + POST_CODE_IS_NOT_UNIQUE(Module.POST, 2, "岗位编号:%s, 已存在"), + + POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED(Module.POST, 3, "职位已分配给用户,请先取消分配再删除"), + + // ------------------------------- Dept --------------------------------------------- + + DEPT_NAME_IS_NOT_UNIQUE(Module.DEPT, 1, "部门名称:%s, 已存在"), + + DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF(Module.DEPT, 2, "父级部门不能选择自己"), + + DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE(Module.DEPT, 3, "子部门还有正在启用的部门,暂时不能停用该部门"), + + DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE(Module.DEPT, 4, "该部门存在下级部门不允许删除"), + + DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE(Module.DEPT, 5, "该部门存在关联的用户不允许删除"), + + DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED(Module.DEPT, 6, "该父级部门不存在或已停用"), + + // ------------------------------- Menu ------------------------------------------------------- + + MENU_NAME_IS_NOT_UNIQUE(Module.MENU, 1, "新增菜单:%s 失败,菜单名称已存在"), + + MENU_EXTERNAL_LINK_MUST_BE_HTTP(Module.MENU, 2, "菜单外链必须以 http(s)://开头"), + + MENU_PARENT_ID_NOT_ALLOW_SELF(Module.MENU, 3, "父级菜单不能选择自身"), + + MENU_EXIST_CHILD_MENU_NOT_ALLOW_DELETE(Module.MENU, 4, "存在子菜单不允许删除"), + + MENU_ALREADY_ASSIGN_TO_ROLE_NOT_ALLOW_DELETE(Module.MENU, 5, "菜单已分配给角色,不允许"), + + // -------------------------------- Role ---------------------------------------------------- + + ROLE_NAME_IS_NOT_UNIQUE(Module.ROLE, 1, "角色名称:%s, 已存在"), + + ROLE_KEY_IS_NOT_UNIQUE(Module.ROLE, 2, "角色标识:%s, 已存在"), + + ROLE_DATA_SCOPE_DUPLICATED_DEPT(Module.ROLE, 3, "重复的部门id"), + + ROLE_ALREADY_ASSIGN_TO_USER(Module.ROLE, 4, "角色已分配给用户,请先取消分配,再删除角色"), + + + // ------------------------ User ------------------------------ + + + USER_NON_EXIST(Module.USER, 1, "登录用户:%s 不存在"), + + USER_IS_DISABLE(Module.USER, 2, "对不起, 您的账号:{} 已停用"), + + USER_CACHE_IS_EXPIRE(Module.USER, 3, "用户缓存信息已经过期"), + + USER_FAIL_TO_GET_USER_ID(Module.USER, 3, "获取用户ID失败"), + + USER_FAIL_TO_GET_DEPT_ID(Module.USER, 4, "获取用户部门ID失败"), + + USER_FAIL_TO_GET_ACCOUNT(Module.USER, 5, "获取用户账户失败"), + + USER_FAIL_TO_GET_USER_INFO(Module.USER, 6, "获取用户信息失败"), + + USER_IMPORT_DATA_IS_NULL(Module.USER, 7, "导入的用户为空"), + + USER_PHONE_NUMBER_IS_NOT_UNIQUE(Module.USER, 8, "该电话号码已被其他用户占用"), + + USER_EMAIL_IS_NOT_UNIQUE(Module.USER, 9, "该邮件地址已被其他用户占用"), + + USER_PASSWORD_IS_NOT_CORRECT(Module.USER, 10, "用户密码错误"), + + USER_NEW_PASSWORD_IS_THE_SAME_AS_OLD(Module.USER, 11, "用户新密码与旧密码相同"), + + USER_UPLOAD_FILE_FAILED(Module.USER, 12, "用户上传文件失败"), + + USER_NAME_IS_NOT_UNIQUE(Module.USER, 13, "用户名已被其他用户占用"), + + USER_CURRENT_USER_CAN_NOT_BE_DELETE(Module.USER, 14, "当前用户不允许被删除"), + + ; + + + private final int code; + private final String msg; + + private static final int BASE_CODE = 40000; + + Business(Module module, int code, String msg) { + this.code = BASE_CODE + module.code() + code; + this.msg = msg; + } + + @Override + public int code() { + return this.code; + } + + @Override + public String message() { + return this.msg; + } + + } + + + /** + * 30000~39999是外部错误码 比如调用支付失败 + */ + public enum External implements ErrorCodeInterface { + + /** + * 支付宝调用失败 + */ + FAIL_TO_PAY_ON_ALIPAY(Module.COMMON, 1,"支付宝调用失败"); + + + private final int code; + private final String msg; + + private static final int BASE_CODE = 30000; + + External(Module module, int code, String msg) { + this.code = BASE_CODE + module.code() + code; + this.msg = msg; + } + + @Override + public int code() { + return this.code; + } + + @Override + public String message() { return this.msg; } + + + } + + + /** + * 20000~29999是客户端错误码 + */ + public enum Client implements ErrorCodeInterface { + + + /** + * 客户端错误码 + */ + COMMON_FORBIDDEN_TO_CALL(Module.COMMON, 1,"禁止调用"), + + COMMON_REQUEST_TO_OFTEN(Module.COMMON, 2, "调用太过频繁"), + + COMMON_REQUEST_PARAMETERS_INVALID(Module.COMMON, 3, "请求参数异常,%s"), + + COMMON_REQUEST_METHOD_INVALID(Module.COMMON, 4, "请求方式不支持"), + + COMMON_NO_AUTHORIZATION(Module.PERMISSION, 1, "请求接口:%s 失败,用户未授权"), + + ; + + + private final int code; + private final String msg; + + private static final int BASE_CODE = 20000; + + Client(Module module, int code, String msg) { + this.code = BASE_CODE + module.code() + code; + this.msg = msg; + } + + @Override + public int code() { + return this.code; + } + + @Override + public String message() { + return this.msg; + } + + } + + + /** + * 10000~19999是内部错误码 例如 框架有问题之类的 + */ + public enum Internal implements ErrorCodeInterface { + + /** + * 内部错误码 + */ + INVALID_PARAMETER(Module.COMMON, 1,"参数异常"), + + UNKNOWN_ERROR(Module.COMMON, 2,"未知异常: %s"), + + GET_ENUM_FAILED(Module.COMMON, 3,"获取枚举类型失败, 枚举类:%s"), + + LOGIN_CAPTCHA_GENERATE_FAIL(Module.LOGIN, 1,"验证码生成失败"), + + INVALID_TOKEN(Module.PERMISSION, 1,"token异常"), + + DB_INTERNAL_ERROR(Module.DB, 1, "数据库异常:%s"), + + ; + + + private final int code; + private final String msg; + + private static final int BASE_CODE = 10000; + + Internal(Module module, int code, String msg) { + this.code = BASE_CODE + module.code() + code; + this.msg = msg; + } + + @Override + public int code() { + return this.code; + } + + @Override + public String message() { + return this.msg; + } + + } + + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCodeInterface.java b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCodeInterface.java new file mode 100644 index 0000000..4575f7d --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCodeInterface.java @@ -0,0 +1,15 @@ +package com.agileboot.common.exception.error; + +public interface ErrorCodeInterface { + + String name(); + + int code(); + + String message(); + + default String i18n() { + return code() + "_" + name(); + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/error/Module.java b/agileboot-common/src/main/java/com/agileboot/common/exception/error/Module.java new file mode 100644 index 0000000..fcb1e30 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/exception/error/Module.java @@ -0,0 +1,46 @@ +package com.agileboot.common.exception.error; + +/** + * 系统内的模块 + */ +public enum Module { + + /** + * 普通模块 + */ + COMMON(0), + + /** + * 权限模块 + */ + PERMISSION(1), + + LOGIN(2), + + DB(3), + + UPLOAD(4), + + USER(5), + + CONFIG(6), + + POST(7), + + DEPT(8), + + MENU(9), + + ROLE(10), + + + ; + + + private final int code; + + Module(int code) { this.code = code * 100; } + + public int code() {return code; } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ServletHolderUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ServletHolderUtil.java new file mode 100644 index 0000000..6081006 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ServletHolderUtil.java @@ -0,0 +1,71 @@ +package com.agileboot.common.utils; + +import cn.hutool.core.util.StrUtil; +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * 客户端工具类 + * + * @author ruoyi + */ +public class ServletHolderUtil { + + /** + * 获取request + */ + public static HttpServletRequest getRequest() { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() { + return getRequestAttributes().getResponse(); + } + + + public static ServletRequestAttributes getRequestAttributes() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) { + try { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 获取仅含有项目根路径的url + * 比如 localhost:8080/agileboot/user/list + * 返回 localhost:8080/agileboot + * @return + */ + public static String getContextUrl() { + HttpServletRequest request = getRequest(); + StringBuffer url = request.getRequestURL(); + String contextPath = request.getServletContext().getContextPath(); + String strip = StrUtil.strip(url, null, request.getRequestURI()); + return strip + contextPath; + } + + + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/file/FileUploadUtils.java b/agileboot-common/src/main/java/com/agileboot/common/utils/file/FileUploadUtils.java new file mode 100644 index 0000000..b8940ad --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/file/FileUploadUtils.java @@ -0,0 +1,216 @@ +package com.agileboot.common.utils.file; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import com.agileboot.common.config.AgileBootConfig; +import com.agileboot.common.constant.Constants; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.FilenameUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.util.MimeType; +import org.springframework.util.MimeTypeUtils; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件上传工具类 + * + * @author ruoyi 待改进 + */ +public class FileUploadUtils { + + /** + * 默认大小 50M + */ + public static final long MAX_FILE_SIZE = 50 * Constants.MB; + + /** + * 默认的文件名最大长度 100 + */ + public static final int MAX_FILE_NAME_LENGTH = 100; + + public static final String[] ALLOWED_DOWNLOAD_EXTENSIONS = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf"}; + + /** + * 默认上传的地址 + */ + private static String defaultBaseDir = AgileBootConfig.getProfile(); + + + public static String getDefaultBaseDir() { + return defaultBaseDir; + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件名称 + */ + public static String upload(MultipartFile file) throws IOException { + try { + return upload(getDefaultBaseDir(), file, ALLOWED_DOWNLOAD_EXTENSIONS); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @return 文件名称 + */ + public static String upload(String baseDir, MultipartFile file) throws IOException { + try { + return upload(baseDir, file, ALLOWED_DOWNLOAD_EXTENSIONS); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws IOException 比如读写文件出错时 + */ + public static String upload(String baseDir, MultipartFile file, String[] allowedExtension) + throws IOException { + + int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNameLength > FileUploadUtils.MAX_FILE_NAME_LENGTH) { + throw new ApiException(ErrorCode.Business.UPLOAD_FILE_NAME_EXCEED_MAX_LENGTH, MAX_FILE_NAME_LENGTH); + } + + assertAllowed(file, allowedExtension); + + String fileName = generateFilename(file); + + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(baseDir, fileName); + } + + /** + * 编码文件名 + */ + public static String generateFilename(MultipartFile file) { + return StrUtil.format("{}_{}_{}.{}", DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN), + FilenameUtils.getBaseName(file.getOriginalFilename()), IdUtil.simpleUUID(), getExtension(file)); + } + + public static File getAbsoluteFile(String uploadDir, String fileName) { + File desc = new File(uploadDir + File.separator + fileName); + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static String getPathFileName(String uploadDir, String fileName) { + String currentDir = StrUtil.strip(uploadDir, AgileBootConfig.getProfile() + "/"); + return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + */ + public static void assertAllowed(MultipartFile file, String[] allowedExtension) { + long size = file.getSize(); + if (size > MAX_FILE_SIZE) { + throw new ApiException(ErrorCode.Business.UPLOAD_FILE_SIZE_EXCEED_MAX_SIZE, MAX_FILE_SIZE / Constants.MB); + } + + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { + throw new ApiException(ErrorCode.Business.UPLOAD_FILE_TYPE_NOT_ALLOWED, + StrUtil.join(",", (Object[]) allowedExtension)); + } + + } + + /** + * 判断MIME类型是否是允许的MIME类型 + */ + public static boolean isAllowedExtension(String extension, String[] allowedExtension) { + return StrUtil.containsAnyIgnoreCase(extension, allowedExtension); + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static String getExtension(MultipartFile file) { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StrUtil.isEmpty(extension)) { + MimeType mimeType = MimeTypeUtils.parseMimeType(Objects.requireNonNull(file.getContentType())); + extension = mimeType.getSubtype(); + } + return extension; + } + + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean isAllowDownload(String resource) { + // 禁止目录上跳级别 + return !StrUtil.contains(resource, "..") && + // 检查允许下载的文件规则 + StrUtil.containsAnyIgnoreCase(FileNameUtil.getSuffix(resource), ALLOWED_DOWNLOAD_EXTENSIONS); + } + + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) { + String fileNameUrlEncoded = URLUtil.encode(realFileName, CharsetUtil.CHARSET_UTF_8); + + String contentDisposition = String.format("attachment; filename=%s;filename*=utf-8''%s", fileNameUrlEncoded, + fileNameUrlEncoded); + + response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Content-Disposition,download-filename"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition); + response.setHeader("download-filename", fileNameUrlEncoded); + } + + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/i18n/MessageUtils.java b/agileboot-common/src/main/java/com/agileboot/common/utils/i18n/MessageUtils.java new file mode 100644 index 0000000..72bff6c --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/i18n/MessageUtils.java @@ -0,0 +1,25 @@ +package com.agileboot.common.utils.i18n; + +import cn.hutool.extra.spring.SpringUtil; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; + +/** + * 获取i18n资源文件 + * + * @author ruoyi + */ +public class MessageUtils { + + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) { + MessageSource messageSource = SpringUtil.getBean(MessageSource.class); + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegion.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegion.java new file mode 100644 index 0000000..8af5816 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegion.java @@ -0,0 +1,32 @@ +package com.agileboot.common.utils.ip; + +import cn.hutool.core.text.CharSequenceUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class IpRegion { + private static final String UNKNOWN = "未知"; + private String country; + private String region; + private String province; + private String city; + private String isp; + + public IpRegion(String province, String city) { + this.province = province; + this.city = city; + } + + public String briefLocation() { + return String.format("%s %s", + CharSequenceUtil.nullToDefault(province, UNKNOWN), + CharSequenceUtil.nullToDefault(city, UNKNOWN)); + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegionUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegionUtil.java new file mode 100644 index 0000000..a42a2e3 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegionUtil.java @@ -0,0 +1,26 @@ +package com.agileboot.common.utils.ip; + +/** + * @author valarchie + */ +public class IpRegionUtil { + + public static IpRegion getIpRegion(String ip) { + IpRegion ipRegionOffline = OfflineIpRegionUtil.getIpRegion(ip); + if (ipRegionOffline != null) { + return ipRegionOffline; + } + + IpRegion ipRegionOnline = OnlineIpRegionUtil.getIpRegion(ip); + if (ipRegionOnline != null) { + return ipRegionOnline; + } + + return new IpRegion(); + } + + public static String getBriefLocationByIp(String ip) { + return getIpRegion(ip).briefLocation(); + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OfflineIpRegionUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OfflineIpRegionUtil.java new file mode 100644 index 0000000..6a515c2 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OfflineIpRegionUtil.java @@ -0,0 +1,51 @@ +package com.agileboot.common.utils.ip; + +import cn.hutool.core.util.StrUtil; +import java.io.IOException; +import java.io.InputStream; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.lionsoul.ip2region.xdb.Searcher; + +@Slf4j +public class OfflineIpRegionUtil { + + private static Searcher searcher; + + static { + InputStream resourceAsStream = OfflineIpRegionUtil.class.getResourceAsStream("/ip2region.xdb"); + + byte[] bytes = null; + try { + bytes = new byte[resourceAsStream.available()]; + IOUtils.read(resourceAsStream, bytes); + } catch (IOException e) { + log.error("读取本地Ip文件失败", e); + } + + try { + searcher = Searcher.newWithBuffer(bytes); + } catch (Exception e) { + log.error("构建本地Ip缓存失败", e); + } + + } + + public static IpRegion getIpRegion(String ip) { + try { + + String rawRegion = searcher.search(ip); + if (StrUtil.isNotEmpty(rawRegion)) { + String[] split = rawRegion.split("\\|"); + return new IpRegion(split[0], split[1], split[2], split[3], split[4]); + } + + return null; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OnlineIpRegionUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OnlineIpRegionUtil.java new file mode 100644 index 0000000..89b5739 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OnlineIpRegionUtil.java @@ -0,0 +1,49 @@ +package com.agileboot.common.utils.ip; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.agileboot.common.config.AgileBootConfig; +import com.agileboot.common.utils.jackson.JacksonUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * query geography address from ip + * + * @author valarchie + */ +@Slf4j +public class OnlineIpRegionUtil { + + /** + * website for query geography address from ip + */ + public static final String ADDRESS_QUERY_SITE = "http://whois.pconline.com.cn/ipJson.jsp"; + + + public static IpRegion getIpRegion(String ip) { + // no need to query address for inner ip + if (NetUtil.isInnerIP(ip)) { + return new IpRegion("internal", "IP"); + } + if (AgileBootConfig.isAddressEnabled()) { + try { + String rspStr = HttpUtil.get(ADDRESS_QUERY_SITE + "ip=" + ip + "&json=true", + CharsetUtil.CHARSET_GBK); + if (StrUtil.isEmpty(rspStr)) { + log.error("获取地理位置异常 {}", ip); + return null; + } + + String province = JacksonUtil.getAsString(rspStr, "pro"); + String city = JacksonUtil.getAsString(rspStr, "city"); + return new IpRegion(province, city); + } catch (Exception e) { + log.error("获取地理位置异常 {}", ip); + } + } + return null; + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonException.java b/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonException.java new file mode 100644 index 0000000..0f43c90 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonException.java @@ -0,0 +1,23 @@ +package com.agileboot.common.utils.jackson; + +/** + * @author valarchie + */ +public class JacksonException extends RuntimeException { + public JacksonException() { + super(); + } + + public JacksonException(String message) { + super(message); + } + + public JacksonException(String message, Exception e) { + super(message, e); + } + + public JacksonException(Throwable cause) { + super(cause); + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonUtil.java new file mode 100644 index 0000000..49e23f5 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonUtil.java @@ -0,0 +1,679 @@ +package com.agileboot.common.utils.jackson; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.MapType; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * Jackson工具类 优势: 数据量高于百万的时候,速度和FastJson相差极小 API和注解支持最完善,可定制性最强 + * 支持的数据源最广泛(字符串,对象,文件、流、URL) + * @author valarchie + */ +@Slf4j +public class JacksonUtil { + + private static ObjectMapper mapper; + + private static final Set JSON_READ_FEATURES_ENABLED = CollUtil.newHashSet( + //允许在JSON中使用Java注释 + JsonReadFeature.ALLOW_JAVA_COMMENTS, + //允许 json 存在没用双引号括起来的 field + JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES, + //允许 json 存在使用单引号括起来的 field + JsonReadFeature.ALLOW_SINGLE_QUOTES, + //允许 json 存在没用引号括起来的 ascii 控制字符 + JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS, + //允许 json number 类型的数存在前导 0 (例: 0001) + JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS, + //允许 json 存在 NaN, INF, -INF 作为 number 类型 + JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS, + //允许 只有Key没有Value的情况 + JsonReadFeature.ALLOW_MISSING_VALUES, + //允许数组json的结尾多逗号 + JsonReadFeature.ALLOW_TRAILING_COMMA + ); + + static { + try { + //初始化 + mapper = initMapper(); + } catch (Exception e) { + log.error("jackson config error", e); + } + } + + public static ObjectMapper initMapper() { + JsonMapper.Builder builder = JsonMapper.builder() + .enable(JSON_READ_FEATURES_ENABLED.toArray(new JsonReadFeature[0])); + return initMapperConfig(builder.build()); + } + + public static ObjectMapper initMapperConfig(ObjectMapper objectMapper) { + String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; + objectMapper.setDateFormat(new SimpleDateFormat(dateTimeFormat)); + //配置序列化级别 + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + //配置JSON缩进支持 + objectMapper.configure(SerializationFeature.INDENT_OUTPUT, false); + //允许单个数值当做数组处理 + objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + //禁止重复键, 抛出异常 + objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY); + //禁止使用int代表Enum的order()來反序列化Enum, 抛出异常 + objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS); + //有属性不能映射的时候不报错 + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + //对象为空时不抛异常 + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + //时间格式 + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + //允许未知字段 + objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN); + //序列化BigDecimal时之间输出原始数字还是科学计数, 默认false, 即是否以toPlainString()科学计数方式来输出 + objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); + //识别Java8时间 + objectMapper.registerModule(new ParameterNamesModule()); + objectMapper.registerModule(new Jdk8Module()); + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDateTime.class, + new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat))) + .addDeserializer(LocalDateTime.class, + new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat))); + objectMapper.registerModule(javaTimeModule); + // if we use guava, we can add this line of code: objectMapper.registerModule(new GuavaModule()) + return objectMapper; + } + + public static ObjectMapper getObjectMapper() { + return mapper; + } + + /** + * JSON反序列化 + */ + public static V from(URL url, Class type) { + try { + return mapper.readValue(url, type); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e); + } + } + + /** + * JSON反序列化 + */ + public static V from(URL url, TypeReference type) { + try { + return mapper.readValue(url, type); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e); + } + } + + /** + * JSON反序列化(List) + */ + public static List fromList(URL url, Class type) { + try { + CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type); + return mapper.readValue(url, collectionType); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e); + } + } + + /** + * JSON反序列化 + */ + public static V from(InputStream inputStream, Class type) { + try { + return mapper.readValue(inputStream, type); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e); + } + } + + /** + * JSON反序列化 + */ + public static V from(InputStream inputStream, TypeReference type) { + try { + return mapper.readValue(inputStream, type); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e); + } + } + + /** + * JSON反序列化(List) + */ + public static List fromList(InputStream inputStream, Class type) { + try { + CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type); + return mapper.readValue(inputStream, collectionType); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e); + } + } + + /** + * JSON反序列化 + */ + public static V from(File file, Class type) { + try { + return mapper.readValue(file, type); + } catch (IOException e) { + throw new JacksonException( + StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e); + } + } + + /** + * JSON反序列化 + */ + public static V from(File file, TypeReference type) { + try { + return mapper.readValue(file, type); + } catch (IOException e) { + throw new JacksonException( + StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e); + } + } + + /** + * JSON反序列化(List) + */ + public static List fromList(File file, Class type) { + try { + CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type); + return mapper.readValue(file, collectionType); + } catch (IOException e) { + throw new JacksonException( + StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e); + } + } + + /** + * JSON反序列化 + */ + public static V from(String json, Class type) { + return from(json, (Type) type); + } + + /** + * JSON反序列化 + */ + public static V from(String json, TypeReference type) { + return from(json, type.getType()); + } + + /** + * JSON反序列化 + */ + public static V from(String json, Type type) { + if (StringUtils.isEmpty(json)) { + return null; + } + try { + JavaType javaType = mapper.getTypeFactory().constructType(type); + return mapper.readValue(json, javaType); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json, type), e); + } + } + + /** + * JSON反序列化(List) + */ + public static List fromList(String json, Class type) { + if (StringUtils.isEmpty(json)) { + return null; + } + try { + CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type); + return mapper.readValue(json, collectionType); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json, type), e); + } + } + + /** + * JSON反序列化(Map) + */ + public static Map fromMap(String json) { + if (StringUtils.isEmpty(json)) { + return null; + } + try { + MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class); + return mapper.readValue(json, mapType); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json), e); + } + } + + /** + * 序列化为JSON + */ + public static String to(List list) { + try { + return mapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + throw new JacksonException(StrUtil.format("jackson to error, data: {}", list), e); + } + } + + /** + * 序列化为JSON + */ + public static String to(V v) { + try { + return mapper.writeValueAsString(v); + } catch (JsonProcessingException e) { + throw new JacksonException(StrUtil.format("jackson to error, data: {}", v), e); + } + } + + /** + * 序列化为JSON + */ + public static void toFile(String path, List list) { + try (Writer writer = new FileWriter(new File(path), true)) { + mapper.writer().writeValues(writer).writeAll(list); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson to file error, path: {}, list: {}", path, list), e); + } + } + + /** + * 序列化为JSON + */ + public static void toFile(String path, V v) { + try (Writer writer = new FileWriter(new File(path), true)) { + mapper.writer().writeValues(writer).write(v); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson to file error, path: {}, data: {}", path, v), e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return String,默认为 null + */ + public static String getAsString(String json, String key) { + if (StringUtils.isEmpty(json)) { + return null; + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return null; + } + return getAsString(jsonNode); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson get string error, json: {}, key: {}", json, key), e); + } + } + + private static String getAsString(JsonNode jsonNode) { + return jsonNode.isTextual() ? jsonNode.textValue() : jsonNode.toString(); + } + + /** + * 从json串中获取某个字段 + * + * @return int,默认为 0 + */ + public static int getAsInt(String json, String key) { + if (StringUtils.isEmpty(json)) { + return 0; + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return 0; + } + return jsonNode.isInt() ? jsonNode.intValue() : Integer.parseInt(getAsString(jsonNode)); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson get int error, json: {}, key: {}", json, key), e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return long,默认为 0 + */ + public static long getAsLong(String json, String key) { + if (StringUtils.isEmpty(json)) { + return 0L; + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return 0L; + } + return jsonNode.isLong() ? jsonNode.longValue() : Long.parseLong(getAsString(jsonNode)); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson get long error, json: {}, key: {}", json, key), e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return double,默认为 0.0 + */ + public static double getAsDouble(String json, String key) { + if (StringUtils.isEmpty(json)) { + return 0.0; + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return 0.0; + } + return jsonNode.isDouble() ? jsonNode.doubleValue() : Double.parseDouble(getAsString(jsonNode)); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson get double error, json: {}, key: {}", json, key), e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return BigInteger,默认为 0.0 + */ + public static BigInteger getAsBigInteger(String json, String key) { + if (StringUtils.isEmpty(json)) { + return new BigInteger(String.valueOf(0.00)); + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return new BigInteger(String.valueOf(0.00)); + } + return jsonNode.isBigInteger() ? jsonNode.bigIntegerValue() : new BigInteger(getAsString(jsonNode)); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson get big integer error, json: {}, key: {}", json, key), + e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return BigDecimal,默认为 0.00 + */ + public static BigDecimal getAsBigDecimal(String json, String key) { + if (StringUtils.isEmpty(json)) { + return new BigDecimal("0.00"); + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return new BigDecimal("0.00"); + } + return jsonNode.isBigDecimal() ? jsonNode.decimalValue() : new BigDecimal(getAsString(jsonNode)); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson get big decimal error, json: {}, key: {}", json, key), + e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return boolean, 默认为false + */ + public static boolean getAsBoolean(String json, String key) { + if (StringUtils.isEmpty(json)) { + return false; + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return false; + } + if (jsonNode.isBoolean()) { + return jsonNode.booleanValue(); + } else { + if (jsonNode.isTextual()) { + String textValue = jsonNode.textValue(); + return Convert.toBool(textValue); + } else {//number + return BooleanUtils.toBoolean(jsonNode.intValue()); + } + } + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson get boolean error, json: {}, key: {}", json, key), e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return byte[], 默认为 null + */ + public static byte[] getAsBytes(String json, String key) { + if (StringUtils.isEmpty(json)) { + return null; + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return null; + } + return jsonNode.isBinary() ? jsonNode.binaryValue() : getAsString(jsonNode).getBytes(); + } catch (Exception e) { + throw new JacksonException(StrUtil.format("jackson get byte error, json: {}, key: {}", json, key), e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return object, 默认为 null + */ + public static V getAsObject(String json, String key, Class type) { + if (StringUtils.isEmpty(json)) { + return null; + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return null; + } + JavaType javaType = mapper.getTypeFactory().constructType(type); + return from(getAsString(jsonNode), javaType); + } catch (Exception e) { + throw new JacksonException( + StrUtil.format("jackson get list error, json: {}, key: {}, type: {}", json, key, type), e); + } + } + + + /** + * 从json串中获取某个字段 + * + * @return list, 默认为 null + */ + public static List getAsList(String json, String key, Class type) { + if (StringUtils.isEmpty(json)) { + return null; + } + try { + JsonNode jsonNode = getAsJsonObject(json, key); + if (null == jsonNode) { + return null; + } + CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type); + return from(getAsString(jsonNode), collectionType); + } catch (Exception e) { + throw new JacksonException( + StrUtil.format("jackson get list error, json: {}, key: {}, type: {}", json, key, type), e); + } + } + + /** + * 从json串中获取某个字段 + * + * @return JsonNode, 默认为 null + */ + public static JsonNode getAsJsonObject(String json, String key) { + try { + JsonNode node = mapper.readTree(json); + if (null == node) { + return null; + } + return node.get(key); + } catch (IOException e) { + throw new JacksonException( + StrUtil.format("jackson get object from json error, json: {}, key: {}", json, key), e); + } + } + + /** + * 向json中添加属性 + * + * @return json + */ + public static String add(String json, String key, V value) { + try { + JsonNode node = mapper.readTree(json); + add(node, key, value); + return node.toString(); + } catch (IOException e) { + throw new JacksonException( + StrUtil.format("jackson add error, json: {}, key: {}, value: {}", json, key, value), e); + } + } + + /** + * 向json中添加属性 + */ + private static void add(JsonNode jsonNode, String key, V value) { + if (value instanceof String) { + ((ObjectNode) jsonNode).put(key, (String) value); + } else if (value instanceof Short) { + ((ObjectNode) jsonNode).put(key, (Short) value); + } else if (value instanceof Integer) { + ((ObjectNode) jsonNode).put(key, (Integer) value); + } else if (value instanceof Long) { + ((ObjectNode) jsonNode).put(key, (Long) value); + } else if (value instanceof Float) { + ((ObjectNode) jsonNode).put(key, (Float) value); + } else if (value instanceof Double) { + ((ObjectNode) jsonNode).put(key, (Double) value); + } else if (value instanceof BigDecimal) { + ((ObjectNode) jsonNode).put(key, (BigDecimal) value); + } else if (value instanceof BigInteger) { + ((ObjectNode) jsonNode).put(key, (BigInteger) value); + } else if (value instanceof Boolean) { + ((ObjectNode) jsonNode).put(key, (Boolean) value); + } else if (value instanceof byte[]) { + ((ObjectNode) jsonNode).put(key, (byte[]) value); + } else { + ((ObjectNode) jsonNode).put(key, to(value)); + } + } + + /** + * 除去json中的某个属性 + * + * @return json + */ + public static String remove(String json, String key) { + try { + JsonNode node = mapper.readTree(json); + ((ObjectNode) node).remove(key); + return node.toString(); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson remove error, json: {}, key: {}", json, key), e); + } + } + + /** + * 修改json中的属性 + */ + public static String update(String json, String key, V value) { + try { + JsonNode node = mapper.readTree(json); + ((ObjectNode) node).remove(key); + add(node, key, value); + return node.toString(); + } catch (IOException e) { + throw new JacksonException( + StrUtil.format("jackson update error, json: {}, key: {}, value: {}", json, key, value), e); + } + } + + /** + * 格式化Json(美化) + * + * @return json + */ + public static String format(String json) { + try { + JsonNode node = mapper.readTree(json); + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node); + } catch (IOException e) { + throw new JacksonException(StrUtil.format("jackson format json error, json: {}", json), e); + } + } + + /** + * 判断字符串是否是json + * + * @return json + */ + public static boolean isJson(String json) { + try { + mapper.readTree(json); + return true; + } catch (Exception e) { + return false; + } + } + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/poi/CustomExcelUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/poi/CustomExcelUtil.java new file mode 100644 index 0000000..da12ecc --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/poi/CustomExcelUtil.java @@ -0,0 +1,86 @@ +package com.agileboot.common.utils.poi; + +import cn.hutool.poi.excel.ExcelReader; +import cn.hutool.poi.excel.ExcelUtil; +import cn.hutool.poi.excel.ExcelWriter; +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.common.annotation.ExcelSheet; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.web.multipart.MultipartFile; + +/** + * @author valarchie + */ +public class CustomExcelUtil { + + + public static void writeToResponse(List list, Class clazz, HttpServletResponse response) { + + // 通过工具类创建writer + ExcelWriter writer = ExcelUtil.getWriter(); + + ExcelSheet sheetAnno = (ExcelSheet)clazz.getAnnotation(ExcelSheet.class); + + if (sheetAnno != null) { + // 默认的sheetName是 sheet1 + writer.renameSheet(sheetAnno.name()); + } + + Field[] fields = clazz.getDeclaredFields(); + + //自定义标题别名 + for (Field field : fields) { + ExcelColumn annotation = field.getAnnotation(ExcelColumn.class); + if (annotation != null) { + writer.addHeaderAlias(field.getName(), annotation.name()); + } + } + + // 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之 + writer.setOnlyAlias(true); + + // 合并单元格后的标题行,使用默认标题样式 + // writer.merge(4, "一班成绩单"); + // 一次性写出内容,使用默认样式,强制输出标题 + writer.write(list, true); + + try { + writer.flush(response.getOutputStream(), true); + } catch (IOException e) { + writer.close(); + e.printStackTrace(); + } + + } + + public static List readFromResponse(Class clazz, MultipartFile file) { + + ExcelReader reader = null; + try { + reader = ExcelUtil.getReader(file.getInputStream()); + } catch (IOException e) { + e.printStackTrace(); + } + + + Field[] fields = clazz.getDeclaredFields(); + + //自定义标题别名 + if (fields != null) { + for (Field field : fields) { + ExcelColumn annotation = field.getAnnotation(ExcelColumn.class); + if (annotation != null) { + reader.addHeaderAlias(annotation.name(), field.getName()); + } + } + } + + return reader.read(0, 1, clazz); + } + + + +} diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/time/DatePicker.java b/agileboot-common/src/main/java/com/agileboot/common/utils/time/DatePicker.java new file mode 100644 index 0000000..6c25e2d --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/time/DatePicker.java @@ -0,0 +1,42 @@ +package com.agileboot.common.utils.time; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import java.util.Date; +import lombok.extern.slf4j.Slf4j; + +/** + * @author valarchie + */ +@Slf4j +public class DatePicker { + + public static Date getBeginOfTheDay(Object date) { + if (date == null) { + return null; + } + try { + DateTime parse = DateUtil.parse(date.toString()); + return DateUtil.beginOfDay(parse); + } catch (Exception e) { + log.error("pick begin of day failed, due to: ", e); + } + return null; + } + + public static Date getEndOfTheDay(Object date) { + if (date == null) { + return null; + } + try { + DateTime parse = DateUtil.parse(date.toString()); + return DateUtil.endOfDay(parse); + } catch (Exception e) { + log.error("pick end of day failed, due to: ", e); + } + return null; + } + + + +} diff --git a/agileboot-common/src/main/resources/ip2region.xdb b/agileboot-common/src/main/resources/ip2region.xdb new file mode 100644 index 0000000..31f96a1 Binary files /dev/null and b/agileboot-common/src/main/resources/ip2region.xdb differ diff --git a/agileboot-common/src/test/java/com/agileboot/common/core/exception/ApiExceptionTest.java b/agileboot-common/src/test/java/com/agileboot/common/core/exception/ApiExceptionTest.java new file mode 100644 index 0000000..cc265e6 --- /dev/null +++ b/agileboot-common/src/test/java/com/agileboot/common/core/exception/ApiExceptionTest.java @@ -0,0 +1,24 @@ +package com.agileboot.common.core.exception; + +import org.junit.Assert; +import org.junit.Test; + +public class ApiExceptionTest { + + + @Test + public void testVarargsWithArrayArgs() { + String errorMsg = "these parameters are null: %s, %s, %s."; + + Object[] array = new Object[] { "param1" , "param2" , "param3"}; + + String format1 = String.format(errorMsg, array); + String format2 = String.format(errorMsg, "param1", "param2", "param3"); + + System.out.println(format1); + System.out.println(format2); + + Assert.assertEquals(format1, format2); + } + +} diff --git a/agileboot-common/src/test/java/com/agileboot/common/utils/JacksonUtilTest.java b/agileboot-common/src/test/java/com/agileboot/common/utils/JacksonUtilTest.java new file mode 100644 index 0000000..2d7647c --- /dev/null +++ b/agileboot-common/src/test/java/com/agileboot/common/utils/JacksonUtilTest.java @@ -0,0 +1,62 @@ +package com.agileboot.common.utils; + +import cn.hutool.core.date.DateUtil; +import com.agileboot.common.utils.jackson.JacksonUtil; +import java.math.BigDecimal; +import java.math.BigInteger; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author duanxinyuan 2019/1/21 18:17 + */ +public class JacksonUtilTest { + + @Test + public void testObjectToJson() { + Person person = Person.newPerson(); + + String jacksonStr = JacksonUtil.to(person); + Assert.assertEquals(DateUtil.formatDateTime(person.getDate()), JacksonUtil.getAsString(jacksonStr, "date")); + Assert.assertEquals(DateUtil.formatLocalDateTime(person.getLocalDateTime()), + JacksonUtil.getAsString(jacksonStr, "localDateTime")); + Assert.assertEquals(person.getName(), JacksonUtil.getAsString(jacksonStr, "name")); + Assert.assertEquals(person.getAge(), JacksonUtil.getAsInt(jacksonStr, "age")); + Assert.assertEquals(person.isMan(), JacksonUtil.getAsBoolean(jacksonStr, "man")); + Assert.assertEquals(person.getMoney(), JacksonUtil.getAsBigDecimal(jacksonStr, "money")); + Assert.assertEquals(person.getTrait(), JacksonUtil.getAsList(jacksonStr, "trait", String.class)); + + Assert.assertNotNull(JacksonUtil.getAsString(jacksonStr, "name")); + } + + /** + * 测试兼容情况 + */ + @Test + public void testAllPrimitiveTypeToJson() { + String json = "{\n" + + "\"code\": \"200\",\n" + + "\"id\": \"2001215464647687987\",\n" + + "\"message\": \"success\",\n" + + "\"amount\": \"1.12345\",\n" + + "\"amount1\": \"0.12345\",\n" + + "\"isSuccess\": \"true\",\n" + + "\"isSuccess1\": \"1\",\n" + + "\"key\": \"8209167202090377654857374178856064487200234961995543450245362822537162918731039965956758726661669012305745755921310000297396309887550627402157318910581311\"\n" + + "}"; + Assert.assertEquals(200, JacksonUtil.getAsInt(json, "code")); + Assert.assertEquals(2001215464647687987L,JacksonUtil.getAsLong(json, "id")); + Assert.assertEquals("success", JacksonUtil.getAsString(json, "message")); + Assert.assertEquals(new BigDecimal("1.12345"), JacksonUtil.getAsBigDecimal(json, "amount")); + Assert.assertEquals(new BigDecimal("0.12345"), JacksonUtil.getAsBigDecimal(json, "amount1")); + Assert.assertEquals(1.12345d, JacksonUtil.getAsDouble(json, "amount"), 0.00001); + Assert.assertEquals(0.12345d, JacksonUtil.getAsDouble(json, "amount1"), 0.00001); + Assert.assertEquals(true, JacksonUtil.getAsBoolean(json, "isSuccess")); + Assert.assertEquals(true, JacksonUtil.getAsBoolean(json, "isSuccess1")); + Assert.assertEquals(new BigInteger( + "8209167202090377654857374178856064487200234961995543450245362822537162918731039965956758726661669012305745755921310000297396309887550627402157318910581311"), + JacksonUtil.getAsBigInteger(json, "key")); + Assert.assertEquals("1", JacksonUtil.getAsString(json, "isSuccess1")); + } + +} diff --git a/agileboot-common/src/test/java/com/agileboot/common/utils/Person.java b/agileboot-common/src/test/java/com/agileboot/common/utils/Person.java new file mode 100644 index 0000000..dc2b4d0 --- /dev/null +++ b/agileboot-common/src/test/java/com/agileboot/common/utils/Person.java @@ -0,0 +1,42 @@ +package com.agileboot.common.utils; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import lombok.Data; + +/** + * @author duanxinyuan + * 2018/6/29 14:17 + */ +@Data +public class Person { + public String name; + public Date date; + public LocalDateTime localDateTime; + public int age; + public BigDecimal money; + public boolean man; + public ArrayList trait; + public HashMap cards; + + public static Person newPerson() { + Person person = new Person(); + person.name = "张三"; + person.date = new Date(); + person.localDateTime = LocalDateTime.now(); + person.age = 100; + person.money = BigDecimal.valueOf(500.21); + person.man = true; + person.trait = new ArrayList<>(); + person.trait.add("淡然"); + person.trait.add("温和"); + person.cards = new HashMap<>(); + person.cards.put("身份证", "4a6d456as"); + person.cards.put("建行卡", "649874545"); + return person; + } + +} diff --git a/agileboot-common/src/test/java/com/agileboot/common/utils/file/FileUploadUtilsTest.java b/agileboot-common/src/test/java/com/agileboot/common/utils/file/FileUploadUtilsTest.java new file mode 100644 index 0000000..0f0d70f --- /dev/null +++ b/agileboot-common/src/test/java/com/agileboot/common/utils/file/FileUploadUtilsTest.java @@ -0,0 +1,16 @@ +package com.agileboot.common.utils.file; + +import org.junit.Assert; +import org.junit.Test; + +public class FileUploadUtilsTest { + + @Test + public void isAllowedExtension() { + String[] imageTypes = new String[]{"img", "gif"}; + boolean isAllow = FileUploadUtils.isAllowedExtension("img", imageTypes); + boolean isNotAllow = FileUploadUtils.isAllowedExtension("png", imageTypes); + Assert.assertTrue(isAllow); + Assert.assertFalse(isNotAllow); + } +} diff --git a/agileboot-common/src/test/java/com/agileboot/common/utils/ip/OfflineIpRegionUtilTest.java b/agileboot-common/src/test/java/com/agileboot/common/utils/ip/OfflineIpRegionUtilTest.java new file mode 100644 index 0000000..c67430d --- /dev/null +++ b/agileboot-common/src/test/java/com/agileboot/common/utils/ip/OfflineIpRegionUtilTest.java @@ -0,0 +1,16 @@ +package com.agileboot.common.utils.ip; + +import org.junit.Assert; +import org.junit.Test; + +public class OfflineIpRegionUtilTest { + + @Test + public void getIpRegion() { + IpRegion ipRegion = OfflineIpRegionUtil.getIpRegion("110.81.189.80"); + + Assert.assertEquals("中国", ipRegion.getCountry()); + Assert.assertEquals("福建省", ipRegion.getProvince()); + Assert.assertEquals("泉州市", ipRegion.getCity()); + } +} diff --git a/agileboot-domain/pom.xml b/agileboot-domain/pom.xml new file mode 100644 index 0000000..42f85a5 --- /dev/null +++ b/agileboot-domain/pom.xml @@ -0,0 +1,47 @@ + + + + agileboot + com.agileboot + 1.0.0 + + 4.0.0 + + agileboot-domain + + + 领域核心代码 放在这个包 + generator代码生成 + + + + + + + com.agileboot + agileboot-common + + + + + com.agileboot + agileboot-orm + + + + com.agileboot + agileboot-infrastructure + + + + + + + diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/common/BulkOperationCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/common/BulkOperationCommand.java new file mode 100644 index 0000000..f270bc2 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/common/BulkOperationCommand.java @@ -0,0 +1,22 @@ +package com.agileboot.domain.common; + +import cn.hutool.core.collection.CollUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import java.util.List; +import lombok.Data; + +@Data +public class BulkOperationCommand { + + public BulkOperationCommand(List idList) { + if (CollUtil.isEmpty(idList)) { + throw new ApiException(ErrorCode.Business.BULK_DELETE_IDS_IS_INVALID); + } + + this.ids = idList; + } + + private List ids; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/common/UploadFileDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/common/UploadFileDTO.java new file mode 100644 index 0000000..ab5efc5 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/common/UploadFileDTO.java @@ -0,0 +1,16 @@ +package com.agileboot.domain.common; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class UploadFileDTO { + + public UploadFileDTO(String imgUrl) { + this.imgUrl = imgUrl; + } + + private String imgUrl; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/TreeSelectedDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/TreeSelectedDTO.java new file mode 100644 index 0000000..2d73028 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/TreeSelectedDTO.java @@ -0,0 +1,16 @@ +package com.agileboot.domain.system; + +import cn.hutool.core.lang.tree.Tree; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class TreeSelectedDTO { + + private List checkedKeys; + private List> menus; + private List> depts; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigDTO.java new file mode 100644 index 0000000..7ea7cbf --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigDTO.java @@ -0,0 +1,43 @@ +package com.agileboot.domain.system.config; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.json.JSONUtil; +import com.agileboot.orm.entity.SysConfigEntity; +import com.agileboot.orm.enums.dictionary.CommonAnswerEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import java.util.Date; +import java.util.List; +import lombok.Data; + +@Data +public class ConfigDTO { + + public ConfigDTO(SysConfigEntity entity) { + + if (entity != null) { + configId = entity.getConfigId() + ""; + configName = entity.getConfigName(); + configKey = entity.getConfigKey(); + configValue = entity.getConfigValue(); + configOptions = + JSONUtil.isTypeJSONArray(entity.getConfigOptions()) ? JSONUtil.toList(entity.getConfigOptions(), + String.class) : ListUtil.empty(); + isAllowChange = Convert.toInt(entity.getIsAllowChange()) + ""; + isAllowChangeStr = BasicEnumUtil.getDescriptionByBool(CommonAnswerEnum.class, entity.getIsAllowChange()); + remark = entity.getRemark(); + createTime = entity.getCreateTime(); + } + } + + private String configId; + private String configName; + private String configKey; + private String configValue; + private List configOptions; + private String isAllowChange; + private String isAllowChangeStr; + private String remark; + private Date createTime; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigDomainService.java new file mode 100644 index 0000000..d49b9eb --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigDomainService.java @@ -0,0 +1,58 @@ +package com.agileboot.domain.system.config; + +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.orm.entity.SysConfigEntity; +import com.agileboot.orm.service.ISysConfigService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author valarchie + */ +@Service +public class ConfigDomainService { + + @Autowired + private ISysConfigService configService; + + public PageDTO getConfigList(ConfigQuery query) { + Page page = configService.page(query.toPage(), query.toQueryWrapper()); + List records = page.getRecords().stream().map(ConfigDTO::new).collect(Collectors.toList()); + return new PageDTO(records, page.getTotal()); + } + + + public ConfigDTO getConfigInfo(Long id) { + SysConfigEntity byId = configService.getById(id); + return new ConfigDTO(byId); + } + + @Transactional + public void updateConfig(ConfigUpdateCommand updateCommand, LoginUser loginUser) { + ConfigModel configModel = getConfigModel(updateCommand.getConfigId()); + + configModel.setConfigValue(updateCommand.getConfigValue()); + configModel.checkCanBeEdit(); + + configModel.logUpdater(loginUser); + configModel.updateById(); + } + + public ConfigModel getConfigModel(Long id) { + SysConfigEntity byId = configService.getById(id); + + if (byId == null) { + throw new ApiException(ErrorCode.Business.OBJECT_NOT_FOUND, id, "参数配置"); + } + + return new ConfigModel(byId); + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigModel.java new file mode 100644 index 0000000..7b46a6b --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigModel.java @@ -0,0 +1,42 @@ +package com.agileboot.domain.system.config; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.orm.entity.SysConfigEntity; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import lombok.Data; + +@Data +public class ConfigModel extends SysConfigEntity { + + private Set configOptionSet; + + public ConfigModel(SysConfigEntity entity) { + BeanUtil.copyProperties(entity, this); + + List options = + JSONUtil.isTypeJSONArray(entity.getConfigOptions()) ? JSONUtil.toList(entity.getConfigOptions(), + String.class) : ListUtil.empty(); + + this.configOptionSet = new HashSet<>(options); + } + + public void checkCanBeEdit() { + if (StrUtil.isBlank(getConfigValue())) { + throw new ApiException(ErrorCode.Business.CONFIG_VALUE_IS_NOT_ALLOW_TO_EMPTY); + } + + if(!configOptionSet.isEmpty()&& !configOptionSet.contains(getConfigValue())) { + throw new ApiException(ErrorCode.Business.CONFIG_VALUE_IS_NOT_IN_OPTIONS); + } + + } + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigQuery.java new file mode 100644 index 0000000..6b37f6b --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigQuery.java @@ -0,0 +1,34 @@ +package com.agileboot.domain.system.config; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysConfigEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class ConfigQuery extends AbstractPageQuery { + + private String configName; + + private String configKey; + + private Boolean isAllowChange; + + + @SuppressWarnings("rawtypes") + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper sysNoticeWrapper = new QueryWrapper<>(); + sysNoticeWrapper.like(StrUtil.isNotEmpty(configName), "config_name", configName); + sysNoticeWrapper.eq(StrUtil.isNotEmpty(configKey), "config_key", configKey); + sysNoticeWrapper.eq(isAllowChange != null, "is_allow_change", isAllowChange); + + addSortCondition(sysNoticeWrapper); + addTimeCondition(sysNoticeWrapper, "create_time"); + + return sysNoticeWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigUpdateCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigUpdateCommand.java new file mode 100644 index 0000000..41c3590 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigUpdateCommand.java @@ -0,0 +1,18 @@ +package com.agileboot.domain.system.config; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import lombok.Data; + +@Data +public class ConfigUpdateCommand { + + @NotNull + @Positive + private Long configId; + @NotNull + @NotEmpty + private String configValue; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/AddDeptCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/AddDeptCommand.java new file mode 100644 index 0000000..53cee53 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/AddDeptCommand.java @@ -0,0 +1,65 @@ +package com.agileboot.domain.system.dept; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.Data; + +@Data +public class AddDeptCommand { + /** + * 父部门ID + */ + private Long parentId; + + /** + * 祖级列表 + */ + private String ancestors; + + /** + * 部门名称 + */ + @NotBlank(message = "部门名称不能为空") + @Size(max = 30, message = "部门名称长度不能超过30个字符") + private String deptName; + + /** + * 显示顺序 + */ + @NotNull(message = "显示顺序不能为空") + private Integer orderNum; + + /** + * 负责人 + */ + private String leaderName; + + /** + * 联系电话 + */ + @Size(max = 11, message = "联系电话长度不能超过11个字符") + private String phone; + + /** + * 邮箱 + */ + @Email(message = "邮箱格式不正确") + @Size(max = 50, message = "邮箱长度不能超过50个字符") + private String email; + + public DeptModel toModel() { + DeptModel model = new DeptModel(); + + model.setParentId(parentId); + model.setAncestors(ancestors); + model.setDeptName(deptName); + model.setOrderNum(orderNum); + model.setLeaderName(leaderName); + model.setPhone(phone); + model.setEmail(email); + + return model; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptDTO.java new file mode 100644 index 0000000..c5538cf --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptDTO.java @@ -0,0 +1,44 @@ +package com.agileboot.domain.system.dept; + +import com.agileboot.orm.entity.SysDeptEntity; +import com.agileboot.orm.enums.dictionary.CommonStatusEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import lombok.Data; + +@Data +public class DeptDTO { + + public DeptDTO(SysDeptEntity entity) { + if (entity != null) { + this.deptId = entity.getDeptId(); + this.parentId = entity.getParentId(); + this.deptName = entity.getDeptName(); + this.orderNum = entity.getOrderNum(); + this.leaderName = entity.getLeaderName(); + this.email = entity.getEmail(); + this.phone = entity.getPhone(); + this.status = entity.getStatus() + ""; + this.statusStr = BasicEnumUtil.getDescriptionByValue(CommonStatusEnum.class, entity.getStatus()); + } + } + + + private Long deptId; + + private Long parentId; + + private String deptName; + + private Integer orderNum; + + private String leaderName; + + private String phone; + + private String email; + + private String status; + + private String statusStr; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptDomainService.java new file mode 100644 index 0000000..b1458e8 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptDomainService.java @@ -0,0 +1,126 @@ +package com.agileboot.domain.system.dept; + +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.system.TreeSelectedDTO; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.orm.entity.SysDeptEntity; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.service.ISysDeptService; +import com.agileboot.orm.service.ISysRoleService; +import com.agileboot.orm.service.ISysUserService; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author valarchie + */ +@SuppressWarnings("AlibabaTransactionMustHaveRollback") +@Service +public class DeptDomainService { + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysUserService userService; + + public List getDeptList(DeptQuery query) { + List list = deptService.list(query.toQueryWrapper()); + return list.stream().map(DeptDTO::new).collect(Collectors.toList()); + } + + public DeptDTO getDeptInfo(Long id) { + SysDeptEntity byId = deptService.getById(id); + return new DeptDTO(byId); + } + + public List> getDeptTree() { + List list = deptService.list(); + + return TreeUtil.build(list, 0L, (dept, tree) -> { + tree.setId(dept.getDeptId()); + tree.setParentId(dept.getParentId()); + tree.putExtra("label", dept.getDeptName()); + }); + } + + public TreeSelectedDTO getDeptTreeForRole(Long roleId) { + List checkedKeys = new ArrayList<>(); + SysRoleEntity role = roleService.getById(roleId); + if (role != null && StrUtil.isNotEmpty(role.getDeptIdSet())) { + checkedKeys = StrUtil.split(role.getDeptIdSet(), ",") + .stream().map(Long::new).collect(Collectors.toList()); + } + + TreeSelectedDTO selectedDTO = new TreeSelectedDTO(); + selectedDTO.setDepts(getDeptTree()); + selectedDTO.setCheckedKeys(checkedKeys); + + return selectedDTO; + } + + + @Transactional + public void addDept(AddDeptCommand addCommand, LoginUser loginUser) { + DeptModel deptModel = addCommand.toModel(); + if (deptService.checkDeptNameUnique(deptModel.getDeptName(), null, deptModel.getParentId())) { + throw new ApiException(ErrorCode.Business.DEPT_NAME_IS_NOT_UNIQUE, deptModel.getDeptName()); + } + + deptModel.generateAncestors(deptService); + deptModel.logCreator(loginUser); + + deptModel.insert(); + } + + @Transactional + public void updateDept(UpdateDeptCommand updateCommand, LoginUser loginUser) { + // TODO 需要再调整一下 + getDeptModel(updateCommand.getDeptId()); + + DeptModel deptModel = updateCommand.toModel(); + if (deptService.checkDeptNameUnique(deptModel.getDeptName(), deptModel.getDeptId(), deptModel.getParentId())) { + throw new ApiException(ErrorCode.Business.DEPT_NAME_IS_NOT_UNIQUE, deptModel.getDeptName()); + } + + deptModel.checkParentId(); + deptModel.checkStatusAllowChange(deptService); + deptModel.generateAncestors(deptService); + + deptModel.logUpdater(loginUser); + + deptModel.updateById(); + } + + @Transactional + public void removeDept(Long deptId) { + DeptModel deptModel = getDeptModel(deptId); + + deptModel.checkExistChildDept(deptService); + deptModel.checkExistLinkedUsers(userService); + + deptService.removeById(deptId); + } + + public DeptModel getDeptModel(Long id) { + SysDeptEntity byId = deptService.getById(id); + + if (byId == null) { + throw new ApiException(ErrorCode.Business.OBJECT_NOT_FOUND, id, "参数配置"); + } + + return new DeptModel(byId); + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptModel.java new file mode 100644 index 0000000..2c8a8e1 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptModel.java @@ -0,0 +1,67 @@ +package com.agileboot.domain.system.dept; + +import cn.hutool.core.bean.BeanUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.orm.entity.SysDeptEntity; +import com.agileboot.orm.enums.dictionary.CommonStatusEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import com.agileboot.orm.service.ISysDeptService; +import com.agileboot.orm.service.ISysUserService; +import java.util.Objects; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class DeptModel extends SysDeptEntity { + + public DeptModel(SysDeptEntity entity) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + } + } + + + public void checkParentId() { + if (Objects.equals(getParentId(), getDeptId())) { + throw new ApiException(ErrorCode.Business.DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF); + } + } + + + public void checkExistChildDept(ISysDeptService deptService) { + if (deptService.hasChildDeptById(getDeptId())) { + throw new ApiException(ErrorCode.Business.DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE); + } + } + + public void checkExistLinkedUsers(ISysUserService userService) { + if (userService.checkDeptExistUser(getDeptId())) { + throw new ApiException(ErrorCode.Business.DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE); + } + } + + public void generateAncestors(ISysDeptService deptService) { + SysDeptEntity parentDept = deptService.getById(getParentId()); + + if (parentDept == null || CommonStatusEnum.DISABLE.equals( + BasicEnumUtil.fromValue(CommonStatusEnum.class, parentDept.getStatus()))) { + throw new ApiException(ErrorCode.Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED); + } + + setAncestors(parentDept.getAncestors() + "," + getParentId()); + } + + + /** + * DDD 有些阻抗 如果为了追求性能的话 还是得通过 数据库的方式来判断 + * @param deptService + */ + public void checkStatusAllowChange(ISysDeptService deptService) { + if (CommonStatusEnum.DISABLE.getValue().equals(getStatus()) && + deptService.existChildrenDeptById(getDeptId(), true)) { + throw new ApiException(ErrorCode.Business.DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE); + } + + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptQuery.java new file mode 100644 index 0000000..a495114 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptQuery.java @@ -0,0 +1,39 @@ +package com.agileboot.domain.system.dept; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysDeptEntity; +import com.agileboot.orm.query.AbstractQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class DeptQuery extends AbstractQuery { + + private Long deptId; + + private Integer status; + + private Long parentId; + + private String deptName; + + private boolean isExcludeCurrentDept; + + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + queryWrapper.eq(status != null, "status", status) + .eq(parentId != null, "parent_id", parentId) + .like(StrUtil.isNotEmpty(deptName), "dept_name", deptName) + .and(deptId != null && isExcludeCurrentDept, o -> + o.ne("dept_id", deptId) + .or() + .apply("FIND_IN_SET ( dept_id , ancestors)") + ); + + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/UpdateDeptCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/UpdateDeptCommand.java new file mode 100644 index 0000000..be5785e --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/dept/UpdateDeptCommand.java @@ -0,0 +1,26 @@ +package com.agileboot.domain.system.dept; + +import cn.hutool.core.convert.Convert; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.PositiveOrZero; +import lombok.Data; + +@Data +public class UpdateDeptCommand extends AddDeptCommand{ + + @NotNull + @PositiveOrZero + private Long deptId; + + @PositiveOrZero + private String status; + + @Override + public DeptModel toModel() { + DeptModel deptModel = super.toModel(); + deptModel.setDeptId(this.deptId); + deptModel.setStatus(Convert.toInt(status, 0)); + return deptModel; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoDTO.java new file mode 100644 index 0000000..f566893 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoDTO.java @@ -0,0 +1,60 @@ +package com.agileboot.domain.system.loginInfo; + +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.common.annotation.ExcelSheet; +import com.agileboot.orm.entity.SysLoginInfoEntity; +import com.agileboot.orm.enums.LoginStatusEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import java.util.Date; +import lombok.Data; + +@Data +@ExcelSheet(name = "登录日志") +public class LoginInfoDTO { + + public LoginInfoDTO(SysLoginInfoEntity entity) { + if (entity != null) { + infoId = entity.getInfoId() + ""; + username = entity.getUsername(); + ipAddress = entity.getIpAddress(); + loginLocation = entity.getLoginLocation(); + operationSystem = entity.getOperationSystem(); + browser = entity.getBrowser(); + status = entity.getStatus() + ""; + statusStr = BasicEnumUtil.getDescriptionByValue(LoginStatusEnum.class, entity.getStatus()); + msg = entity.getMsg(); + loginTime = entity.getLoginTime(); + } + } + + + @ExcelColumn(name = "ID") + private String infoId; + + @ExcelColumn(name = "用户名") + private String username; + + @ExcelColumn(name = "ip地址") + private String ipAddress; + + @ExcelColumn(name = "登录地点") + private String loginLocation; + + @ExcelColumn(name = "操作系统") + private String operationSystem; + + @ExcelColumn(name = "浏览器") + private String browser; + + private String status; + + @ExcelColumn(name = "状态") + private String statusStr; + + @ExcelColumn(name = "描述") + private String msg; + + @ExcelColumn(name = "登录时间") + private Date loginTime; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoDomainService.java new file mode 100644 index 0000000..201e04a --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoDomainService.java @@ -0,0 +1,35 @@ +package com.agileboot.domain.system.loginInfo; + +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.orm.entity.SysLoginInfoEntity; +import com.agileboot.orm.service.ISysLoginInfoService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author valarchie + */ +@Service +public class LoginInfoDomainService { + + @Autowired + private ISysLoginInfoService loginInfoService; + + public PageDTO getLoginInfoList(LoginInfoQuery query) { + Page page = loginInfoService.page(query.toPage(), query.toQueryWrapper()); + List records = page.getRecords().stream().map(LoginInfoDTO::new).collect(Collectors.toList()); + return new PageDTO(records, page.getTotal()); + } + + public void deleteLoginInfo(BulkOperationCommand deleteCommand) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("info_id", deleteCommand.getIds()); + loginInfoService.remove(queryWrapper); + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoQuery.java new file mode 100644 index 0000000..d48d1a4 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoQuery.java @@ -0,0 +1,32 @@ +package com.agileboot.domain.system.loginInfo; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysLoginInfoEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class LoginInfoQuery extends AbstractPageQuery { + + private String ipaddr; + private String status; + private String username; + + + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + queryWrapper.like(StrUtil.isNotEmpty(ipaddr), "ip_address", ipaddr) + .eq(StrUtil.isNotEmpty(status), "status", status) + .like(StrUtil.isNotEmpty(username), "username", username); + + addSortCondition(queryWrapper); + addTimeCondition(queryWrapper, "login_time"); + + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/SearchUserQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/SearchUserQuery.java new file mode 100644 index 0000000..969e78c --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/SearchUserQuery.java @@ -0,0 +1,37 @@ +package com.agileboot.domain.system.loginInfo; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.query.AbstractPageQuery; +import com.agileboot.orm.result.SearchUserDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; + +@Data +public class SearchUserQuery extends AbstractPageQuery { + + private Long userId; + private String username; + private Integer status; + private String phoneNumber; + private Long deptId; + + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + queryWrapper.like(StrUtil.isNotEmpty(username), "username", username) + .like(StrUtil.isNotEmpty(phoneNumber), "u.phone_number", phoneNumber) + .eq(userId != null, "u.user_id", userId) + .eq(status != null, "u.status", status) + .eq("u.deleted", 0) + .and(deptId != null, o -> + o.eq("u.dept_id", deptId) + .or() + .apply("u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(" + deptId + + ", ancestors))")); + + this.addTimeCondition(queryWrapper, "u.create_time"); + + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/AddMenuCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/AddMenuCommand.java new file mode 100644 index 0000000..ad6870f --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/AddMenuCommand.java @@ -0,0 +1,50 @@ +package com.agileboot.domain.system.menu; + +import cn.hutool.core.bean.BeanUtil; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.Data; + +@Data +public class AddMenuCommand { + + @NotBlank(message = "菜单名称不能为空") + @Size(max = 50, message = "菜单名称长度不能超过50个字符") + private String menuName; + + private Long parentId; + + @NotNull(message = "显示顺序不能为空") + private Integer orderNum; + + @Size(max = 200, message = "路由地址不能超过200个字符") + private String path; + + @Size(max = 200, message = "组件路径不能超过255个字符") + private String component; + + private Integer isExternal; + + private Integer isCache; + + private Integer menuType; + + private Integer isVisible; + + private Integer status; + + private String query; + + @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") + private String perms; + + private String icon; + + public MenuModel toModel() { + MenuModel model = new MenuModel(); + BeanUtil.copyProperties(this, model); + return model; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuDTO.java new file mode 100644 index 0000000..b040897 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuDTO.java @@ -0,0 +1,51 @@ +package com.agileboot.domain.system.menu; + +import cn.hutool.core.util.BooleanUtil; +import com.agileboot.orm.entity.SysMenuEntity; +import com.agileboot.orm.enums.dictionary.CommonStatusEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import java.util.Date; +import lombok.Data; + +@Data +public class MenuDTO { + + public MenuDTO(SysMenuEntity entity) { + if (entity != null) { + this.menuId = entity.getMenuId(); + this.parentId = entity.getParentId(); + this.menuName = entity.getMenuName(); + this.menuType = entity.getMenuType() + ""; + this.icon = entity.getIcon(); + this.orderNum = entity.getOrderNum() + ""; + this.component = entity.getComponent(); + this.perms = entity.getPerms(); + this.path = entity.getPath(); + this.status = entity.getStatus() + ""; + this.statusStr = BasicEnumUtil.getDescriptionByValue(CommonStatusEnum.class, entity.getStatus()); + this.createTime = entity.getCreateTime(); + this.isExternal = BooleanUtil.toInt(entity.getIsExternal()) + ""; + this.isCache = BooleanUtil.toInt(entity.getIsCache()) + ""; + this.isVisible = BooleanUtil.toInt(entity.getIsVisible()) + ""; + this.query = entity.getQuery(); + } + } + + private Long menuId; + private Long parentId; + private String menuType; + private String menuName; + private String icon; + private String orderNum; + private String component; + private String path; + private String perms; + private String status; + private String statusStr; + private Date createTime; + private String isExternal; + private String isCache; + private String isVisible; + private String query; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuDomainService.java new file mode 100644 index 0000000..b778b01 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuDomainService.java @@ -0,0 +1,190 @@ +package com.agileboot.domain.system.menu; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNodeConfig; +import cn.hutool.core.lang.tree.TreeUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.system.TreeSelectedDTO; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.entity.SysMenuEntity; +import com.agileboot.orm.service.ISysMenuService; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author valarchie + */ +@Service +public class MenuDomainService { + + @Autowired + private ISysMenuService menuService; + + + public List getMenuList(MenuQuery query) { + List list = menuService.list(query.toQueryWrapper()); + return list.stream().map(MenuDTO::new).collect(Collectors.toList()); + } + + public MenuDTO getMenuInfo(Long menuId) { + SysMenuEntity byId = menuService.getById(menuId); + return new MenuDTO(byId); + } + + public List> getDropdownList(LoginUser loginUser) { + List menuEntityList = + loginUser.isAdmin() ? menuService.list() : menuService.selectMenuListByUserId(loginUser.getUserId()); + + return buildMenuTreeSelect(menuEntityList); + } + + + public TreeSelectedDTO getRoleDropdownList(LoginUser loginUser, Long roleId) { + List menus = loginUser.isAdmin() ? + menuService.list() : menuService.selectMenuListByUserId(loginUser.getUserId()); + + TreeSelectedDTO tree = new TreeSelectedDTO(); + tree.setMenus(buildMenuTreeSelect(menus)); + tree.setCheckedKeys(menuService.selectMenuListByRoleId(roleId)); + + return tree; + } + + + public void addMenu(AddMenuCommand addCommand, LoginUser loginUser) { + MenuModel model = addCommand.toModel(); + + model.checkMenuNameUnique(menuService); + model.checkExternalLink(); + + model.logCreator(loginUser); + + model.insert(); + } + + public void updateMenu(UpdateMenuCommand updateCommand, LoginUser loginUser) { + MenuModel model = updateCommand.toModel(); + model.checkMenuNameUnique(menuService); + model.checkExternalLink(); + model.checkParentId(); + + model.logUpdater(loginUser); + + model.updateById(); + } + + + public void remove(Long menuId) { + MenuModel menuModel = getMenuModel(menuId); + + menuModel.checkHasChildMenus(menuService); + menuModel.checkMenuAlreadyAssignToRole(menuService); + + menuModel.deleteById(); + } + + + public MenuModel getMenuModel(Long menuId) { + SysMenuEntity byId = menuService.getById(menuId); + if (byId == null) { + throw new ApiException(ErrorCode.Business.OBJECT_NOT_FOUND, menuId, "菜单"); + } + return new MenuModel(byId); + } + + + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + public List> buildMenuTreeSelect(List menus) { + TreeNodeConfig config = new TreeNodeConfig(); + //默认为id可以不设置 + config.setIdKey("menuId"); + return TreeUtil.build(menus, 0L, config, (menu, tree) -> { + // 也可以使用 tree.setId(dept.getId());等一些默认值 + tree.setId(menu.getMenuId()); + tree.setParentId(menu.getParentId()); + tree.putExtra("label", menu.getMenuName()); + }); + } + + + public List> buildMenuEntityTree(Long userId) { + + List menus = null; + if (AuthenticationUtils.isAdmin(userId)) { + menus = menuService.listMenuListWithoutButton(); + } else { + menus = menuService.listMenuListWithoutButtonByUserId(userId); + } + + TreeNodeConfig config = new TreeNodeConfig(); + //默认为id可以不设置 + config.setIdKey("menuId"); + + return TreeUtil.build(menus, 0L, config, (menu, tree) -> { + // 也可以使用 tree.setId(dept.getId());等一些默认值 + tree.setId(menu.getMenuId()); + tree.setParentId(menu.getParentId()); + tree.putExtra("entity", menu); + }); + + } + + + public List buildRouterTree(List> trees) { + List routers = new LinkedList(); + if (CollUtil.isNotEmpty(trees)) { + for (Tree tree : trees) { + RouterVo routerVo = null; + + Object entity = tree.get("entity"); + + if (entity != null) { + RouterModel model = new RouterModel(); + BeanUtil.copyProperties(entity, model, true); + + routerVo = model.produceDefaultRouterVO(); + + if(model.isMultipleLevelMenu(tree)) { + routerVo = model.produceDirectoryRouterVO(buildRouterTree(tree.getChildren())); + } + + if(model.isSingleLevelMenu()) { + routerVo = model.produceMenuFrameRouterVO(); + } + + if(model.getParentId() == 0L && model.isInnerLink()) { + routerVo = model.produceInnerLinkRouterVO(); + } + + routers.add(routerVo); + } + + } + } + + return routers; + } + + + public List getRouterTree(Long userId) { + List> trees = buildMenuEntityTree(userId); + return buildRouterTree(trees); + } + + + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuModel.java new file mode 100644 index 0000000..13c22e3 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuModel.java @@ -0,0 +1,54 @@ +package com.agileboot.domain.system.menu; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.http.HttpUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.orm.entity.SysMenuEntity; +import com.agileboot.orm.service.ISysMenuService; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class MenuModel extends SysMenuEntity { + + public MenuModel(SysMenuEntity entity) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + } + } + + + public void checkMenuNameUnique(ISysMenuService menuService) { + if (menuService.checkMenuNameUnique(getMenuName(), getMenuId(), getParentId())) { + throw new ApiException(ErrorCode.Business.MENU_NAME_IS_NOT_UNIQUE); + } + } + + + public void checkExternalLink() { + if (getIsExternal() && !HttpUtil.isHttp(getPath()) && !HttpUtil.isHttps(getPath())) { + throw new ApiException(ErrorCode.Business.MENU_EXTERNAL_LINK_MUST_BE_HTTP); + } + } + + + public void checkParentId() { + if (getMenuId().equals(getParentId())) { + throw new ApiException(ErrorCode.Business.MENU_PARENT_ID_NOT_ALLOW_SELF); + } + } + + public void checkHasChildMenus(ISysMenuService menuService) { + if (menuService.hasChildByMenuId(getMenuId())) { + throw new ApiException(ErrorCode.Business.MENU_EXIST_CHILD_MENU_NOT_ALLOW_DELETE); + } + } + + public void checkMenuAlreadyAssignToRole(ISysMenuService menuService) { + if (menuService.checkMenuExistRole(getMenuId())) { + throw new ApiException(ErrorCode.Business.MENU_ALREADY_ASSIGN_TO_ROLE_NOT_ALLOW_DELETE); + } + } + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuQuery.java new file mode 100644 index 0000000..2e78ff3 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuQuery.java @@ -0,0 +1,29 @@ +package com.agileboot.domain.system.menu; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysMenuEntity; +import com.agileboot.orm.query.AbstractQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import java.util.Arrays; +import lombok.Data; + +@Data +public class MenuQuery extends AbstractQuery { + + private String menuName; + private Boolean isVisible; + private Integer status; + + + @Override + public QueryWrapper toQueryWrapper() { + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StrUtil.isNotEmpty(menuName), columnName("menu_name"), menuName) + .eq(isVisible!=null, "is_visible", isVisible) + .eq(status!=null, "status", status); + + queryWrapper.orderBy(true, true, Arrays.asList("parent_id", "order_num")); + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MetaVo.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MetaVo.java new file mode 100644 index 0000000..a53a2a6 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MetaVo.java @@ -0,0 +1,59 @@ +package com.agileboot.domain.system.menu; + +import cn.hutool.http.HttpUtil; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 路由显示信息 + * + * @author ruoyi + */ +@Data +@NoArgsConstructor +public class MetaVo { + + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + private String link; + + public MetaVo(String title, String icon) { + this.title = title; + this.icon = icon; + } + + + public MetaVo(String title, String icon, String link) { + this.title = title; + this.icon = icon; + if (HttpUtil.isHttp(link)) { + this.link = link; + } + } + + public MetaVo(String title, String icon, boolean noCache, String link) { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (HttpUtil.isHttp(link)) { + this.link = link; + } + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/RouterModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/RouterModel.java new file mode 100644 index 0000000..c50498a --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/RouterModel.java @@ -0,0 +1,197 @@ +package com.agileboot.domain.system.menu; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.agileboot.common.constant.Constants; +import com.agileboot.orm.entity.SysMenuEntity; +import com.agileboot.orm.enums.MenuComponentEnum; +import com.agileboot.orm.enums.MenuTypeEnum; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class RouterModel extends SysMenuEntity { + + + public RouterVo produceDirectoryRouterVO(List children) { + + RouterVo router = produceDefaultRouterVO(); + + if (CollUtil.isNotEmpty(children) && Objects.equals(MenuTypeEnum.DIRECTORY.getValue(), getMenuType())) { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(children); + } + + return router; + } + + + public RouterVo produceMenuFrameRouterVO() { + RouterVo router = new RouterVo(); + + router.setMeta(null); + List childrenList = new ArrayList<>(); + RouterVo children = new RouterVo(); + children.setPath(getPath()); + children.setComponent(getComponent()); + children.setName(StrUtil.upperFirst(getPath())); + children.setMeta(new MetaVo(getMenuName(), getIcon(), !getIsCache(), getPath())); + children.setQuery(getQuery()); + childrenList.add(children); + router.setChildren(childrenList); + + return router; + } + + + public RouterVo produceInnerLinkRouterVO() { + + RouterVo router = new RouterVo(); + + router.setMeta(new MetaVo(getMenuName(), getIcon())); + router.setPath("/"); + List childrenList = new ArrayList<>(); + RouterVo children = new RouterVo(); + String routerPath = trimHttpPrefixForInnerLink(getPath()); + children.setPath(routerPath); + children.setComponent(MenuComponentEnum.INNER_LINK.description()); + children.setName(StrUtil.upperFirst(routerPath)); + children.setMeta(new MetaVo(getMenuName(), getIcon(), getPath())); + childrenList.add(children); + router.setChildren(childrenList); + + return router; + } + + public RouterVo produceDefaultRouterVO() { + RouterVo router = new RouterVo(); + router.setHidden(!getIsVisible()); + router.setName(getRouteName()); + router.setPath(getRouterPath()); + router.setComponent(getComponentTypeForFrontEnd()); + router.setQuery(getQuery()); + router.setMeta(new MetaVo(getMenuName(), getIcon(), !getIsCache(), getPath())); + return router; + } + + + /** + * 获取路由名称 + * @return 路由名称 + */ + public String getRouteName() { + String routerName = StrUtil.upperFirst(getPath()); + // 非外链并且是一级目录(类型为目录) + if (isSingleLevelMenu()) { + routerName = StrUtil.EMPTY; + } + return routerName; + } + + + /** + * 是否为单个一级菜单 + * + * @return 结果 + */ + public boolean isSingleLevelMenu() { + return getParentId().intValue() == 0 + && MenuTypeEnum.MENU.getValue().equals(getMenuType()) + && !getIsExternal(); + } + + + /** + * 是否为菜单内部跳转 + * + * @return 结果 + */ + public boolean isMultipleLevelMenu(Tree tree) { + return MenuTypeEnum.DIRECTORY.getValue().equals(getMenuType()) && tree.hasChild(); + } + + + /** + * 获取路由地址 + * @return 路由地址 + */ + public String getRouterPath() { + String routerPath = getPath(); + // 内链打开外网方式 + if (getParentId().intValue() != 0 && isInnerLink()) { + routerPath = trimHttpPrefixForInnerLink(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (0L == getParentId() && Objects.equals(MenuTypeEnum.DIRECTORY.getValue(), getMenuType()) && !getIsExternal()) { + routerPath = "/" + getPath(); + // 非外链并且是一级目录(类型为菜单) + } else if (isSingleLevelMenu()) { + routerPath = "/"; + } + return routerPath; + } + + /** + * 是否为内链组件 + * + * @return 结果 + */ + public boolean isInnerLink() { + return !getIsExternal() && (HttpUtil.isHttp(getPath()) || HttpUtil.isHttps(getPath())); + } + + + /** + * 内链域名特殊字符替换 + */ + public String trimHttpPrefixForInnerLink(String path) { + if (HttpUtil.isHttp(path)) { + return StrUtil.stripIgnoreCase(path, Constants.HTTP, ""); + } + if (HttpUtil.isHttps(path)) { + return StrUtil.stripIgnoreCase(path, Constants.HTTPS, ""); + } + return path; + } + + /** + * 获取组件信息 + * + * @return 组件信息 + */ + public String getComponentTypeForFrontEnd() { + String component = MenuComponentEnum.LAYOUT.description(); + if (StrUtil.isNotEmpty(getComponent()) && !isSingleLevelMenu()) { + component = getComponent(); + } else if (isInnerLinkView()) { + component = MenuComponentEnum.INNER_LINK.description(); + } else if (isParentView()) { + component = MenuComponentEnum.PARENT_VIEW.description(); + } + return component; + } + + /** + * 是否为inner_link_view组件 + * + * @return 结果 + */ + public boolean isInnerLinkView() { + return StrUtil.isEmpty(getComponent()) && getParentId().intValue() != 0 && isInnerLink(); + } + + + /** + * 是否为parent_view组件 + * + * @return 结果 + */ + public boolean isParentView() { + return StrUtil.isEmpty(getComponent()) && getParentId().intValue() != 0 && MenuTypeEnum.DIRECTORY.getValue() == getMenuType(); + } + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/RouterVo.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/RouterVo.java new file mode 100644 index 0000000..66d6a6e --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/RouterVo.java @@ -0,0 +1,61 @@ +package com.agileboot.domain.system.menu; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; +import lombok.Data; + +/** + * 路由配置信息 + * + * @author ruoyi + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@Data +public class RouterVo { + + /** + * 路由名字 + */ + private String name; + + /** + * 路由地址 + */ + private String path; + + /** + * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 + */ + private boolean hidden; + + /** + * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + */ + private String redirect; + + /** + * 组件地址 + */ + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "agileBoot"} + */ + private String query; + + /** + * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + */ + private Boolean alwaysShow; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List children; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/UpdateMenuCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/UpdateMenuCommand.java new file mode 100644 index 0000000..36dcb2b --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/UpdateMenuCommand.java @@ -0,0 +1,19 @@ +package com.agileboot.domain.system.menu; + +import javax.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class UpdateMenuCommand extends AddMenuCommand{ + + @NotNull + private Long menuId; + + @Override + public MenuModel toModel() { + MenuModel model = super.toModel(); + model.setMenuId(menuId); + return model; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorDomainService.java new file mode 100644 index 0000000..916d2ef --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorDomainService.java @@ -0,0 +1,124 @@ +package com.agileboot.domain.system.monitor; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO; +import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO.CommonStatusDTO; +import com.agileboot.infrastructure.cache.RedisUtil; +import com.agileboot.infrastructure.cache.guava.GuavaCacheService; +import com.agileboot.infrastructure.cache.redis.CacheKeyEnum; +import com.agileboot.infrastructure.cache.redis.RedisCacheService; +import com.agileboot.infrastructure.web.domain.OnlineUser; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.domain.server.ServerInfo; +import com.agileboot.orm.entity.SysDeptEntity; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisServerCommands; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +public class MonitorDomainService { + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private RedisUtil redisUtil; + + @Autowired + private RedisCacheService redisCacheService; + + + public RedisCacheInfoDTO getRedisCacheInfo() { + + Properties info = (Properties) redisTemplate.execute((RedisCallback) RedisServerCommands::info); + Properties commandStats = (Properties) redisTemplate.execute( + (RedisCallback) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) RedisServerCommands::dbSize); + + if(commandStats == null) { + throw new RuntimeException("找不到对应的redis信息。"); + } + + RedisCacheInfoDTO cacheInfo = new RedisCacheInfoDTO(); + + cacheInfo.setInfo(info); + cacheInfo.setDbSize(dbSize); + cacheInfo.setCommandStats(new ArrayList<>()); + + commandStats.stringPropertyNames().forEach(key -> { + String property = commandStats.getProperty(key); + + RedisCacheInfoDTO.CommonStatusDTO commonStatus = new CommonStatusDTO(); + commonStatus.setName(StrUtil.removePrefix(key, "cmdstat_")); + commonStatus.setValue(StrUtil.subBetween(property, "calls=", ",usec")); + + cacheInfo.getCommandStats().add(commonStatus); + }); + + return cacheInfo; + } + + public List getOnlineUserList(String userName, String ipaddr) { + Collection keys = redisUtil.keys(CacheKeyEnum.LOGIN_USER_KEY.key() + "*"); + + List allOnlineUsers = keys.stream().map( + o -> mapLoginUserToUserOnline(redisCacheService.loginUserCache.getCachedObjectByKey(o))) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + List filteredOnlineUsers = allOnlineUsers.stream() + .filter(o -> + StrUtil.isEmpty(userName) || userName.equals(o.getUserName()) + ).filter( o -> + StrUtil.isEmpty(ipaddr) || ipaddr.equals(o.getIpaddr()) + ).collect(Collectors.toList()); + + Collections.reverse(filteredOnlineUsers); + return filteredOnlineUsers; + } + + public ServerInfo getServerInfo() { + return ServerInfo.fillInfo(); + } + + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + public OnlineUser mapLoginUserToUserOnline(LoginUser user) { + if (user == null) { + return null; + } + OnlineUser onlineUser = new OnlineUser(); + onlineUser.setTokenId(user.getToken()); + onlineUser.setUserName(user.getUsername()); + onlineUser.setIpaddr(user.getLoginInfo().getIpAddress()); + onlineUser.setLoginLocation(user.getLoginInfo().getLocation()); + onlineUser.setBrowser(user.getLoginInfo().getBrowser()); + onlineUser.setOs(user.getLoginInfo().getOperationSystem()); + onlineUser.setLoginTime(user.getLoginTime()); + + GuavaCacheService cacheService = SpringUtil.getBean(GuavaCacheService.class); + + SysDeptEntity deptEntity = cacheService.deptCache.get(user.getDeptId() + ""); + + if (deptEntity != null) { + onlineUser.setDeptName(deptEntity.getDeptName()); + } + + return onlineUser; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/RedisCacheInfoDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/RedisCacheInfoDTO.java new file mode 100644 index 0000000..b98db7a --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/RedisCacheInfoDTO.java @@ -0,0 +1,24 @@ +package com.agileboot.domain.system.monitor.dto; + +import java.util.List; +import java.util.Properties; +import lombok.Data; + + +/** + * @author valarchie + */ +@Data +public class RedisCacheInfoDTO { + + private Properties info; + private Object dbSize; + private List commandStats; + + @Data + public static class CommonStatusDTO { + private String name; + private String value; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeAddCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeAddCommand.java new file mode 100644 index 0000000..922a466 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeAddCommand.java @@ -0,0 +1,33 @@ +package com.agileboot.domain.system.notice; + +import cn.hutool.core.convert.Convert; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import lombok.Data; + +@Data +public class NoticeAddCommand { + + @NotBlank(message = "公告标题不能为空") + @Size(max = 50, message = "公告标题不能超过50个字符") + protected String noticeTitle; + + protected String noticeType; + + @NotBlank + protected String noticeContent; + + protected String status; + + public NoticeModel toModel() { + NoticeModel model = new NoticeModel(); + + model.setNoticeTitle(noticeTitle); + model.setNoticeType(Convert.toInt(noticeType)); + model.setNoticeContent(noticeContent); + model.setStatus(Convert.toInt(status)); + + return model; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeDTO.java new file mode 100644 index 0000000..0a6cd49 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeDTO.java @@ -0,0 +1,36 @@ +package com.agileboot.domain.system.notice; + +import com.agileboot.orm.entity.SysNoticeEntity; +import java.util.Date; +import lombok.Data; + +@Data +public class NoticeDTO { + + public NoticeDTO(SysNoticeEntity entity) { + if (entity != null) { + this.noticeId = entity.getNoticeId() + ""; + this.noticeTitle = entity.getNoticeTitle() + ""; + this.noticeType = entity.getNoticeType() + ""; + this.noticeContent = entity.getNoticeContent(); + this.status = entity.getStatus() + ""; + this.createTime = entity.getCreateTime(); + this.creatorName = entity.getCreatorName(); + } + } + + private String noticeId; + + private String noticeTitle; + + private String noticeType; + + private String noticeContent; + + private String status; + + private Date createTime; + + private String creatorName; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeDomainService.java new file mode 100644 index 0000000..6bec587 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeDomainService.java @@ -0,0 +1,72 @@ +package com.agileboot.domain.system.notice; + +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.orm.entity.SysNoticeEntity; +import com.agileboot.orm.service.ISysNoticeService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author valarchie + */ +@Service +public class NoticeDomainService { + + @Autowired + private ISysNoticeService noticeService; + + public PageDTO getNoticeList(NoticeQuery query) { + Page page = noticeService.page(query.toPage(), query.toQueryWrapper()); + List records = page.getRecords().stream().map(NoticeDTO::new).collect(Collectors.toList()); + return new PageDTO(records, page.getTotal()); + } + + + public NoticeDTO getNoticeInfo(Long id) { + SysNoticeEntity byId = noticeService.getById(id); + return new NoticeDTO(byId); + } + + + public void addNotice(NoticeAddCommand addCommand, LoginUser loginUser) { + NoticeModel noticeModel = addCommand.toModel(); + + noticeModel.checkFields(); + + noticeModel.logCreator(loginUser); + + noticeModel.insert(); + } + + + public void updateNotice(NoticeUpdateCommand updateCommand, LoginUser loginUser) { + SysNoticeEntity byId = noticeService.getById(updateCommand.getNoticeId()); + + if (byId == null) { + throw new ApiException(ErrorCode.Business.OBJECT_NOT_FOUND, updateCommand.getNoticeId(), "通知公告"); + } + + NoticeModel noticeModel = updateCommand.toModel(); + + noticeModel.checkFields(); + + noticeModel.logUpdater(loginUser); + + noticeModel.updateById(); + } + + public void deleteNotice(BulkOperationCommand deleteCommand) { + noticeService.removeBatchByIds(deleteCommand.getIds()); + } + + + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeModel.java new file mode 100644 index 0000000..f931531 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeModel.java @@ -0,0 +1,23 @@ +package com.agileboot.domain.system.notice; + +import com.agileboot.orm.entity.SysNoticeEntity; +import com.agileboot.orm.enums.dictionary.CommonStatusEnum; +import com.agileboot.orm.enums.dictionary.NoticeTypeEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import lombok.Data; + +@Data +public class NoticeModel extends SysNoticeEntity { + + + public void checkFields() { + + Integer noticeType = this.getNoticeType(); + BasicEnumUtil.fromValue(NoticeTypeEnum.class, noticeType); + + Integer status = this.getStatus(); + BasicEnumUtil.fromValue(CommonStatusEnum.class, status); + + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeQuery.java new file mode 100644 index 0000000..e15ef56 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeQuery.java @@ -0,0 +1,33 @@ +package com.agileboot.domain.system.notice; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysNoticeEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +public class NoticeQuery extends AbstractPageQuery { + + private String noticeType; + + private String noticeTitle; + + private String creatorName; + + + @SuppressWarnings("rawtypes") + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper sysNoticeWrapper = new QueryWrapper<>(); + sysNoticeWrapper.like(StrUtil.isNotEmpty(noticeTitle), "notice_title", noticeTitle) + .eq(noticeType != null, "notice_type", noticeType) + .like(StrUtil.isNotEmpty(creatorName), "creator_name", creatorName); + + return sysNoticeWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeUpdateCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeUpdateCommand.java new file mode 100644 index 0000000..6e41da6 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeUpdateCommand.java @@ -0,0 +1,23 @@ +package com.agileboot.domain.system.notice; + +import cn.hutool.core.convert.Convert; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import lombok.Data; + +@Data +public class NoticeUpdateCommand extends NoticeAddCommand{ + + @NotNull + @Positive + protected String noticeId; + + + @Override + public NoticeModel toModel() { + NoticeModel noticeModel = super.toModel(); + noticeModel.setNoticeId(Convert.toInt(noticeId)); + return noticeModel; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogDTO.java new file mode 100644 index 0000000..79a8751 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogDTO.java @@ -0,0 +1,92 @@ +package com.agileboot.domain.system.operationLog; + +import cn.hutool.core.bean.BeanUtil; +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.common.annotation.ExcelSheet; +import com.agileboot.orm.entity.SysOperationLogEntity; +import com.agileboot.orm.enums.OperatorTypeEnum; +import com.agileboot.orm.enums.RequestMethodEnum; +import com.agileboot.orm.enums.dictionary.BusinessTypeEnum; +import com.agileboot.orm.enums.dictionary.OperationStatusEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import java.util.Date; +import lombok.Data; + +@Data +@ExcelSheet(name = "操作日志") +public class OperationLogDTO { + + public OperationLogDTO(SysOperationLogEntity entity) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + this.requestMethod = BasicEnumUtil.getDescriptionByValue(RequestMethodEnum.class, + entity.getRequestMethod()); + this.statusStr = BasicEnumUtil.getDescriptionByValue(OperationStatusEnum.class, entity.getStatus()); + businessTypeStr = BasicEnumUtil.getDescriptionByValue(BusinessTypeEnum.class, entity.getBusinessType()); + operatorTypeStr = BasicEnumUtil.getDescriptionByValue(OperatorTypeEnum.class, entity.getOperatorType()); + } + + + } + + @ExcelColumn(name = "ID") + private Long operationId; + + private Integer businessType; + + @ExcelColumn(name = "操作类型") + private String businessTypeStr; + + @ExcelColumn(name = "操作类型") + private String requestMethod; + + @ExcelColumn(name = "操作类型") + private String requestModule; + + @ExcelColumn(name = "操作类型") + private String requestUrl; + + @ExcelColumn(name = "操作类型") + private String calledMethod; + + private Integer operatorType; + + @ExcelColumn(name = "操作人类型") + private String operatorTypeStr; + + @ExcelColumn(name = "用户ID") + private Long userId; + + @ExcelColumn(name = "用户名") + private String username; + + @ExcelColumn(name = "ip地址") + private String operatorIp; + + @ExcelColumn(name = "ip地点") + private String operatorLocation; + + @ExcelColumn(name = "部门ID") + private Long deptId; + + @ExcelColumn(name = "部门") + private String deptName; + + @ExcelColumn(name = "操作参数") + private String operationParam; + + @ExcelColumn(name = "操作结果") + private String operationResult; + + private Integer status; + + @ExcelColumn(name = "状态") + private String statusStr; + + @ExcelColumn(name = "错误堆栈") + private String errorStack; + + @ExcelColumn(name = "操作时间") + private Date operationTime; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogDomainService.java new file mode 100644 index 0000000..20f5e6a --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogDomainService.java @@ -0,0 +1,32 @@ +package com.agileboot.domain.system.operationLog; + +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.orm.entity.SysOperationLogEntity; +import com.agileboot.orm.service.ISysOperationLogService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author valarchie + */ +@Service +public class OperationLogDomainService { + + @Autowired + private ISysOperationLogService operationLogService; + + public PageDTO getOperationLogList(OperationLogQuery query) { + Page page = operationLogService.page(query.toPage(), query.toQueryWrapper()); + List records = page.getRecords().stream().map(OperationLogDTO::new).collect(Collectors.toList()); + return new PageDTO(records, page.getTotal()); + } + + public void deleteOperationLog(BulkOperationCommand deleteCommand) { + operationLogService.removeBatchByIds(deleteCommand.getIds()); + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogQuery.java new file mode 100644 index 0000000..f070226 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogQuery.java @@ -0,0 +1,34 @@ +package com.agileboot.domain.system.operationLog; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysLoginInfoEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class OperationLogQuery extends AbstractPageQuery { + + private String businessType; + private String status; + private String username; + private String requestModule; + + + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + queryWrapper.like(businessType!=null, "business_type", businessType) + .eq(status != null, "status", status) + .like(StrUtil.isNotEmpty(username), "username", username) + .like(StrUtil.isNotEmpty(requestModule), "request_module", requestModule); + + addSortCondition(queryWrapper); + addTimeCondition(queryWrapper, "operation_time"); + + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/post/AddPostCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/AddPostCommand.java new file mode 100644 index 0000000..3a39544 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/AddPostCommand.java @@ -0,0 +1,47 @@ +package com.agileboot.domain.system.post; + +import cn.hutool.core.convert.Convert; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.PositiveOrZero; +import javax.validation.constraints.Size; +import lombok.Data; + +@Data +public class AddPostCommand { + + @NotBlank(message = "岗位编码不能为空") + @Size(max = 64, message = "岗位编码长度不能超过64个字符") + protected String postCode; + + /** + * 岗位名称 + */ + @NotBlank(message = "岗位名称不能为空") + @Size(max = 64, message = "岗位名称长度不能超过64个字符") + protected String postName; + + /** + * 岗位排序 + */ + @NotNull(message = "显示顺序不能为空") + protected Integer postSort; + + protected String remark; + + @PositiveOrZero + protected String status; + + public PostModel toModel() { + PostModel model = new PostModel(); + + model.setPostCode(postCode); + model.setPostName(postName); + model.setPostSort(postSort); + model.setRemark(remark); + model.setStatus(Convert.toInt(status)); + + return model; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostDTO.java new file mode 100644 index 0000000..1979a64 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostDTO.java @@ -0,0 +1,42 @@ +package com.agileboot.domain.system.post; + +import cn.hutool.core.bean.BeanUtil; +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.enums.dictionary.CommonStatusEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import lombok.Data; + +@Data +public class PostDTO { + + public PostDTO(SysPostEntity entity) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + statusStr = BasicEnumUtil.getDescriptionByValue(CommonStatusEnum.class, entity.getStatus()); + } + } + + @ExcelColumn(name = "岗位ID") + private Long postId; + + + @ExcelColumn(name = "岗位编码") + private String postCode; + + @ExcelColumn(name = "岗位名称") + private String postName; + + + @ExcelColumn(name = "岗位排序") + private String postSort; + + @ExcelColumn(name = "备注") + private String remark; + + private String status; + + @ExcelColumn(name = "状态") + private String statusStr; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostDomainService.java new file mode 100644 index 0000000..6cadcff --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostDomainService.java @@ -0,0 +1,93 @@ +package com.agileboot.domain.system.post; + +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.exception.error.ErrorCode.Business; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.service.ISysPostService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author valarchie + */ +@Service +public class PostDomainService { + + @Autowired + private ISysPostService postService; + + public PageDTO getPostList(PostQuery query) { + Page page = postService.page(query.toPage(), query.toQueryWrapper()); + List records = page.getRecords().stream().map(PostDTO::new).collect(Collectors.toList()); + return new PageDTO(records, page.getTotal()); + } + + public PostDTO getPostInfo(Long postId) { + SysPostEntity byId = postService.getById(postId); + return new PostDTO(byId); + } + + public void addPost(AddPostCommand addCommand, LoginUser loginUser) { + PostModel postModel = addCommand.toModel(); + + // check这种全局唯一性的判断 不适合放在 model领域类当中, 所以放在db service中 比较合适 + if (postService.checkPostNameUnique(null, postModel.getPostName())) { + throw new ApiException(ErrorCode.Business.POST_NAME_IS_NOT_UNIQUE, postModel.getPostName()); + } + + if (postService.checkPostCodeUnique(null, postModel.getPostCode())) { + throw new ApiException(ErrorCode.Business.POST_CODE_IS_NOT_UNIQUE, postModel.getPostCode()); + } + + postModel.logCreator(loginUser); + + postModel.insert(); + } + + public void updatePost(UpdatePostCommand updateCommand, LoginUser loginUser) { + PostModel postModel = updateCommand.toModel(); + + // check这种全局唯一性的判断 不适合放在 model领域类当中, 所以放在db service中 比较合适 + if (postService.checkPostNameUnique(postModel.getPostId(), postModel.getPostName())) { + throw new ApiException(ErrorCode.Business.POST_NAME_IS_NOT_UNIQUE, postModel.getPostName()); + } + + if (postService.checkPostCodeUnique(postModel.getPostId(), postModel.getPostCode())) { + throw new ApiException(ErrorCode.Business.POST_CODE_IS_NOT_UNIQUE, postModel.getPostCode()); + } + + postModel.logUpdater(loginUser); + + postModel.updateById(); + } + + + public void deletePost(BulkOperationCommand deleteCommand) { + for (Long id : deleteCommand.getIds()) { + PostModel postModel = getPostModel(id); + postModel.checkCanBeDelete(postService); + } + + postService.removeBatchByIds(deleteCommand.getIds()); + } + + + public PostModel getPostModel(Long id) { + SysPostEntity byId = postService.getById(id); + + if (byId == null) { + throw new ApiException(Business.OBJECT_NOT_FOUND, id, "职位"); + } + + return new PostModel(byId); + } + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostModel.java new file mode 100644 index 0000000..4d34f9c --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostModel.java @@ -0,0 +1,26 @@ +package com.agileboot.domain.system.post; + +import cn.hutool.core.bean.BeanUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.service.ISysPostService; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class PostModel extends SysPostEntity { + + public PostModel(SysPostEntity entity) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + } + } + + + public void checkCanBeDelete(ISysPostService postService) { + if (postService.isAssignedToUser(this.getPostId())) { + throw new ApiException(ErrorCode.Business.POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED); + } + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostQuery.java new file mode 100644 index 0000000..96d580f --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostQuery.java @@ -0,0 +1,31 @@ +package com.agileboot.domain.system.post; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class PostQuery extends AbstractPageQuery { + + private String postCode; + private String postName; + private Integer status; + + + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + queryWrapper.eq(status != null, "status", status) + .eq(postCode != null, "post_code", postCode) + .like(StrUtil.isNotEmpty(postName), "post_name", postName); + + addSortCondition(queryWrapper); + + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/post/UpdatePostCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/UpdatePostCommand.java new file mode 100644 index 0000000..f0b3483 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/post/UpdatePostCommand.java @@ -0,0 +1,21 @@ +package com.agileboot.domain.system.post; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import lombok.Data; + +@Data +public class UpdatePostCommand extends AddPostCommand{ + + @NotNull(message = "岗位ID不能为空") + @Positive + private Long postId; + + @Override + public PostModel toModel() { + PostModel postModel = super.toModel(); + postModel.setPostId(postId); + return postModel; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/AddRoleCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/AddRoleCommand.java new file mode 100644 index 0000000..8cd9a4d --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/AddRoleCommand.java @@ -0,0 +1,64 @@ +package com.agileboot.domain.system.role; + +import cn.hutool.core.convert.Convert; +import com.agileboot.common.annotation.ExcelColumn; +import java.util.List; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.PositiveOrZero; +import javax.validation.constraints.Size; +import lombok.Data; + +@Data +public class AddRoleCommand { + + /** + * 角色名称 + */ + @NotBlank(message = "角色名称不能为空") + @Size(max = 30, message = "角色名称长度不能超过30个字符") + private String roleName; + + /** + * 角色权限 + */ + @ExcelColumn(name = "角色权限") + @NotBlank(message = "权限字符不能为空") + @Size(max = 100, message = "权限字符长度不能超过100个字符") + private String roleKey; + + /** + * 角色排序 + */ + @ExcelColumn(name = "角色排序") + @NotBlank(message = "显示顺序不能为空") + private String roleSort; + + + private String remark; + + + @ExcelColumn(name = "数据范围") + private String dataScope; + + @PositiveOrZero + private String status; + + @NotNull + private List menuIds; + + public RoleModel toModel() { + RoleModel model = new RoleModel(); + + model.setRoleName(this.roleName); + model.setRoleKey(this.roleKey); + model.setRoleSort(Convert.toInt(this.roleSort)); + model.setDataScope(Convert.toInt(this.dataScope)); + model.setStatus(Convert.toInt(status)); + model.setRemark(this.remark); + model.setMenuIds(menuIds); + + return model; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/AllocatedRoleQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/AllocatedRoleQuery.java new file mode 100644 index 0000000..7ae9850 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/AllocatedRoleQuery.java @@ -0,0 +1,27 @@ +package com.agileboot.domain.system.role; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +public class AllocatedRoleQuery extends AbstractPageQuery { + + private Long roleId; + private String username; + private String phoneNumber; + + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("r.role_id", roleId).like(StrUtil.isNotEmpty(username),"u.username", username) + .like(StrUtil.isNotEmpty(phoneNumber), "u.phone_number", phoneNumber); + + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleDTO.java new file mode 100644 index 0000000..ea69efc --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleDTO.java @@ -0,0 +1,42 @@ +package com.agileboot.domain.system.role; + +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.common.annotation.ExcelSheet; +import com.agileboot.orm.entity.SysRoleEntity; +import java.util.Date; +import lombok.Data; + +@Data +@ExcelSheet(name = "角色列表") +public class RoleDTO { + + public RoleDTO(SysRoleEntity entity) { + if (entity != null) { + this.roleId = entity.getRoleId(); + this.roleName = entity.getRoleName() + ""; + this.roleKey = entity.getRoleKey(); + this.roleSort = entity.getRoleSort() + ""; + this.createTime = entity.getCreateTime(); + this.status = entity.getStatus() + ""; + this.remark = entity.getRemark(); + this.dataScope = entity.getDataScope() + ""; + } + } + + @ExcelColumn(name = "角色ID") + private Long roleId; + @ExcelColumn(name = "角色名称") + private String roleName; + @ExcelColumn(name = "角色标识") + private String roleKey; + @ExcelColumn(name = "角色排序") + private String roleSort; + @ExcelColumn(name = "角色状态") + private String status; + @ExcelColumn(name = "备注") + private String remark; + @ExcelColumn(name = "创建时间") + private Date createTime; + @ExcelColumn(name = "数据范围") + private String dataScope; +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleDomainService.java new file mode 100644 index 0000000..afbcf07 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleDomainService.java @@ -0,0 +1,184 @@ +package com.agileboot.domain.system.role; + +import cn.hutool.core.collection.CollUtil; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.system.user.UserDTO; +import com.agileboot.infrastructure.cache.CacheCenter; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.service.TokenService; +import com.agileboot.infrastructure.web.service.UserDetailsServiceImpl; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.service.ISysRoleMenuService; +import com.agileboot.orm.service.ISysRoleService; +import com.agileboot.orm.service.ISysUserService; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class RoleDomainService { + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysRoleMenuService roleMenuService; + + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + @Autowired + private UserDetailsServiceImpl userDetailsService; + + public PageDTO getRoleList(RoleQuery query) { + Page page = roleService.page(query.toPage(), query.toQueryWrapper()); + List records = page.getRecords().stream().map(RoleDTO::new).collect(Collectors.toList()); + return new PageDTO(records, page.getTotal()); + } + + public RoleDTO getRoleInfo(Long roleId) { + SysRoleEntity byId = roleService.getById(roleId); + return new RoleDTO(byId); + } + + + public void addRole(AddRoleCommand addCommand, LoginUser loginUser) { + RoleModel roleModel = addCommand.toModel(); + + roleModel.checkRoleNameUnique(roleService); + roleModel.checkRoleKeyUnique(roleService); + + roleModel.logCreator(loginUser); + + roleModel.insert(roleMenuService); + } + + public void deleteRoleByBulk(List roleIds, LoginUser loginUser) { + if (roleIds != null) { + for (Long roleId : roleIds) { + deleteRole(roleId, loginUser); + } + } + } + + public void deleteRole(Long roleId, LoginUser loginUser) { + RoleModel roleModel = getRoleModel(roleId); + + roleModel.checkRoleNameUnique(roleService); + + roleModel.logUpdater(loginUser); + + roleModel.deleteById(roleMenuService); + } + + + public void updateRole(UpdateRoleCommand updateCommand, LoginUser loginUser) { + SysRoleEntity byId = roleService.getById(updateCommand.getRoleId()); + + if (byId == null) { + throw new ApiException(ErrorCode.Business.OBJECT_NOT_FOUND, updateCommand.getRoleId(), "角色"); + } + + RoleModel roleModel = updateCommand.toModel(); + roleModel.checkRoleKeyUnique(roleService); + roleModel.checkRoleNameUnique(roleService); + + roleModel.logUpdater(loginUser); + + roleModel.updateById(roleMenuService); + + if (loginUser.isAdmin()) { + loginUser.setMenuPermissions(userDetailsService.getMenuPermissions(loginUser.getUserId())); + tokenService.setLoginUser(loginUser); + } + } + + + public RoleModel getRoleModel(Long roleId) { + SysRoleEntity byId = roleService.getById(roleId); + + if (byId == null) { + throw new ApiException(ErrorCode.Business.OBJECT_NOT_FOUND, roleId, "角色"); + } + + return new RoleModel(byId); + } + + public void updateStatus(UpdateStatusCommand command, LoginUser loginUser) { + RoleModel roleModel = getRoleModel(command.getRoleId()); + roleModel.setStatus(command.getStatus()); + roleModel.setUpdaterId(loginUser.getUserId()); + roleModel.setUpdaterName(loginUser.getUsername()); + roleModel.updateById(); + } + + public void updateDataScope(UpdateDataScopeCommand command) { + RoleModel roleModel = getRoleModel(command.getRoleId()); + roleModel.setDeptIds(command.getDeptIds()); + roleModel.setDataScope(command.getDataScope()); + + roleModel.generateDeptIdSet(); + roleModel.updateById(); + + CacheCenter.guavaCache.roleCache.invalidate(command.getRoleId() + ""); + + } + + + public PageDTO getAllocatedUserList(AllocatedRoleQuery query) { + Page page = userService.selectAllocatedList(query); + List dtoList = page.getRecords().stream().map(UserDTO::new).collect(Collectors.toList()); + return new PageDTO(dtoList, page.getTotal()); + } + + public PageDTO getUnallocatedUserList(UnallocatedRoleQuery query) { + Page page = userService.selectUnallocatedList(query); + List dtoList = page.getRecords().stream().map(UserDTO::new).collect(Collectors.toList()); + return new PageDTO(dtoList, page.getTotal()); + } + + + public void deleteRoleOfUser(Long userId) { + SysUserEntity user = userService.getById(userId); + if (user != null) { + user.setRoleId(null); + user.updateById(); + } + } + + public void deleteRoleOfUserByBulk(List userIds) { + if (CollUtil.isEmpty(userIds)) { + return; + } + + for (Long userId : userIds) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.set(SysUserEntity::getRoleId, null).eq(SysUserEntity::getUserId, userId); + + userService.update(updateWrapper); + } + } + + public void addRoleOfUserByBulk(Long roleId, List userIds) { + if (CollUtil.isEmpty(userIds)) { + return; + } + + for (Long userId : userIds) { + SysUserEntity user = userService.getById(userId); + user.setRoleId(roleId); + user.updateById(); + } + } + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleModel.java new file mode 100644 index 0000000..0e998de --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleModel.java @@ -0,0 +1,99 @@ +package com.agileboot.domain.system.role; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.entity.SysRoleMenuEntity; +import com.agileboot.orm.service.ISysRoleMenuService; +import com.agileboot.orm.service.ISysRoleService; +import com.agileboot.orm.service.ISysUserService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class RoleModel extends SysRoleEntity { + + public RoleModel(SysRoleEntity entity) { + if (entity != null) { + BeanUtil.copyProperties(entity,this); + } + } + + + private List menuIds; + + private List deptIds; + + public void checkRoleNameUnique(ISysRoleService roleService) { + if (roleService.checkRoleNameUnique(getRoleId(), getRoleName())) { + throw new ApiException(ErrorCode.Business.ROLE_NAME_IS_NOT_UNIQUE, getRoleName()); + } + } + + public void checkRoleCanBeDelete(ISysUserService userService) { + if (userService.checkExistUserLinkToRole(getRoleId())) { + throw new ApiException(ErrorCode.Business.ROLE_NAME_IS_NOT_UNIQUE, getRoleName()); + } + } + + public void checkRoleKeyUnique(ISysRoleService roleService) { + if (roleService.checkRoleKeyUnique(getRoleId(), getRoleKey())) { + throw new ApiException(ErrorCode.Business.ROLE_KEY_IS_NOT_UNIQUE, getRoleKey()); + } + } + + public void generateDeptIdSet() { + if (deptIds == null) { + setDeptIdSet(""); + } + + if (deptIds.size() > new HashSet<>(deptIds).size()) { + throw new ApiException(ErrorCode.Business.ROLE_DATA_SCOPE_DUPLICATED_DEPT); + } + + String deptIdSet = StrUtil.join(",", deptIds); + setDeptIdSet(deptIdSet); + } + + + + public void insert(ISysRoleMenuService roleMenuService) { + this.insert(); + saveMenus(roleMenuService); + } + + public void updateById(ISysRoleMenuService roleMenuService) { + this.updateById(); + // 清空之前的角色菜单关联 + roleMenuService.getBaseMapper().deleteByMap(Collections.singletonMap("role_id", getRoleId())); + saveMenus(roleMenuService); + } + + public void deleteById(ISysRoleMenuService roleMenuService) { + this.deleteById(); + // 清空之前的角色菜单关联 + roleMenuService.getBaseMapper().deleteByMap(Collections.singletonMap("role_id", getRoleId())); + } + + + public void saveMenus(ISysRoleMenuService roleMenuService) { + List list = new ArrayList<>(); + if (getMenuIds() != null) { + for (Long menuId : getMenuIds()) { + SysRoleMenuEntity rm = new SysRoleMenuEntity(); + rm.setRoleId(getRoleId()); + rm.setMenuId(menuId); + list.add(rm); + } + roleMenuService.saveBatch(list); + } + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleQuery.java new file mode 100644 index 0000000..c445100 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleQuery.java @@ -0,0 +1,34 @@ +package com.agileboot.domain.system.role; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class RoleQuery extends AbstractPageQuery { + + + private String roleName; + + private String roleKey; + + private String status; + + + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + + queryWrapper.eq(status != null, "status", status) + .eq(StrUtil.isNotEmpty(roleKey), "role_key", roleKey) + .like(StrUtil.isNotEmpty(roleName), "role_name", roleName); + + this.addTimeCondition(queryWrapper, "create_time"); + + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UnallocatedRoleQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UnallocatedRoleQuery.java new file mode 100644 index 0000000..26597ad --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UnallocatedRoleQuery.java @@ -0,0 +1,29 @@ +package com.agileboot.domain.system.role; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +public class UnallocatedRoleQuery extends AbstractPageQuery { + + private Long roleId; + private String username; + private String phoneNumber; + + @Override + public QueryWrapper toQueryWrapper() { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.like(StrUtil.isNotEmpty(username),"u.username", username) + .like(StrUtil.isNotEmpty(phoneNumber), "u.phone_number", phoneNumber) + .and(o-> o.ne("r.role_id", roleId) + .or().isNull("u.role_id")); + + return queryWrapper; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateDataScopeCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateDataScopeCommand.java new file mode 100644 index 0000000..440f0ba --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateDataScopeCommand.java @@ -0,0 +1,23 @@ +package com.agileboot.domain.system.role; + +import java.util.List; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import lombok.Data; + +@Data +public class UpdateDataScopeCommand { + + @NotNull + @Positive + private Long roleId; + + @NotNull + @NotEmpty + private List deptIds; + + private Integer dataScope; + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateRoleCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateRoleCommand.java new file mode 100644 index 0000000..1f589ba --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateRoleCommand.java @@ -0,0 +1,21 @@ +package com.agileboot.domain.system.role; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.PositiveOrZero; +import lombok.Data; + +@Data +public class UpdateRoleCommand extends AddRoleCommand{ + + @NotNull + @PositiveOrZero + private Long roleId; + + @Override + public RoleModel toModel() { + RoleModel roleModel = super.toModel(); + roleModel.setRoleId(this.roleId); + return roleModel; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateStatusCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateStatusCommand.java new file mode 100644 index 0000000..421e3e1 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateStatusCommand.java @@ -0,0 +1,14 @@ +package com.agileboot.domain.system.role; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class UpdateStatusCommand { + + private Long roleId; + + private Integer status; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/RegisterUserModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/RegisterUserModel.java new file mode 100644 index 0000000..e9f14db --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/RegisterUserModel.java @@ -0,0 +1,12 @@ +package com.agileboot.domain.system.user; + +import com.agileboot.orm.entity.SysUserEntity; +import lombok.Data; + +@Data +public class RegisterUserModel extends SysUserEntity { + + private String code; + private String uuid; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDTO.java new file mode 100644 index 0000000..e13b89d --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDTO.java @@ -0,0 +1,102 @@ +package com.agileboot.domain.system.user; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.common.annotation.ExcelSheet; +import com.agileboot.infrastructure.cache.guava.GuavaCacheService; +import com.agileboot.orm.entity.SysDeptEntity; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.result.SearchUserDO; +import java.util.Date; +import lombok.Data; + +@ExcelSheet(name = "用户列表") +@Data +public class UserDTO { + + public UserDTO(SysUserEntity entity) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + SysDeptEntity dept = SpringUtil.getBean(GuavaCacheService.class).deptCache + .get(entity.getDeptId() + ""); + if (dept != null) { + this.deptName = dept.getDeptName(); + } + } + } + + public UserDTO(SearchUserDO entity) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + } + } + + + @ExcelColumn(name = "用户ID") + private Long userId; + + @ExcelColumn(name = "职位ID") + private Long postId; + + @ExcelColumn(name = "角色ID") + private Long roleId; + + @ExcelColumn(name = "部门ID") + private Long deptId; + + @ExcelColumn(name = "部门名称") + private String deptName; + + @ExcelColumn(name = "用户名") + private String username; + + @ExcelColumn(name = "用户昵称") + private String nickName; + + @ExcelColumn(name = "用户类型") + private Integer userType; + + @ExcelColumn(name = "邮件") + private String email; + + @ExcelColumn(name = "号码") + private String phoneNumber; + + @ExcelColumn(name = "性别") + private String sex; + + @ExcelColumn(name = "用户头像") + private String avatar; + + @ExcelColumn(name = "状态") + private String status; + + @ExcelColumn(name = "IP") + private String loginIp; + + @ExcelColumn(name = "登录时间") + private Date loginDate; + + @ExcelColumn(name = "创建者ID") + private Long creatorId; + + @ExcelColumn(name = "创建者") + private String creatorName; + + @ExcelColumn(name = "创建时间") + private Date createTime; + + @ExcelColumn(name = "修改者ID") + private Long updaterId; + + @ExcelColumn(name = "修改者") + private String updaterName; + + @ExcelColumn(name = "修改时间") + private Date updateTime; + + @ExcelColumn(name = "备注") + private String remark; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDetailDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDetailDTO.java new file mode 100644 index 0000000..22e1f90 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDetailDTO.java @@ -0,0 +1,30 @@ +package com.agileboot.domain.system.user; + +import com.agileboot.domain.system.post.PostDTO; +import com.agileboot.domain.system.role.RoleDTO; +import java.util.List; +import java.util.Set; +import lombok.Data; + +@Data +public class UserDetailDTO { + + private UserDTO user; + + /** + * 返回所有role + */ + private List roles; + + /** + * 返回所有posts + */ + private List posts; + + private Long postId; + + private Long roleId; + + private Set permissions; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDomainService.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDomainService.java new file mode 100644 index 0000000..2244179 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDomainService.java @@ -0,0 +1,209 @@ +package com.agileboot.domain.system.user; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.Convert; +import com.agileboot.common.core.page.PageDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.common.BulkOperationCommand; +import com.agileboot.domain.system.loginInfo.SearchUserQuery; +import com.agileboot.domain.system.post.PostDTO; +import com.agileboot.domain.system.role.RoleDTO; +import com.agileboot.domain.system.user.command.AddUserCommand; +import com.agileboot.domain.system.user.command.ChangeStatusCommand; +import com.agileboot.domain.system.user.command.ResetPasswordCommand; +import com.agileboot.domain.system.user.command.UpdateProfileCommand; +import com.agileboot.domain.system.user.command.UpdateUserAvatarCommand; +import com.agileboot.domain.system.user.command.UpdateUserCommand; +import com.agileboot.domain.system.user.command.UpdateUserPasswordCommand; +import com.agileboot.infrastructure.cache.redis.RedisCacheService; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.service.TokenService; +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.result.SearchUserDO; +import com.agileboot.orm.service.ISysPostService; +import com.agileboot.orm.service.ISysRoleService; +import com.agileboot.orm.service.ISysUserService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UserDomainService { + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysPostService postService; + + @Autowired + private TokenService tokenService; + + @Autowired + private RedisCacheService redisCacheService; + + + + public PageDTO getUserList(SearchUserQuery query) { + Page searchUserDOPage = userService.selectUserList(query); + List userDTOList = searchUserDOPage.getRecords().stream().map(UserDTO::new).collect(Collectors.toList()); + return new PageDTO(userDTOList, searchUserDOPage.getTotal()); + } + + public UserProfileDTO getUserProfile(Long userId) { + + SysUserEntity userEntity = userService.getById(userId); + SysPostEntity postEntity = userService.getPostOfUser(userId); + SysRoleEntity roleEntity = userService.getRoleOfUser(userId); + + return new UserProfileDTO(userEntity, postEntity, roleEntity); + } + + + public void updateUserProfile(UpdateProfileCommand command, LoginUser loginUser) { + UserModel userModel = getUserModel(command.getUserId()); + command.updateModel(userModel); + + userModel.checkPhoneNumberIsUnique(userService); + userModel.checkEmailIsUnique(userService); + userModel.logUpdater(loginUser); + + userModel.updateById(); + + redisCacheService.userCache.delete(userModel.getUserId()); + } + + public UserDetailDTO getUserDetailInfo(Long userId) { + SysUserEntity userEntity = userService.getById(userId); + UserDetailDTO detailDTO = new UserDetailDTO(); + + List roleDTOs = roleService.list().stream().map(RoleDTO::new).collect(Collectors.toList()); + List postDTOs = postService.list().stream().map(PostDTO::new).collect(Collectors.toList()); + detailDTO.setRoles(roleDTOs); + detailDTO.setPosts(postDTOs); + + if (userEntity != null) { + detailDTO.setUser(new UserDTO(userEntity)); + detailDTO.setRoleId(userEntity.getRoleId()); + detailDTO.setPostId(userEntity.getPostId()); + } + return detailDTO; + } + + public void addUser(LoginUser loginUser, AddUserCommand command) { + UserModel model = command.toModel(); + + model.checkUsernameIsUnique(userService); + model.checkPhoneNumberIsUnique(userService); + model.checkEmailIsUnique(userService); + + model.logCreator(loginUser); + + model.insert(); + } + + public void updateUser(LoginUser loginUser, UpdateUserCommand command) { + UserModel model = command.toModel(); + + + model.checkPhoneNumberIsUnique(userService); + model.checkEmailIsUnique(userService); + model.checkCanBeModify(loginUser); + + model.logUpdater(loginUser); + + model.updateById(); + + redisCacheService.userCache.delete(model.getUserId()); + } + + @Transactional + public void deleteUsers(LoginUser loginUser, BulkOperationCommand command) { + for (Long id : command.getIds()) { + UserModel userModel = getUserModel(id); + userModel.checkCanBeDelete(loginUser); + userModel.deleteById(); + } + } + + public void updateUserPassword(LoginUser loginUser, UpdateUserPasswordCommand command) { + UserModel userModel = getUserModel(command.getUserId()); + userModel.modifyPassword(command); + userModel.updateById(); + + loginUser.setEntity(userModel); + + userModel.logUpdater(loginUser); + + tokenService.setLoginUser(loginUser); + redisCacheService.userCache.delete(userModel.getUserId()); + } + + public void resetUserPassword(LoginUser loginUser, ResetPasswordCommand command) { + UserModel userModel = getUserModel(command.getUserId()); + userModel.checkCanBeModify(loginUser); + userModel.resetPassword(command.getPassword()); + + userModel.logUpdater(loginUser); + + userModel.updateById(); + redisCacheService.userCache.delete(userModel.getUserId()); + } + + public void changeUserStatus(LoginUser loginUser, ChangeStatusCommand command) { + UserModel userModel = getUserModel(command.getUserId()); + userModel.setStatus(Convert.toInt(command.getStatus())); + + userModel.checkCanBeModify(loginUser); + userModel.logUpdater(loginUser); + + userModel.updateById(); + redisCacheService.userCache.delete(userModel.getUserId()); + } + + public void updateUserAvatar(LoginUser loginUser, UpdateUserAvatarCommand command) { + UserModel userModel = getUserModel(command.getUserId()); + userModel.setAvatar(command.getAvatar()); + + userModel.logUpdater(loginUser); + userModel.updateById(); + tokenService.setLoginUser(loginUser); + redisCacheService.userCache.delete(userModel.getUserId()); + } + + public UserInfoDTO getUserWithRole(Long userId) { + UserModel userModel = getUserModel(userId); + UserDTO userDTO = new UserDTO(userModel); + + SysRoleEntity roleEntity = roleService.getById(userModel.getRoleId()); + RoleDTO roleDTO = new RoleDTO(roleEntity); + + UserInfoDTO userInfoDTO = new UserInfoDTO(); + userInfoDTO.setUser(userDTO); + userInfoDTO.setRole(roleDTO); + return userInfoDTO; + } + + public UserModel getUserModel(Long userId) { + SysUserEntity byId = userService.getById(userId); + if (byId == null) { + throw new ApiException(ErrorCode.Business.OBJECT_NOT_FOUND, userId, "用户"); + } + + UserModel userModel = new UserModel(); + BeanUtil.copyProperties(byId, userModel); + return userModel; + } + + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserInfoDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserInfoDTO.java new file mode 100644 index 0000000..c8f3e1b --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserInfoDTO.java @@ -0,0 +1,12 @@ +package com.agileboot.domain.system.user; + +import com.agileboot.domain.system.role.RoleDTO; +import lombok.Data; + +@Data +public class UserInfoDTO { + + private UserDTO user; + private RoleDTO role; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserModel.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserModel.java new file mode 100644 index 0000000..677b329 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserModel.java @@ -0,0 +1,70 @@ +package com.agileboot.domain.system.user; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.exception.error.ErrorCode.Business; +import com.agileboot.domain.system.user.command.UpdateUserPasswordCommand; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.service.ISysUserService; +import java.util.Objects; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class UserModel extends SysUserEntity { + + public void checkUsernameIsUnique(ISysUserService userService) { + if (userService.checkUserNameUnique(getUsername())) { + throw new ApiException(ErrorCode.Business.USER_NAME_IS_NOT_UNIQUE); + } + } + + + public void checkPhoneNumberIsUnique(ISysUserService userService) { + if (StrUtil.isNotEmpty(getPhoneNumber()) && userService.checkPhoneUnique(getPhoneNumber(), + getUserId())) { + throw new ApiException(ErrorCode.Business.USER_PHONE_NUMBER_IS_NOT_UNIQUE); + } + } + + public void checkEmailIsUnique(ISysUserService userService) { + if (StrUtil.isNotEmpty(getEmail()) && userService.checkEmailUnique(getEmail(), getUserId())) { + throw new ApiException(ErrorCode.Business.USER_EMAIL_IS_NOT_UNIQUE); + } + } + + public void checkCanBeDelete(LoginUser loginUser) { + if (Objects.equals(getUserId(), loginUser.getUserId())) { + throw new ApiException(ErrorCode.Business.USER_CURRENT_USER_CAN_NOT_BE_DELETE); + } + } + + public void checkCanBeModify(LoginUser loginUser) { + if (LoginUser.isAdmin(this.getUserId()) && !loginUser.isAdmin()) { + throw new ApiException(Business.UNSUPPORTED_OPERATION); + } + } + + + public void modifyPassword(UpdateUserPasswordCommand command) { + if (!AuthenticationUtils.matchesPassword(command.getOldPassword(), getPassword())) { + throw new ApiException(ErrorCode.Business.USER_PASSWORD_IS_NOT_CORRECT); + } + + if (AuthenticationUtils.matchesPassword(command.getNewPassword(), getPassword())) { + throw new ApiException(ErrorCode.Business.USER_NEW_PASSWORD_IS_THE_SAME_AS_OLD); + } + setPassword(AuthenticationUtils.encryptPassword(command.getNewPassword())); + } + + public void resetPassword(String newPassword) { + setPassword(AuthenticationUtils.encryptPassword(newPassword)); + } + + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserProfileDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserProfileDTO.java new file mode 100644 index 0000000..e307469 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserProfileDTO.java @@ -0,0 +1,32 @@ +package com.agileboot.domain.system.user; + +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.entity.SysUserEntity; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +public class UserProfileDTO { + + public UserProfileDTO(SysUserEntity userEntity, SysPostEntity postEntity, SysRoleEntity roleEntity) { + if (userEntity != null) { + this.user = new UserDTO(userEntity); + } + + if (postEntity != null) { + this.postName = postEntity.getPostName(); + } + + if (roleEntity != null) { + this.roleName = roleEntity.getRoleName(); + } + } + + private UserDTO user; + private String roleName; + private String postName; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/AddUserCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/AddUserCommand.java new file mode 100644 index 0000000..4b043fc --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/AddUserCommand.java @@ -0,0 +1,70 @@ +package com.agileboot.domain.system.user.command; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.domain.system.user.UserModel; +import lombok.Data; + +@Data +public class AddUserCommand { + + @ExcelColumn(name = "部门ID") + private Long deptId; + + @ExcelColumn(name = "用户名") + private String username; + + @ExcelColumn(name = "昵称") + private String nickName; + + @ExcelColumn(name = "邮件") + private String email; + + @ExcelColumn(name = "电话号码") + private String phoneNumber; + + @ExcelColumn(name = "性别") + private Integer sex; + + @ExcelColumn(name = "头像") + private String avatar; + + @ExcelColumn(name = "密码") + private String password; + + @ExcelColumn(name = "状态") + private String status; + + @ExcelColumn(name = "角色ID") + private Long roleId; + + @ExcelColumn(name = "职位ID") + private Long postId; + + @ExcelColumn(name = "备注") + private String remark; + + + public UserModel toModel() { + UserModel model = new UserModel(); + + model.setDeptId(deptId); + model.setUsername(username); + model.setNickName(nickName); + model.setEmail(email); + model.setPhoneNumber(phoneNumber); + model.setSex(sex); + if (StrUtil.isNotEmpty(password)) { + model.setPassword(password); + } + model.setStatus(Convert.toInt(status)); + + model.setRoleId(roleId); + model.setPostId(postId); + model.setRemark(remark); + + return model; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ChangeStatusCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ChangeStatusCommand.java new file mode 100644 index 0000000..27eef34 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ChangeStatusCommand.java @@ -0,0 +1,11 @@ +package com.agileboot.domain.system.user.command; + +import lombok.Data; + +@Data +public class ChangeStatusCommand { + + private Long userId; + private String status; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ResetPasswordCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ResetPasswordCommand.java new file mode 100644 index 0000000..a4053e7 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ResetPasswordCommand.java @@ -0,0 +1,11 @@ +package com.agileboot.domain.system.user.command; + +import lombok.Data; + +@Data +public class ResetPasswordCommand { + + private Long userId; + private String password; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateProfileCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateProfileCommand.java new file mode 100644 index 0000000..113ddb5 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateProfileCommand.java @@ -0,0 +1,26 @@ +package com.agileboot.domain.system.user.command; + +import com.agileboot.domain.system.user.UserModel; +import lombok.Data; + +@Data +public class UpdateProfileCommand { + + private Long userId; + + private Integer sex; + private String nickName; + private String phoneNumber; + private String email; + + + public void updateModel(UserModel model) { + if (model != null) { + model.setSex(sex); + model.setNickName(nickName); + model.setPhoneNumber(phoneNumber); + model.setEmail(email); + } + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserAvatarCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserAvatarCommand.java new file mode 100644 index 0000000..d60b5af --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserAvatarCommand.java @@ -0,0 +1,18 @@ +package com.agileboot.domain.system.user.command; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class UpdateUserAvatarCommand { + + public UpdateUserAvatarCommand(Long userId, String avatar) { + this.userId = userId; + this.avatar = avatar; + } + + private Long userId; + private String avatar; + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserCommand.java new file mode 100644 index 0000000..ec1bf08 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserCommand.java @@ -0,0 +1,19 @@ +package com.agileboot.domain.system.user.command; + +import com.agileboot.domain.system.user.UserModel; +import lombok.Data; + +@Data +public class UpdateUserCommand extends AddUserCommand { + + + private Long userId; + + @Override + public UserModel toModel() { + UserModel model = super.toModel(); + model.setUserId(userId); + return model; + } + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserPasswordCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserPasswordCommand.java new file mode 100644 index 0000000..c37d39c --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserPasswordCommand.java @@ -0,0 +1,12 @@ +package com.agileboot.domain.system.user.command; + +import lombok.Data; + +@Data +public class UpdateUserPasswordCommand { + + private Long userId; + private String newPassword; + private String oldPassword; + +} diff --git a/agileboot-infrastructure/pom.xml b/agileboot-infrastructure/pom.xml new file mode 100644 index 0000000..3b6cf6a --- /dev/null +++ b/agileboot-infrastructure/pom.xml @@ -0,0 +1,134 @@ + + + + agileboot + com.agileboot + 1.0.0 + + jar + 4.0.0 + + agileboot-infrastructure + + + infrastructure框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + druid-spring-boot-starter + + + + + com.github.penggle + kaptcha + + + javax.servlet-api + javax.servlet + + + + + + + com.github.oshi + oshi-core + + + + + com.agileboot + agileboot-orm + + + + + com.agileboot + agileboot-common + + + + + + com.baomidou + mybatis-plus-generator + + + + + org.apache.velocity + velocity-engine-core + + + + + mysql + mysql-connector-java + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.google.guava + guava + 31.0.1-jre + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.1.RELEASE + + true + + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 + + false + ${project.artifactId} + + + + ${project.artifactId} + + + diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/AgileBootApplication.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/AgileBootApplication.java new file mode 100644 index 0000000..8ffb749 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/AgileBootApplication.java @@ -0,0 +1,32 @@ +package com.agileboot.infrastructure; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; + +/** + * 启动程序 + * 定制banner.txt的网站 + * http://patorjk.com/software/taag + * http://www.network-science.de/ascii/ + * http://www.degraeve.com/img2txt.php + * http://life.chacuo.net/convertfont2char + * @author valarchie + */ +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +@ComponentScan(basePackages = "com.agileboot.*") +public class AgileBootApplication { + + public static void main(String[] args) { + SpringApplication.run(AgileBootApplication.class, args); + String successMsg = " ____ _ _ __ _ _ \n" + + " / ___| | |_ __ _ _ __ | |_ _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / _| _ _ | || |\n" + + " \\___ \\ | __|/ _` || '__|| __| | | | || '_ \\ / __|| | | | / __|/ __|/ _ \\/ __|/ __|| |_ | | | || || |\n" + + " ___) || |_| (_| || | | |_ | |_| || |_) | \\__ \\| |_| || (__| (__| __/\\__ \\\\__ \\| _|| |_| || ||_|\n" + + " |____/ \\__|\\__,_||_| \\__| \\__,_|| .__/ |___/ \\__,_| \\___|\\___|\\___||___/|___/|_| \\__,_||_|(_)\n" + + " |_| "; + + System.out.println(successMsg); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/WarDeploymentInitializer.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/WarDeploymentInitializer.java new file mode 100644 index 0000000..8d2f50f --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/WarDeploymentInitializer.java @@ -0,0 +1,19 @@ +package com.agileboot.infrastructure; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * 对于Spring Boot应用,我们一般会打成jar包使用内置容器运行,但是有时候我们想要像使用传统spring web项目一样, + * 将Spring Boot应用打成WAR包,然后部署到外部容器运行,那么我们传统的使用Main类启动的方式稍显蹩脚, + * 因为外部容器无法识别到应用启动类,需要在应用中继承SpringBootServletInitializer类,然后重写config方法, + * 将其指向应用启动类。 + * @author valarchie + */ +public class WarDeploymentInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(AgileBootApplication.class); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/AccessLog.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/AccessLog.java new file mode 100644 index 0000000..689beb1 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/AccessLog.java @@ -0,0 +1,45 @@ +package com.agileboot.infrastructure.annotations; + +import com.agileboot.orm.enums.BusinessType; +import com.agileboot.orm.enums.OperatorTypeEnum; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义操作日志记录注解 + * + * @author ruoyi + */ +@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface AccessLog { + + /** + * 模块 + */ + String title() default ""; + + /** + * 功能 + */ + BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + OperatorTypeEnum operatorType() default OperatorTypeEnum.WEB; + + /** + * 是否保存请求的参数 + */ + boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + boolean isSaveResponseData() default false; +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/DataPermissionScope.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/DataPermissionScope.java new file mode 100644 index 0000000..8825b69 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/DataPermissionScope.java @@ -0,0 +1,28 @@ +package com.agileboot.infrastructure.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * data permission scope + * + * @author rouyi valarchie + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataPermissionScope { + + /** + * alias name of department table + */ + String deptAlias() default ""; + + /** + * alias name of user table + */ + String userAlias() default ""; +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/DataSource.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/DataSource.java new file mode 100644 index 0000000..fbbe68e --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/DataSource.java @@ -0,0 +1,28 @@ +package com.agileboot.infrastructure.annotations; + +import com.agileboot.common.enums.DataSourceType; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author ruoyi + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource { + + /** + * 切换数据源名称 + */ + DataSourceType value() default DataSourceType.MASTER; +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/RateLimiter.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/RateLimiter.java new file mode 100644 index 0000000..5695292 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/RateLimiter.java @@ -0,0 +1,41 @@ +package com.agileboot.infrastructure.annotations; + +import com.agileboot.common.enums.LimitType; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 限流注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + + /** + * 限流key + */ + String key() default "none"; + + /** + * 限流时间,单位秒 + */ + int time() default 60; + + /** + * 限流次数 + */ + int count() default 100; + + /** + * 限流类型 + */ + LimitType limitType() default LimitType.DEFAULT; + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/RepeatSubmit.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/RepeatSubmit.java new file mode 100644 index 0000000..e8d4575 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/RepeatSubmit.java @@ -0,0 +1,30 @@ +package com.agileboot.infrastructure.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义注解防止表单重复提交 + * + * @author ruoyi + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit { + + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + int interval() default 5000; + + /** + * 提示消息 + */ + String message() default "不允许重复提交,请稍候再试"; +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/AccessLogAspect.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/AccessLogAspect.java new file mode 100644 index 0000000..c641545 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/AccessLogAspect.java @@ -0,0 +1,202 @@ +package com.agileboot.infrastructure.aspectj; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.json.JSONUtil; +import com.agileboot.common.utils.ServletHolderUtil; +import com.agileboot.infrastructure.annotations.AccessLog; +import com.agileboot.infrastructure.thread.AsyncTaskFactory; +import com.agileboot.infrastructure.thread.ThreadPoolManager; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.entity.SysOperationLogEntity; +import com.agileboot.orm.enums.RequestMethodEnum; +import com.agileboot.orm.enums.dictionary.OperationStatusEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import java.util.Collection; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.HandlerMapping; + +/** + * 操作日志记录处理 + * + * @author valarchie + */ +@Aspect +@Component +@Slf4j +public class AccessLogAspect { + + + /** + * TODO 优化这个类 乱七八糟的 + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, AccessLog controllerLog, Object jsonResult) { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, AccessLog controllerLog, Exception e) { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, AccessLog controllerLog, final Exception e, Object jsonResult) { + try { + // 获取当前的用户 + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + + // *========数据库日志=========*// + SysOperationLogEntity operationLog = new SysOperationLogEntity(); + operationLog.setStatus(OperationStatusEnum.SUCCESS.getValue()); + // 请求的地址 + + String ip = ServletUtil.getClientIP(ServletHolderUtil.getRequest()); + operationLog.setOperatorIp(ip); + operationLog.setRequestUrl(ServletHolderUtil.getRequest().getRequestURI()); + if (loginUser != null) { + operationLog.setUsername(loginUser.getUsername()); + } + + if (e != null) { + operationLog.setStatus(OperationStatusEnum.FAIL.getValue()); + + operationLog.setErrorStack(StrUtil.sub(e.getMessage(), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operationLog.setCalledMethod(className + "." + methodName + "()"); + // 设置请求方式 + RequestMethodEnum requestMethodEnum = EnumUtil.fromString(RequestMethodEnum.class, + ServletHolderUtil.getRequest().getMethod()); + operationLog.setRequestMethod(requestMethodEnum != null ? requestMethodEnum.getValue() : RequestMethodEnum.UNKNOWN.getValue()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operationLog, jsonResult); + operationLog.setOperationTime(DateUtil.date()); + // 保存数据库 + ThreadPoolManager.execute(AsyncTaskFactory.recordOperationLog(operationLog)); + } catch (Exception exp) { + // 记录本地异常日志 + log.error("==前置通知异常=="); + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operationLog 操作日志 + */ + public void getControllerMethodDescription(JoinPoint joinPoint, AccessLog log, + SysOperationLogEntity operationLog, Object jsonResult) + throws Exception { + // 设置action动作 + operationLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operationLog.setRequestModule(log.title()); + // 设置操作人类别 + operationLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operationLog); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && jsonResult != null) { + operationLog.setOperationResult(StrUtil.sub(JSONUtil.toJsonStr(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operationLog 操作日志 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperationLogEntity operationLog) { + + RequestMethodEnum requestMethodEnum = BasicEnumUtil.fromValue(RequestMethodEnum.class, + operationLog.getRequestMethod()); + + if (requestMethodEnum == RequestMethodEnum.GET || requestMethodEnum == RequestMethodEnum.POST) { + String params = argsArrayToString(joinPoint.getArgs()); + operationLog.setOperationParam(StrUtil.sub(params, 0, 2000)); + } else { + Map paramsMap = (Map) ServletHolderUtil.getRequest() + .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + operationLog.setOperationParam(StrUtil.sub(paramsMap.toString(), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) { + StringBuilder params = new StringBuilder(); + if (paramsArray != null && paramsArray.length > 0) { + for (Object o : paramsArray) { + if (o != null && !isFilterObject(o)) { + try { + Object jsonObj = JSONUtil.parseObj(o); + params.append(jsonObj).append(" "); + } catch (Exception e) { + log.info("参数拼接错误", e); + } + } + } + } + return params.toString().trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.entrySet()) { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DBExceptionAspect.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DBExceptionAspect.java new file mode 100644 index 0000000..a774ab4 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DBExceptionAspect.java @@ -0,0 +1,42 @@ +package com.agileboot.infrastructure.aspectj; + +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; + + +/** + * @author valarchie + */ +@Aspect +@Component +@Slf4j +public class DBExceptionAspect { + + + @Pointcut("within(com.agileboot.orm..*)") + public void dbException() { + } + + @Around("dbException()") + public Object aroundDbException(ProceedingJoinPoint joinPoint) throws Throwable { + Object proceed = null; + try { + proceed = joinPoint.proceed(); + } catch (Exception e) { +// log.error("catch the DB EXCEPTION, CLASS NAME : {} ; REQUEST:{} ; EXCEPTION : {}", +// joinPoint.getSignature().toShortString(), +// joinPoint.getArgs(), +// e.getMessage()); + throw new ApiException(e, ErrorCode.Internal.DB_INTERNAL_ERROR, e.getMessage()); + } + return proceed; + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DataScopeAspect.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..f1407a6 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DataScopeAspect.java @@ -0,0 +1,115 @@ +package com.agileboot.infrastructure.aspectj; + +/** + * 数据过滤处理 + * + * @author ruoyi + */ +//@Aspect +//@Component +public class DataScopeAspect { + + /** +// * 全部数据权限 +// */ +// public static final String DATA_SCOPE_ALL = "1"; +// +// /** +// * 自定数据权限 +// */ +// public static final String DATA_SCOPE_CUSTOM = "2"; +// +// /** +// * 部门数据权限 +// */ +// public static final String DATA_SCOPE_DEPT = "3"; +// +// /** +// * 部门及以下数据权限 +// */ +// public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; +// +// /** +// * 仅本人数据权限 +// */ +// public static final String DATA_SCOPE_SELF = "5"; +// +// /** +// * 数据权限过滤关键字 +// */ +// public static final String DATA_SCOPE = "dataScope"; +// +// @Before("@annotation(controllerDataPermissionScope)") +// public void doBefore(JoinPoint point, DataPermissionScope controllerDataPermissionScope) throws Throwable { +// clearDataScope(point); +// handleDataScope(point, controllerDataPermissionScope); +// } +// +// protected void handleDataScope(final JoinPoint joinPoint, DataPermissionScope controllerDataPermissionScope) { +// // 获取当前的用户 +// LoginUser loginUser = AuthenticationUtils.getLoginUser(); +// if (loginUser != null) { +// SysUser currentUser = loginUser.getUser(); +// // 如果是超级管理员,则不过滤数据 +// if (currentUser != null && !currentUser.isAdmin()) { +// dataScopeFilter(joinPoint, currentUser, controllerDataPermissionScope.deptAlias(), +// controllerDataPermissionScope.userAlias()); +// } +// } +// } +// +// /** +// * 数据范围过滤 +// * +// * @param joinPoint 切点 +// * @param user 用户 +// * @param userAlias 别名 +// */ +// public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) { +// StringBuilder sqlString = new StringBuilder(); +// +// for (SysRole role : user.getRoles()) { +// String dataScope = role.getDataScope(); +// if (DATA_SCOPE_ALL.equals(dataScope)) { +// sqlString = new StringBuilder(); +// break; +// } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) { +// sqlString.append(StrUtil.format( +// " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, +// role.getRoleId())); +// } else if (DATA_SCOPE_DEPT.equals(dataScope)) { +// sqlString.append(StrUtil.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); +// } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { +// sqlString.append(StrUtil.format( +// " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", +// deptAlias, user.getDeptId(), user.getDeptId())); +// } else if (DATA_SCOPE_SELF.equals(dataScope)) { +// if (StrUtil.isNotBlank(userAlias)) { +// sqlString.append(StrUtil.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); +// } else { +// // 数据权限为仅本人且没有userAlias别名不查询任何数据 +// sqlString.append(" OR 1=0 "); +// } +// } +// } +// +// if (StrUtil.isNotBlank(sqlString.toString())) { +// Object params = joinPoint.getArgs()[0]; +// if (params != null && params instanceof BaseEntity) { +// BaseEntity baseEntity = (BaseEntity) params; +// baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); +// } +// } +// } +// +// /** +// * 拼接权限sql前先清空params.dataScope参数防止注入 +// */ +// private void clearDataScope(final JoinPoint joinPoint) { +// Object params = joinPoint.getArgs()[0]; +// if (params != null && params instanceof BaseEntity) { +// BaseEntity baseEntity = (BaseEntity) params; +// baseEntity.getParams().put(DATA_SCOPE, ""); +// } +// } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DataSourceAspect.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DataSourceAspect.java new file mode 100644 index 0000000..ad27667 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DataSourceAspect.java @@ -0,0 +1,61 @@ +package com.agileboot.infrastructure.aspectj; + +import com.agileboot.infrastructure.annotations.DataSource; +import com.agileboot.infrastructure.datasource.DynamicDataSourceContextHolder; +import java.util.Objects; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * 多数据源处理 + * + * @author ruoyi + */ +@Aspect +@Order(1) +@Component +@Slf4j +public class DataSourceAspect { + + @Pointcut("@annotation(com.agileboot.infrastructure.annotations.DataSource)" + + "|| @within(com.agileboot.infrastructure.annotations.DataSource)") + public void dsPointCut() { + + } + + @Around("dsPointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable { + DataSource dataSource = getDataSource(point); + + if (dataSource != null) { + DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); + } + + try { + return point.proceed(); + } finally { + // 销毁数据源 在执行方法之后 + DynamicDataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 获取需要切换的数据源 + */ + public DataSource getDataSource(ProceedingJoinPoint point) { + MethodSignature signature = (MethodSignature) point.getSignature(); + DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); + if (Objects.nonNull(dataSource)) { + return dataSource; + } + + return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/MethodLogAspect.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/MethodLogAspect.java new file mode 100644 index 0000000..bcaaf69 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/MethodLogAspect.java @@ -0,0 +1,85 @@ +package com.agileboot.infrastructure.aspectj; + +import cn.hutool.json.JSONUtil; +import com.agileboot.common.utils.jackson.JacksonUtil; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; + + +/** + * @author valarchie + */ +@Aspect +@Component +@Slf4j +public class MethodLogAspect { + + @Pointcut("execution(public * com.agileboot.orm.service.*.*(..))") + public void dbService() { + } + + @Around("dbService()") + public Object aroundDbService(ProceedingJoinPoint joinPoint) throws Throwable { + Object proceed = joinPoint.proceed(); + log.info("DB SERVICE : {} ; REQUEST:{} ; RESPONSE : {}", joinPoint.getSignature().toShortString(), + safeToJson(joinPoint.getArgs()), safeToJson(proceed)); + return proceed; + } + + @AfterThrowing(value = "dbService()", throwing = "e") + public void afterDbServiceThrow(JoinPoint joinPoint, Exception e) { + log.error("DB SERVICE : {} ; REQUEST:{} ; EXCEPTION : {}", joinPoint.getSignature().toShortString(), + safeToJson(joinPoint.getArgs()), e.getMessage()); + } + + + @Pointcut("bean(*ApplicationService)") + public void applicationServiceLog() { + } + + @Around("applicationServiceLog()") + public Object aroundApplicationService(ProceedingJoinPoint joinPoint) throws Throwable { + Object proceed = joinPoint.proceed(); + log.info("APPLICATION SERVICE : {} ; REQUEST:{} ; RESPONSE : {}", joinPoint.getSignature().toShortString(), + safeToJson(joinPoint.getArgs()), safeToJson(proceed)); + return proceed; + } + + @AfterThrowing(value = "applicationServiceLog()", throwing = "e") + public void afterApplicationServiceThrow(JoinPoint joinPoint, Exception e) { + log.error("APPLICATION SERVICE : {} ; REQUEST:{} ; EXCEPTION : {}", joinPoint.getSignature().toShortString(), + safeToJson(joinPoint.getArgs()), e.getMessage()); + } + + + /** + * 安全的打印出Json字符串 因为Jackson的Json格式化要求比较高,可能会报错 + * 如果报错的话 使用Hutool的JSON工具 如果还是报错,直接使用对象的toString方法即可 + * 目的只是为了打印参数和返回值 + * 逻辑上不用太严格 + */ + private String safeToJson(Object o) { + if (o == null) { + return "null"; + } + String json = null; + try { + json = JacksonUtil.to(o); + } catch (Exception e) { + json = JSONUtil.toJsonStr(o); + } finally { + if (json == null) { + json = o.toString(); + } + } + return json; + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/RateLimiterAspect.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..08834dc --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/RateLimiterAspect.java @@ -0,0 +1,84 @@ +package com.agileboot.infrastructure.aspectj; + +import cn.hutool.extra.servlet.ServletUtil; +import com.agileboot.common.enums.LimitType; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.utils.ServletHolderUtil; +import com.agileboot.infrastructure.annotations.RateLimiter; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; + +/** + * 限流处理 + * + * @author ruoyi + */ +@Aspect +@Component +@Slf4j +public class RateLimiterAspect { + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) { + String key = rateLimiter.key(); + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List keys = Collections.singletonList(combineKey); + try { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (number == null || number.intValue() > count) { + throw new ApiException(ErrorCode.Client.COMMON_REQUEST_TO_OFTEN); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key); + } catch (Exception e) { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) { + StringBuilder stringBuilder = new StringBuilder(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + + if (loginUser != null) { + stringBuilder.append(loginUser.getLoginInfo().getIpAddress()).append("-"); + } else { + stringBuilder.append(ServletUtil.getClientIP(ServletHolderUtil.getRequest())).append("-"); + } + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuilder.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuilder.toString(); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/CacheCenter.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/CacheCenter.java new file mode 100644 index 0000000..b92d90d --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/CacheCenter.java @@ -0,0 +1,28 @@ +package com.agileboot.infrastructure.cache; + +import com.agileboot.infrastructure.cache.guava.GuavaCacheService; +import com.agileboot.infrastructure.cache.redis.RedisCacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 缓存中心 提供全局访问点 + */ +@Component +public class CacheCenter { + + public static GuavaCacheService guavaCache; + + public static RedisCacheService redisCache; + + @Autowired + public void setGuavaCache(GuavaCacheService guavaCache) { + CacheCenter.guavaCache = guavaCache; + } + + @Autowired + public void setRedisCache(RedisCacheService redisCache) { + CacheCenter.redisCache = redisCache; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/RedisUtil.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/RedisUtil.java new file mode 100644 index 0000000..77e43e9 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/RedisUtil.java @@ -0,0 +1,212 @@ +package com.agileboot.infrastructure.cache; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +/** + * spring redis 工具类 + * + * @author valarchie + **/ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +public class RedisUtil { + + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + */ + public long deleteObject(final Collection collection) { + return redisTemplate.delete(collection); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 删除Hash中的数据 + */ + public void delCacheMapValue(final String key, final String hKey) { + HashOperations hashOperations = redisTemplate.opsForHash(); + hashOperations.delete(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/CacheNameConstants.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/CacheNameConstants.java new file mode 100644 index 0000000..7d1e7d7 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/CacheNameConstants.java @@ -0,0 +1,12 @@ +package com.agileboot.infrastructure.cache.aop; + +/** + * @author valarchie + */ +public class CacheNameConstants { + + public static final String GUAVA = "guava"; + + public static final String REDIS = "redis"; + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/GuavaCacheBean.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/GuavaCacheBean.java new file mode 100644 index 0000000..49444b2 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/GuavaCacheBean.java @@ -0,0 +1,89 @@ +package com.agileboot.infrastructure.cache.aop; + +import cn.hutool.core.util.StrUtil; +import com.google.common.cache.CacheBuilder; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; +import org.springframework.cache.Cache; +import org.springframework.cache.support.SimpleValueWrapper; + +//@Component +public class GuavaCacheBean implements Cache { + + /** + * 缓存仓库 + */ + private com.google.common.cache.Cache storage; + + @PostConstruct + private void init() { + storage = CacheBuilder.newBuilder() + // 设置缓存的容量为100 + .maximumSize(100) + // 设置初始容量为16 + .initialCapacity(16) + // 设置过期时间为写入缓存后10分钟过期 + .refreshAfterWrite(10, TimeUnit.MINUTES) + .build(); + } + + @Override + public String getName() { + return CacheNameConstants.GUAVA; + } + + + + @Override + public ValueWrapper get(Object key) { + if (Objects.isNull(key)) { + return null; + } + Object ifPresent = storage.getIfPresent(key.toString()); + return Objects.isNull(ifPresent) ? null : new SimpleValueWrapper(ifPresent); + } + + @Override + public void put(Object key, Object value) { + if (StrUtil.isEmpty((CharSequence) key)) { + return; + } + storage.put(key, value); + } + + @Override + public void evict(Object key) { + if (key == null) { + return; + } + + storage.invalidate(key); + } + + + + /*-----------------------暂时不用实现的方法-----------------*/ + + + @Override + public T get(Object key, Class type) { + return null; + } + + @Override + public T get(Object key, Callable valueLoader) { + return null; + } + + @Override + public void clear() { + + } + + @Override + public Object getNativeCache() { + return null; + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/RedisCacheBean.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/RedisCacheBean.java new file mode 100644 index 0000000..8304b41 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/RedisCacheBean.java @@ -0,0 +1,70 @@ +package com.agileboot.infrastructure.cache.aop; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.infrastructure.cache.RedisUtil; +import java.util.concurrent.Callable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.cache.support.SimpleValueWrapper; + +/** + * @author valarchie + */ +//@Component +public class RedisCacheBean implements Cache { + + /** + * 缓存仓库 + */ + @Autowired + private RedisUtil redisUtil; + + @Override + public String getName() { + return CacheNameConstants.REDIS; + } + + + @Override + public void put(Object key, Object value) { + if (StrUtil.isNotEmpty((CharSequence) key)) { + redisUtil.setCacheObject((String) key, value); + } + } + + @Override + public void evict(Object key) { + if (StrUtil.isNotEmpty((CharSequence) key)) { + redisUtil.deleteObject((String) key); + } + } + + @Override + public ValueWrapper get(Object key) { + return key == null ? null : new SimpleValueWrapper(redisUtil.getCacheObject((String) key)); + } + + + /*-----------------------暂时不用实现的方法-----------------*/ + + + @Override + public T get(Object key, Class type) { + return null; + } + + @Override + public T get(Object key, Callable valueLoader) { + return null; + } + + @Override + public Object getNativeCache() { + return null; + } + + @Override + public void clear() { + + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/package-info.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/package-info.java new file mode 100644 index 0000000..e6558e0 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/package-info.java @@ -0,0 +1,11 @@ +package com.agileboot.infrastructure.cache.aop; + +/** + * 本来想通过注解的形式 @cacheable来实现缓存 + * 但是MybatisPlus 生成的Service类 里面是空的 不好打注解 + * 并且本项目借鉴CQRS的想法,所有写相关的操作,都放置在domainService当中,而查询可以通过 + * mybatis plus普通的service进行查询。 + * 查询和操作 分别在两个地方 如果用@cacheable注解的话, 在两个地方进行分别注解 很容易疏漏出问题 + * 所以最终还是想采用手动操作缓存的方式 + * 利用三级缓存 map-> guava -> redis 的形式 + */ diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/GuavaCacheService.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/GuavaCacheService.java new file mode 100644 index 0000000..5211152 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/GuavaCacheService.java @@ -0,0 +1,62 @@ +package com.agileboot.infrastructure.cache.guava; + + +import com.agileboot.infrastructure.web.domain.login.Role; +import com.agileboot.orm.entity.SysDeptEntity; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.service.ISysConfigService; +import com.agileboot.orm.service.ISysDeptService; +import com.agileboot.orm.service.ISysRoleService; +import java.io.Serializable; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author valarchie + */ +@Component +@Slf4j +public class GuavaCacheService { + + @Autowired + private ISysConfigService configService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysRoleService roleService; + + public GuavaCacheTemplate configCache = new GuavaCacheTemplate() { + @Override + public String getObjectFromDb(Object id) { + return configService.getConfigValueByKey(id.toString()); + } + }; + + public GuavaCacheTemplate deptCache = new GuavaCacheTemplate() { + @Override + public SysDeptEntity getObjectFromDb(Object id) { + return deptService.getById(id.toString()); + } + }; + + + public GuavaCacheTemplate roleCache = new GuavaCacheTemplate() { + @Override + public Role getObjectFromDb(Object id) { + SysRoleEntity byId = roleService.getById((Serializable) id); + if (byId != null) { + return new Role(byId); + } + return null; + } + }; + + + + + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/GuavaCacheTemplate.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/GuavaCacheTemplate.java new file mode 100644 index 0000000..6c914d1 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/GuavaCacheTemplate.java @@ -0,0 +1,104 @@ +package com.agileboot.infrastructure.cache.guava; + +import cn.hutool.core.util.StrUtil; +import com.google.common.base.Ticker; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +/** + * 缓存接口实现类 + */ +@Slf4j +public abstract class GuavaCacheTemplate { + + private final LoadingCache> guavaCache = CacheBuilder.newBuilder() + // 基于容量回收。缓存的最大数量。超过就取MAXIMUM_CAPACITY = 1 << 30。依靠LRU队列recencyQueue来进行容量淘汰 + .maximumSize(1024) + // 基于容量回收。但这是统计占用内存大小,maximumWeight与maximumSize不能同时使用。设置最大总权重 +// .maximumWeight(1000) + // 设置权重(可当成每个缓存占用的大小) +// .weigher((o, o2) -> 5) + // 软弱引用(引用强度顺序:强软弱虚) + // -- 弱引用key +// .weakKeys() + // -- 弱引用value +// .weakValues() + // -- 软引用value +// .softValues() + // 过期失效回收 + // -- 没读写访问下,超过5秒会失效(非自动失效,需有任意get put方法才会扫描过期失效数据) +// .expireAfterAccess(5L, TimeUnit.MINUTES) + // -- 没写访问下,超过5秒会失效(非自动失效,需有任意put get方法才会扫描过期失效数据) +// .expireAfterWrite(5L, TimeUnit.MINUTES) + // 没写访问下,超过5秒会失效(非自动失效,需有任意put get方法才会扫描过期失效数据。但区别是会开一个异步线程进行刷新,刷新过程中访问返回旧数据) + .refreshAfterWrite(5L, TimeUnit.MINUTES) + // 移除监听事件 + .removalListener(removal -> { + // 可做一些删除后动作,比如上报删除数据用于统计 + log.info("触发删除动作,删除的key={}, value={}", removal.getKey(), removal.getValue()); + }) + // 并行等级。决定segment数量的参数,concurrencyLevel与maxWeight共同决定 + .concurrencyLevel(16) + // 开启缓存统计。比如命中次数、未命中次数等 + .recordStats() + // 所有segment的初始总容量大小 + .initialCapacity(512) + // 用于测试,可任意改变当前时间。参考:https://www.geek-share.com/detail/2689756248.html + .ticker(new Ticker() { + @Override + public long read() { + return 0; + } + }) + .build(new CacheLoader>() { + @Override + public Optional load(String key) { + T cacheObject = getObjectFromDb(key); + log.debug("find the local guava cache of key: {} is {}", key, cacheObject); + return Optional.ofNullable(cacheObject); + } + }); + + + + public T get(String key) { + try { + if (StrUtil.isEmpty(key)) { + return null; + } + + Optional optional = guavaCache.get(key); + return optional.orElse(null); + } catch (ExecutionException e) { + log.error("get cache object from guava cache failed."); + e.printStackTrace(); + return null; + } + } + + + public void invalidate(String key) { + if (StrUtil.isEmpty(key)) { + return; + } + + guavaCache.invalidate(key); + } + + public void invalidateAll() { + guavaCache.invalidateAll(); + } + + /** + * 从数据库加载数据 + * @param id + * @return + */ + public abstract T getObjectFromDb(Object id); + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/map/MapCache.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/map/MapCache.java new file mode 100644 index 0000000..9a42782 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/map/MapCache.java @@ -0,0 +1,60 @@ +package com.agileboot.infrastructure.cache.map; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import com.agileboot.orm.enums.dictionary.BusinessTypeEnum; +import com.agileboot.orm.enums.dictionary.CommonAnswerEnum; +import com.agileboot.orm.enums.dictionary.CommonStatusEnum; +import com.agileboot.orm.enums.dictionary.GenderEnum; +import com.agileboot.orm.enums.dictionary.NoticeStatusEnum; +import com.agileboot.orm.enums.dictionary.NoticeTypeEnum; +import com.agileboot.orm.enums.dictionary.OperationStatusEnum; +import com.agileboot.orm.enums.dictionary.VisibleStatusEnum; +import com.agileboot.orm.enums.interfaces.DictionaryEnum; +import com.agileboot.orm.result.DictionaryData; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 本地一级缓存 使用Map + * + * @author valarchie + */ +public class MapCache { + + private final static Map> DICTIONARY_CACHE = MapUtil.newHashMap(128); + + static { + initDictionaryCache(); + } + + private static void initDictionaryCache() { + + DICTIONARY_CACHE.put(BusinessTypeEnum.getDictName(), arrayToList(BusinessTypeEnum.values())); + DICTIONARY_CACHE.put(CommonAnswerEnum.getDictName(), arrayToList(CommonAnswerEnum.values())); + DICTIONARY_CACHE.put(CommonStatusEnum.getDictName(), arrayToList(CommonStatusEnum.values())); + DICTIONARY_CACHE.put(GenderEnum.getDictName(), arrayToList(GenderEnum.values())); + DICTIONARY_CACHE.put(NoticeStatusEnum.getDictName(), arrayToList(NoticeStatusEnum.values())); + DICTIONARY_CACHE.put(NoticeTypeEnum.getDictName(), arrayToList(NoticeTypeEnum.values())); + DICTIONARY_CACHE.put(OperationStatusEnum.getDictName(), arrayToList(OperationStatusEnum.values())); + DICTIONARY_CACHE.put(VisibleStatusEnum.getDictName(), arrayToList(VisibleStatusEnum.values())); + + } + + public static Map> dictionaryCache() { + return DICTIONARY_CACHE; + } + + @SuppressWarnings("rawtypes") + private static List arrayToList(DictionaryEnum[] dictionaryEnums) { + if(ArrayUtil.isEmpty(dictionaryEnums)) { + return ListUtil.empty(); + } + return Arrays.stream(dictionaryEnums).map(DictionaryData::new).collect(Collectors.toList()); + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/CacheKeyEnum.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/CacheKeyEnum.java new file mode 100644 index 0000000..b2eb359 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/CacheKeyEnum.java @@ -0,0 +1,44 @@ +package com.agileboot.infrastructure.cache.redis; + +import java.util.concurrent.TimeUnit; + +/** + * @author valarchie + */ +public enum CacheKeyEnum { + + /** + * Redis各类缓存集合 + */ + CAPTCHAT("captcha_codes:", 2, TimeUnit.MINUTES), + LOGIN_USER_KEY("login_tokens:", 30, TimeUnit.MINUTES), + REPEAT_SUBMIT_KEY("repeat_submit:", 5, TimeUnit.SECONDS), + RATE_LIMIT_KEY("rate_limit:", 60, TimeUnit.SECONDS), + USER_ENTITY_KEY("user_entity:", 60, TimeUnit.MINUTES), + + ; + + + CacheKeyEnum(String key, int expiration, TimeUnit timeUnit) { + this.key = key; + this.expiration = expiration; + this.timeUnit = timeUnit; + } + + private final String key; + private final int expiration; + private final TimeUnit timeUnit; + + public String key() { + return key; + } + + public int expiration() { + return expiration; + } + + public TimeUnit timeUnit() { + return timeUnit; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheService.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheService.java new file mode 100644 index 0000000..a0402de --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheService.java @@ -0,0 +1,48 @@ +package com.agileboot.infrastructure.cache.redis; + +import cn.hutool.extra.spring.SpringUtil; +import com.agileboot.infrastructure.cache.RedisUtil; +import com.agileboot.infrastructure.interceptor.repeatSubmit.RepeatRequest; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.service.ISysUserService; +import java.io.Serializable; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author valarchie + */ +@Component +public class RedisCacheService { + + @Autowired + private RedisUtil redisUtil; + + public RedisCacheTemplate captchaCache; + public RedisCacheTemplate loginUserCache; + public RedisCacheTemplate repeatSubmitCache; + public RedisCacheTemplate userCache; + + @PostConstruct + public void init() { + + captchaCache = new RedisCacheTemplate<>(redisUtil, CacheKeyEnum.CAPTCHAT); + + loginUserCache = new RedisCacheTemplate<>(redisUtil, CacheKeyEnum.LOGIN_USER_KEY); + + repeatSubmitCache = new RedisCacheTemplate<>(redisUtil, CacheKeyEnum.REPEAT_SUBMIT_KEY); + + userCache = new RedisCacheTemplate(redisUtil, CacheKeyEnum.USER_ENTITY_KEY) { + @Override + public Object getObjectFromDb(Object id) { + ISysUserService userService = SpringUtil.getBean(ISysUserService.class); + return userService.getById((Serializable) id); + } + }; + + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheTemplate.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheTemplate.java new file mode 100644 index 0000000..9af24f5 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheTemplate.java @@ -0,0 +1,126 @@ +package com.agileboot.infrastructure.cache.redis; + +import com.agileboot.infrastructure.cache.RedisUtil; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +/** + * 缓存接口实现类 + */ +@Slf4j +public class RedisCacheTemplate { + + private final RedisUtil redisUtil; + private final CacheKeyEnum redisRedisEnum; + private final LoadingCache> guavaCache = CacheBuilder.newBuilder() + // 基于容量回收。缓存的最大数量。超过就取MAXIMUM_CAPACITY = 1 << 30。依靠LRU队列recencyQueue来进行容量淘汰 + .maximumSize(1024) + .softValues() + // 没写访问下,超过5秒会失效(非自动失效,需有任意put get方法才会扫描过期失效数据。 + // 但区别是会开一个异步线程进行刷新,刷新过程中访问返回旧数据) + .refreshAfterWrite(30L, TimeUnit.MINUTES) + // 并行等级。决定segment数量的参数,concurrencyLevel与maxWeight共同决定 + .concurrencyLevel(64) + // 所有segment的初始总容量大小 + .initialCapacity(128) + .build(new CacheLoader>() { + @Override + public Optional load(String cachedKey) { + T cacheObject = redisUtil.getCacheObject(cachedKey); + log.debug("find the redis cache of key: {} is {}", cachedKey, cacheObject); + return Optional.ofNullable(cacheObject); + } + }); + + public RedisCacheTemplate(RedisUtil redisUtil, CacheKeyEnum redisRedisEnum) { + this.redisUtil = redisUtil; + this.redisRedisEnum = redisRedisEnum; + } + + /** + * 从缓存中获取对象 如果获取不到的话 从DB层面获取 + * @param id + * @return + */ + public T getObjectById(Object id) { + String cachedKey = generateKey(id); + try { + Optional optional = guavaCache.get(cachedKey); + log.debug("find the guava cache of key: {}", cachedKey); + + if (!optional.isPresent()) { + T objectFromDb = getObjectFromDb(id); + set(id, objectFromDb); + return objectFromDb; + } + + return optional.get(); + } catch (ExecutionException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 从缓存中获取 对象, 即使找不到的话 也不从DB中找 + * @param id + * @return + */ + public T getCachedObjectById(Object id) { + String cachedKey = generateKey(id); + try { + Optional optional = guavaCache.get(cachedKey); + log.debug("find the guava cache of key: {}", cachedKey); + return optional.orElse(null); + } catch (ExecutionException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 从缓存中获取 对象, 即使找不到的话 也不从DB中找 + * @param cachedKey 直接通过redis的key来搜索 + * @return + */ + public T getCachedObjectByKey(String cachedKey) { + try { + Optional optional = guavaCache.get(cachedKey); + log.debug("find the guava cache of key: {}", cachedKey); + return optional.orElse(null); + } catch (ExecutionException e) { + e.printStackTrace(); + return null; + } + } + + + public void set(Object id, T obj) { + redisUtil.setCacheObject(generateKey(id), obj, redisRedisEnum.expiration(), redisRedisEnum.timeUnit()); + guavaCache.refresh(generateKey(id)); + } + + public void delete(Object id) { + redisUtil.deleteObject(generateKey(id)); + guavaCache.refresh(generateKey(id)); + } + + public void refresh(Object id) { + redisUtil.expire(generateKey(id), redisRedisEnum.expiration(), redisRedisEnum.timeUnit()); + guavaCache.refresh(generateKey(id)); + } + + public String generateKey(Object id) { + return redisRedisEnum.key() + id; + } + + public T getObjectFromDb(Object id) { + return null; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ApplicationConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ApplicationConfig.java new file mode 100644 index 0000000..a3c4ed4 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ApplicationConfig.java @@ -0,0 +1,29 @@ +package com.agileboot.infrastructure.config; + +import java.util.TimeZone; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author ruoyi + */ +@Configuration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.agileboot.orm.**.mapper") +public class ApplicationConfig { + + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/FilterConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/FilterConfig.java new file mode 100644 index 0000000..14b0c2c --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/FilterConfig.java @@ -0,0 +1,50 @@ +package com.agileboot.infrastructure.config; + +import com.agileboot.infrastructure.filter.RepeatableFilter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Filter配置 + * ruoyi TODO 需整改 + */ +@Configuration +public class FilterConfig { + + @Value("${xss.excludes}") + private String excludes; + + @Value("${xss.urlPatterns}") + private String urlPatterns; + +/* @SuppressWarnings({"rawtypes", "unchecked"}) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + StrUtil.split(urlPatterns, ","); + registration.addUrlPatterns(ArrayUtil.toArray(StrUtil.split(urlPatterns, ","), String.class)); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap<>(); + initParameters.put("excludes", excludes); + registration.setInitParameters(initParameters); + return registration; + }*/ + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Bean + public FilterRegistrationBean someFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/MyBatisConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/MyBatisConfig.java new file mode 100644 index 0000000..158eb45 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/MyBatisConfig.java @@ -0,0 +1,26 @@ +package com.agileboot.infrastructure.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Mybatis支持*匹配扫描包 + * + * @author valarchie + */ +@Configuration +public class MyBatisConfig { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); + return interceptor; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/RedisConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/RedisConfig.java new file mode 100644 index 0000000..62f81fc --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/RedisConfig.java @@ -0,0 +1,94 @@ +package com.agileboot.infrastructure.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author ruoyi valarchie + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.registerModule((new SimpleModule())); + //有属性不能映射的时候不报错 + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + //对象为空时不抛异常 + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, + JsonTypeInfo.As.PROPERTY); + + GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper); + +// ObjectMapper mapper = new ObjectMapper(); +// mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); +// mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, +// JsonTypeInfo.As.PROPERTY); +// serializer.setObjectMapper(mapper); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ResourcesConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ResourcesConfig.java new file mode 100644 index 0000000..8ed5068 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ResourcesConfig.java @@ -0,0 +1,67 @@ +package com.agileboot.infrastructure.config; + +import com.agileboot.common.config.AgileBootConfig; +import com.agileboot.common.constant.Constants; +import com.agileboot.infrastructure.interceptor.repeatSubmit.AbstractRepeatSubmitInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 通用配置 + * + * @author ruoyi + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer { + + @Autowired + private AbstractRepeatSubmitInterceptor repeatSubmitInterceptor; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + /** 本地文件上传路径 */ + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + AgileBootConfig.getProfile() + "/"); + + /** swagger配置 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); + } + + /** + * 自定义拦截规则 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SecurityConfiguration.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SecurityConfiguration.java new file mode 100644 index 0000000..1d0281b --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SecurityConfiguration.java @@ -0,0 +1,122 @@ +package com.agileboot.infrastructure.config; + +import com.agileboot.infrastructure.security.filter.JwtAuthenticationTokenFilter; +import com.agileboot.infrastructure.security.handle.AuthenticationEntryPointImpl; +import com.agileboot.infrastructure.security.handle.LogoutSuccessHandlerImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfiguration { + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + +// @Bean +// public AuthenticationManager authenticationManager( +// AuthenticationConfiguration authConfig) throws Exception { +// return authConfig.getAuthenticationManager(); +// } + + + @Bean + public AuthenticationManager authManager(HttpSecurity http, PasswordEncoder passwordEncoder, + UserDetailsService userDetailService) + throws Exception { + return http.getSharedObject(AuthenticationManagerBuilder.class) + .userDetailsService(userDetailService) + .passwordEncoder(passwordEncoder) + .and() + .build(); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + + @Bean + public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + httpSecurity + // CSRF禁用,因为不使用session + .csrf().disable() + // 认证失败处理类 + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + // 基于token,所以不需要session + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + // 过滤请求 + .authorizeRequests() + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + .antMatchers("/login", "/register", "/captchaImage").anonymous() + .antMatchers( + HttpMethod.GET, + "/", + "/*.html", + "/**/*.html", + "/**/*.css", + "/**/*.js", + "/profile/**" + ).permitAll() + // TODO this is danger. + .antMatchers("/swagger-ui.html").anonymous() + .antMatchers("/swagger-resources/**").anonymous() + .antMatchers("/webjars/**").anonymous() + .antMatchers("/*/api-docs").anonymous() + .antMatchers("/druid/**").anonymous() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated() + .and() + .headers().frameOptions().disable(); + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); + // 添加JWT filter + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + // 添加CORS filter + httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); + httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); + + return httpSecurity.build(); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SecuritySbConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SecuritySbConfig.java new file mode 100644 index 0000000..c449d10 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SecuritySbConfig.java @@ -0,0 +1,132 @@ +package com.agileboot.infrastructure.config; + +import com.agileboot.infrastructure.security.filter.JwtAuthenticationTokenFilter; +import com.agileboot.infrastructure.security.handle.AuthenticationEntryPointImpl; +import com.agileboot.infrastructure.security.handle.LogoutSuccessHandlerImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; + +/** + * spring security配置 + * + * @author ruoyi + */ +// TODO delete +//@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +@Deprecated +public class SecuritySbConfig extends WebSecurityConfigurerAdapter { + + /** + * 自定义用户认证逻辑 + */ + @Autowired + private UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 解决 无法直接注入 AuthenticationManager + */ + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + /** + * anyRequest | 匹配所有请求路径 access | SpringEl表达式结果为true时可以访问 anonymous | 匿名可以访问 + * denyAll | 用户不能访问 fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) hasAnyAuthority | + * 如果有参数,参数表示权限,则其中任何一个权限可以访问 hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 hasAuthority | + * 如果有参数,参数表示权限,则其权限可以访问 hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 hasRole | + * 如果有参数,参数表示角色,则其角色可以访问 permitAll | 用户可以任意访问 rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception { + httpSecurity + // CSRF禁用,因为不使用session + .csrf().disable() + // 认证失败处理类 + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + // 基于token,所以不需要session + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + // 过滤请求 + .authorizeRequests() + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + .antMatchers("/login", "/register", "/captchaImage").anonymous() + .antMatchers( + HttpMethod.GET, + "/", + "/*.html", + "/**/*.html", + "/**/*.css", + "/**/*.js", + "/profile/**" + ).permitAll() + // TODO this is danger. + .antMatchers("/swagger-ui.html").anonymous() + .antMatchers("/swagger-resources/**").anonymous() + .antMatchers("/webjars/**").anonymous() + .antMatchers("/*/api-docs").anonymous() + .antMatchers("/druid/**").anonymous() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated() + .and() + .headers().frameOptions().disable(); + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); + // 添加JWT filter + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + // 添加CORS filter + httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); + httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * 身份认证接口 + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SwaggerConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SwaggerConfig.java new file mode 100644 index 0000000..1ae4edd --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SwaggerConfig.java @@ -0,0 +1,126 @@ +package com.agileboot.infrastructure.config; + +import com.agileboot.common.config.AgileBootConfig; +import io.swagger.annotations.ApiOperation; +import io.swagger.models.auth.In; +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; + +/** + * Swagger2的接口配置 + * + * @author ruoyi + */ +@Configuration +public class SwaggerConfig { + + /** + * 系统基础配置 + */ + @Autowired + private AgileBootConfig agileBootConfig; + + /** + * 是否开启swagger + */ + @Value("${swagger.enabled}") + private boolean enabled; + + /** + * 设置请求的统一前缀 + */ + @Value("${swagger.pathMapping}") + private String pathMapping; + + /** + * 创建API + */ + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.OAS_30) + // 是否启用Swagger + .enable(enabled) + // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) + .apiInfo(apiInfo()) + // 设置哪些接口暴露给Swagger展示 + .select() + // 扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + // 扫描指定包中的swagger注解 + // .apis(RequestHandlerSelectors.basePackage("com.agileboot.project.tool.swagger")) + // 扫描所有 .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build() + /* 设置安全模式,swagger可以设置访问token */ + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List securitySchemes() { + List apiKeyList = new ArrayList(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List securityContexts() { + List securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + /** + * 默认的安全上引用 + */ + private List defaultAuth() { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + /** + * 添加摘要信息 + */ + private ApiInfo apiInfo() { + // 用ApiInfoBuilder进行定制 + return new ApiInfoBuilder() + // 设置标题 + .title("标题:AgileBoot管理系统_接口文档") + // 描述 + .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") + // 作者信息 + .contact(new Contact(agileBootConfig.getName(), null, null)) + // 版本 + .version("版本号:" + agileBootConfig.getVersion()) + .build(); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaConfig.java new file mode 100644 index 0000000..5f7f437 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaConfig.java @@ -0,0 +1,95 @@ +package com.agileboot.infrastructure.config.captcha; + +import static com.google.code.kaptcha.Constants.KAPTCHA_BORDER; +import static com.google.code.kaptcha.Constants.KAPTCHA_BORDER_COLOR; +import static com.google.code.kaptcha.Constants.KAPTCHA_IMAGE_HEIGHT; +import static com.google.code.kaptcha.Constants.KAPTCHA_IMAGE_WIDTH; +import static com.google.code.kaptcha.Constants.KAPTCHA_NOISE_COLOR; +import static com.google.code.kaptcha.Constants.KAPTCHA_NOISE_IMPL; +import static com.google.code.kaptcha.Constants.KAPTCHA_OBSCURIFICATOR_IMPL; +import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_CONFIG_KEY; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE; +import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_IMPL; + +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 验证码配置 + * + * @author ruoyi + */ +@Configuration +public class CaptchaConfig { + + @Bean(name = "captchaProducer") + public DefaultKaptcha getCaptchaBean() { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 需要填 文本生成器类的全限定包名 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.agileboot.infrastructure.config.captcha.CaptchaMathTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaMathTextCreator.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaMathTextCreator.java new file mode 100644 index 0000000..8488ed3 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaMathTextCreator.java @@ -0,0 +1,73 @@ +package com.agileboot.infrastructure.config.captcha; + +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.RandomUtil; +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * verification code of math Generator + * + * @author valarchie + */ +public class CaptchaMathTextCreator extends DefaultTextCreator { + + @Override + public String getText() { + int x = RandomUtil.randomInt(13); + int y = RandomUtil.randomInt(13); + Operand randomOperand = EnumUtil.getEnumAt(Operand.class, RandomUtil.randomInt(4)); + + StringBuilder mathExpression = new StringBuilder(); + int result = randomOperand.generateMathExpression(x, y, mathExpression); + + mathExpression.append("=?@").append(result); + return mathExpression.toString(); + } + + @SuppressWarnings("AlibabaEnumConstantsMustHaveComment") + enum Operand { + ADD { + @Override + public int generateMathExpression(int x, int y, StringBuilder expression) { + expression.append(x); + expression.append("+"); + expression.append(y); + return x + y; + } + }, + MINUS { + @Override + public int generateMathExpression(int x, int y, StringBuilder expression) { + expression.append(Math.max(x, y)); + expression.append("-"); + expression.append(Math.min(x, y)); + return Math.abs(x - y); + } + }, + MULTIPLE { + @Override + public int generateMathExpression(int x, int y, StringBuilder expression) { + expression.append(x); + expression.append("*"); + expression.append(y); + return x * y; + } + }, + DIVIDE { + @Override + public int generateMathExpression(int x, int y, StringBuilder expression) { + // Judge whether an integer can be divided + if (x != 0 && y % x == 0) { + expression.append(y); + expression.append("/"); + expression.append(x); + return y / x; + } else { + // use add addition instead + return Operand.ADD.generateMathExpression(x, y, expression); + } + } + }; + public abstract int generateMathExpression(int x, int y, StringBuilder expression); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/druid/DruidConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/druid/DruidConfig.java new file mode 100644 index 0000000..ed29f4a --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/druid/DruidConfig.java @@ -0,0 +1,115 @@ +package com.agileboot.infrastructure.config.druid; + +import cn.hutool.extra.spring.SpringUtil; +import com.agileboot.common.enums.DataSourceType; +import com.agileboot.infrastructure.datasource.DynamicDataSource; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.sql.DataSource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * druid 配置多数据源 + * + * @author ruoyi + */ +@Configuration +public class DruidConfig { + + @Bean + @ConfigurationProperties("spring.datasource.druid.master") + public DataSource masterDataSource(DruidProperties druidProperties) { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean + @ConfigurationProperties("spring.datasource.druid.slave") + @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") + public DataSource slaveDataSource(DruidProperties druidProperties) { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean(name = "dynamicDataSource") + @Primary + public DynamicDataSource dataSource(DataSource masterDataSource) { + Map targetDataSources = new HashMap<>(); + targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); + setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); + return new DynamicDataSource(masterDataSource, targetDataSources); + } + + /** + * 设置数据源 + * + * @param targetDataSources 备选数据源集合 + * @param sourceName 数据源名称 + * @param beanName bean名称 + */ + public void setDataSource(Map targetDataSources, String sourceName, String beanName) { + try { + DataSource dataSource = SpringUtil.getBean(beanName); + targetDataSources.put(sourceName, dataSource); + } catch (Exception e) { + } + } + + /** + * 去除监控页面底部的广告 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") + public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) { + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + // 创建filter进行过滤 + Filter filter = new Filter() { + @Override + public void init(javax.servlet.FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + + @Override + public void destroy() { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/druid/DruidProperties.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/druid/DruidProperties.java new file mode 100644 index 0000000..c5e0f54 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/druid/DruidProperties.java @@ -0,0 +1,77 @@ +package com.agileboot.infrastructure.config.druid; + +import com.alibaba.druid.pool.DruidDataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +/** + * druid 配置属性 + * + * @author ruoyi + */ +@Configuration +//@ConfigurationProperties(prefix = "agileboot") +public class DruidProperties { + + @Value("${spring.datasource.druid.initialSize}") + private int initialSize; + + @Value("${spring.datasource.druid.minIdle}") + private int minIdle; + + @Value("${spring.datasource.druid.maxActive}") + private int maxActive; + + @Value("${spring.datasource.druid.maxWait}") + private int maxWait; + + @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") + private int maxEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.validationQuery}") + private String validationQuery; + + @Value("${spring.datasource.druid.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${spring.datasource.druid.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${spring.datasource.druid.testOnReturn}") + private boolean testOnReturn; + + public DruidDataSource dataSource(DruidDataSource datasource) { + /** 配置初始化大小、最小、最大 */ + datasource.setInitialSize(initialSize); + datasource.setMaxActive(maxActive); + datasource.setMinIdle(minIdle); + + /** 配置获取连接等待超时的时间 */ + datasource.setMaxWait(maxWait); + + /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ + datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + + /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ + datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); + + /** + * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + */ + datasource.setValidationQuery(validationQuery); + /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ + datasource.setTestWhileIdle(testWhileIdle); + /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnBorrow(testOnBorrow); + /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnReturn(testOnReturn); + return datasource; + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/datasource/DynamicDataSource.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/datasource/DynamicDataSource.java new file mode 100644 index 0000000..84c13ad --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/datasource/DynamicDataSource.java @@ -0,0 +1,24 @@ +package com.agileboot.infrastructure.datasource; + +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * + * @author ruoyi + */ +public class DynamicDataSource extends AbstractRoutingDataSource { + + public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() { + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/datasource/DynamicDataSourceContextHolder.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..11ded66 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,40 @@ +package com.agileboot.infrastructure.datasource; + +import lombok.extern.slf4j.Slf4j; + +/** + * 数据源切换处理 + * + * @author ruoyi + */ +@Slf4j +public class DynamicDataSourceContextHolder { + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源的变量 + */ + public static void setDataSourceType(String dsType) { + log.info("切换到{}数据源", dsType); + CONTEXT_HOLDER.set(dsType); + } + + /** + * 获得数据源的变量 + */ + public static String getDataSourceType() { + return CONTEXT_HOLDER.get(); + } + + /** + * 清空数据源变量 + */ + public static void clearDataSourceType() { + CONTEXT_HOLDER.remove(); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/RepeatableFilter.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/RepeatableFilter.java new file mode 100644 index 0000000..cf98721 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/RepeatableFilter.java @@ -0,0 +1,46 @@ +package com.agileboot.infrastructure.filter; + +import cn.hutool.core.util.StrUtil; +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) { + chain.doFilter(request, response); + } else { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() { + + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/RepeatedlyRequestWrapper.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..9e3cbfd --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,65 @@ +package com.agileboot.infrastructure.filter; + +import cn.hutool.extra.servlet.ServletUtil; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi valarchie + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { + + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { + super(request); + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + body = ServletUtil.getBodyBytes(request); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public int read() { + return bais.read(); + } + + @Override + public int available() { + return body.length; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/i18n/MessageI18nCheckerRunner.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/i18n/MessageI18nCheckerRunner.java new file mode 100644 index 0000000..b79172e --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/i18n/MessageI18nCheckerRunner.java @@ -0,0 +1,45 @@ +package com.agileboot.infrastructure.i18n; + +import cn.hutool.core.util.ArrayUtil; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.exception.error.ErrorCodeInterface; +import com.agileboot.common.utils.i18n.MessageUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +/** + * 检测 未添加到i18n文件(messages.properties)中的message + * @author valarchie + */ +@Component +@Slf4j +public class MessageI18nCheckerRunner implements ApplicationRunner { + + public static Object[] allErrorCodes = ArrayUtil.addAll( + ErrorCode.Internal.values(), + ErrorCode.External.values(), + ErrorCode.Client.values(), + ErrorCode.Business.values()); + + @Override + public void run(ApplicationArguments args) { + checkEveryMessage(); + } + + /** + * 如果想支持i18n, 请把对应的错误码描述填到 /resources/i18n/messages.properties 文件中 + */ + public void checkEveryMessage() { + for (Object errorCode : allErrorCodes) { + ErrorCodeInterface errorInterface = (ErrorCodeInterface)errorCode; + try { + String message = MessageUtils.message(errorInterface.i18n()); + } catch (Exception e) { + log.warn(" in the file /resources/i18n/messages.properties, could not find i18n message for:" + + errorInterface.i18n()); + } + } + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/exception/GlobalExceptionHandler.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..1e58f37 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/exception/GlobalExceptionHandler.java @@ -0,0 +1,126 @@ +package com.agileboot.infrastructure.interceptor.exception; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.exception.error.ErrorCode.Business; +import com.agileboot.common.exception.error.ErrorCode.Client; +import com.agileboot.common.exception.error.ErrorCode.Internal; +import com.google.common.util.concurrent.UncheckedExecutionException; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理器 + * + * @author valarchie + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + /** + * 权限校验异常 + */ + @ExceptionHandler(AccessDeniedException.class) + public ResponseDTO handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + return ResponseDTO.fail(Business.NO_PERMISSION_TO_OPERATE); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResponseDTO handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return ResponseDTO.fail(Client.COMMON_REQUEST_METHOD_INVALID); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ApiException.class) + public ResponseDTO handleServiceException(ApiException e, HttpServletRequest request) { + if (e.getErrorCode() == ErrorCode.Internal.DB_INTERNAL_ERROR) { + String sensitiveInfoBegin = "### The error may exist"; + String nonSensitiveMsg = StrUtil.subBefore(e.getMessage(), sensitiveInfoBegin, false); + return ResponseDTO.fail(e.getErrorCode(), nonSensitiveMsg); + } + log.error(e.getMessage(), e); + return ResponseDTO.fail(e); + } + + /** + * 捕获缓存类当中的错误 + */ + @ExceptionHandler(UncheckedExecutionException.class) + public ResponseDTO handleServiceException(UncheckedExecutionException e, HttpServletRequest request) { + log.error(e.getMessage(), e); + return ResponseDTO.fail(Internal.DB_INTERNAL_ERROR, e.getMessage()); + } + + + /** + * 捕获DB层当中的错误 + */ +// @ExceptionHandler(BadSqlGrammarException.class) + public ResponseDTO handleServiceException(BadSqlGrammarException e, HttpServletRequest request) { + log.error(e.getMessage(), e); + return ResponseDTO.fail(Internal.DB_INTERNAL_ERROR, e.getMessage()); + } + + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public ResponseDTO handleRuntimeException(RuntimeException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return ResponseDTO.fail(Internal.UNKNOWN_ERROR, e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public ResponseDTO handleException(Exception e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return ResponseDTO.fail(Internal.UNKNOWN_ERROR, e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public ResponseDTO handleBindException(BindException e) { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return ResponseDTO.fail(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return ResponseDTO.fail(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, message); + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/AbstractRepeatSubmitInterceptor.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/AbstractRepeatSubmitInterceptor.java new file mode 100644 index 0000000..a2b1d82 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/AbstractRepeatSubmitInterceptor.java @@ -0,0 +1,44 @@ +package com.agileboot.infrastructure.interceptor.repeatSubmit; + +import cn.hutool.json.JSONUtil; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.error.ErrorCode.Client; +import com.agileboot.common.utils.ServletHolderUtil; +import com.agileboot.infrastructure.annotations.RepeatSubmit; +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +/** + * 防止重复提交拦截器 + * + * @author ruoyi + */ +@Component +public abstract class AbstractRepeatSubmitInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + if (annotation != null) { + if (this.isRepeatSubmit(request, annotation)) { + ResponseDTO responseDTO = ResponseDTO.fail(Client.COMMON_REQUEST_TO_OFTEN); + ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(responseDTO)); + return false; + } + } + } + return true; + } + + /** + * 验证是否重复提交由子类实现具体的防重复提交的规则 + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/RepeatRequest.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/RepeatRequest.java new file mode 100644 index 0000000..f6bfd05 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/RepeatRequest.java @@ -0,0 +1,30 @@ +package com.agileboot.infrastructure.interceptor.repeatSubmit; + +import java.util.Objects; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +public class RepeatRequest { + + private String repeatParams; + private Long repeatTime; + + public boolean compareParams(RepeatRequest preRequest) { + if (preRequest == null) { + return false; + } + return Objects.equals(this.repeatParams, preRequest.repeatParams); + } + + + public boolean compareTime(RepeatRequest preRequest, int interval) { + if (preRequest == null) { + return false; + } + return preRequest.getRepeatTime() - this.repeatTime < interval; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/SameUrlDataInterceptorAbstract.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/SameUrlDataInterceptorAbstract.java new file mode 100644 index 0000000..fccce35 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/SameUrlDataInterceptorAbstract.java @@ -0,0 +1,69 @@ +package com.agileboot.infrastructure.interceptor.repeatSubmit; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import com.agileboot.common.utils.jackson.JacksonUtil; +import com.agileboot.infrastructure.annotations.RepeatSubmit; +import com.agileboot.infrastructure.cache.redis.RedisCacheService; +import com.agileboot.infrastructure.filter.RepeatedlyRequestWrapper; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 判断请求url和数据是否和上一次相同, 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 + * + * @author valarchie + */ +@Component +public class SameUrlDataInterceptorAbstract extends AbstractRepeatSubmitInterceptor { + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + @Autowired + private RedisCacheService redisCacheService; + + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) { + String nowParams = ""; + if (request instanceof RepeatedlyRequestWrapper) { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + nowParams = ServletUtil.getBody(repeatedlyRequest); + } + + // body参数为空,获取Parameter的数据 + if (StrUtil.isEmpty(nowParams)) { + // use jackson util to parse is more safe + nowParams = JacksonUtil.to(request.getParameterMap()); + } + + RepeatRequest currentRequest = new RepeatRequest(); + currentRequest.setRepeatParams(nowParams); + currentRequest.setRepeatTime(System.currentTimeMillis()); + + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + + // 唯一值(没有消息头则使用请求地址) + String submitKey = StrUtil.trimToEmpty(request.getHeader(header)); + + // 唯一标识(指定key + url + 消息头) +// String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + url + submitKey; + RepeatRequest preRequest = redisCacheService.repeatSubmitCache.getObjectById(url + submitKey); + if (preRequest != null) { + if (currentRequest.compareParams(preRequest) && + currentRequest.compareTime(preRequest, annotation.interval())) { + return true; + } + } + + redisCacheService.repeatSubmitCache.set(url + submitKey, currentRequest); + return false; + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java new file mode 100644 index 0000000..bc7729b --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java @@ -0,0 +1,254 @@ +package com.agileboot.infrastructure.mybatisplus; + +import com.agileboot.common.core.base.BaseController; +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.config.DataSourceConfig.Builder; +import com.baomidou.mybatisplus.generator.config.OutputFile; +import com.baomidou.mybatisplus.generator.config.StrategyConfig; +import com.baomidou.mybatisplus.generator.config.TemplateType; +import com.baomidou.mybatisplus.generator.config.builder.Entity; +import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; +import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery; +import com.baomidou.mybatisplus.generator.config.rules.DateType; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; +import com.baomidou.mybatisplus.generator.fill.Column; +import com.baomidou.mybatisplus.generator.fill.Property; +import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler; +import java.util.Collections; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +@lombok.Builder +public class CodeGenerator { + + private String author; + private String module; + private String tableName; + private String databaseUrl; + private String username; + private String password; + private String parentPackage; + private Boolean isExtendsFromBaseEntity; + + /** + * 避免覆盖掉原有生成的类 生成的类 放在orm子模块下的/target/generated-code目录底下 + * 有需要更新的实体自己在手动覆盖 或者 挪动过去 + * @param args + */ + public static void main(String[] args) { + + CodeGenerator generator = CodeGenerator.builder() + .databaseUrl("jdbc:mysql://localhost:33066/agileboot") + .username("root") + .password("Wds123123#") + .author("valarchie") + .module("/agileboot-orm/target/generated-code") + .parentPackage("com.agileboot") + .tableName("sys_user") + // 决定是否继承基类 + .isExtendsFromBaseEntity(true) + .build(); + + generator.generateCode(); + } + + public void generateCode() { + FastAutoGenerator generator = FastAutoGenerator.create( + new Builder(databaseUrl, username, password) +// .schema("mybatis-plus") + // all these three options + .dbQuery(new MySqlQuery()) + .typeConvert(new MySqlTypeConvert()) + .keyWordsHandler(new MySqlKeyWordsHandler())); + + globalConfig(generator); + packageConfig(generator); +// templateConfig(generator); + injectionConfig(generator); + strategyConfig(generator); + // 使用Freemarker引擎模板,默认的是Velocity引擎模板 + generator.templateEngine(new VelocityTemplateEngine()); + generator.execute(); + } + + + /** + * 为了避免 覆盖掉service中的方法 + * @param generator 生成器 + */ + private void globalConfig(FastAutoGenerator generator) { + generator.globalConfig( + builder -> { + builder + // override old code of file + .fileOverride() + .outputDir(System.getProperty("user.dir") + module + "/src/main/java") + // use date type under package of java utils + .dateType(DateType.ONLY_DATE) + // 配置生成文件中的author + .author(author) +// .enableKotlin() + // generate swagger annotations. + .enableSwagger() + // 注释日期的格式 + .commentDate("yyyy-MM-dd") + .build(); + }); + } + + + private void packageConfig(FastAutoGenerator generator) { + generator.packageConfig(builder -> { + builder + // parent package name + .parent(parentPackage) + .moduleName("orm") + .entity("entity") + .service("service") + .serviceImpl("service.impl") + .mapper("mapper") + .xml("mapper.xml") + .controller("controller") + .other("other") + // define dir related to OutputFileType(entity,mapper,service,controller,mapper.xml) + .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir") + module + + "/src/main/resources/mapper/system/test")) + .build(); + + }); + } + + private void templateConfig(FastAutoGenerator generator) { + // customization code template. disable if you don't have specific requirement. + generator.templateConfig(builder -> { + builder + .disable(TemplateType.ENTITY) + .entity("/templates/entity.java") + .service("/templates/service.java") + .serviceImpl("/templates/serviceImpl.java") + .mapper("/templates/mapper.java") + .mapperXml("/templates/mapper.xml") + .controller("/templates/controller.java") + .build(); + }); + } + + private void injectionConfig(FastAutoGenerator generator) { + // customization code template. disable if you don't have specific requirement. + generator.injectionConfig(builder -> { + // Customization + builder.beforeOutputFile((tableInfo, objectMap) -> { + System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size()); + }) +// .customMap(Collections.singletonMap("test", "baomidou")) +// .customFile(Collections.singletonMap("test.txt", "/templates/test.vm")) + .build(); + }); + } + + + private void strategyConfig(FastAutoGenerator generator) { + // customization code template. disable if you don't have specific requirement. + generator.strategyConfig(builder -> { + builder + // Global Configuration + .enableCapitalMode() + // does not generate view + .enableSkipView() + .disableSqlFilter() + // filter which tables need to be generated +// .likeTable(new LikeTable("USER")) +// .addInclude("t_simple") +// .addTablePrefix("t_", "c_") +// .addFieldSuffix("_flag") + .addInclude(tableName); + + entityConfig(builder); + controllerConfig(builder); + serviceConfig(builder); + mapperConfig(builder); + }); + } + + + private void entityConfig(StrategyConfig.Builder builder) { + Entity.Builder entityBuilder = builder.entityBuilder(); + + entityBuilder +// .superClass(BaseEntity.class) +// .disableSerialVersionUID() +// .enableChainModel() + .enableLombok() + // boolean field +// .enableRemoveIsPrefix() + .enableTableFieldAnnotation() + // operate entity like JPA. + .enableActiveRecord() +// .versionColumnName("version") +// .versionPropertyName("version") + // deleted的字段设置成tinyint 长度为1 + .logicDeleteColumnName("deleted") +// .logicDeletePropertyName("deleteFlag") + .naming(NamingStrategy.underline_to_camel) + .columnNaming(NamingStrategy.underline_to_camel) + // 如果不需要BaseEntity 请注释掉以下两行 +// .superClass(BaseEntity.class) +// .addSuperEntityColumns("creator_id", "create_time", "creator_name", "updater_id", "update_time", "updater_name", "deleted") +// .addIgnoreColumns("age") + // 两种配置方式 + .addTableFills(new Column("create_time", FieldFill.INSERT)) + .addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE)) + // ID strategy AUTO, NONE, INPUT, ASSIGN_ID, ASSIGN_UUID; + .idType(IdType.AUTO) + .formatFileName("%sEntity"); + + if (isExtendsFromBaseEntity) { + entityBuilder + .superClass(BaseEntity.class) + .addSuperEntityColumns("creator_id", "create_time", "creator_name", "updater_id", "update_time", + "updater_name", "deleted"); + } + + entityBuilder.build(); + } + + + private void controllerConfig(StrategyConfig.Builder builder) { + builder.controllerBuilder() + .superClass(BaseController.class) + .enableHyphenStyle() + .enableRestStyle() + .formatFileName("%sController") + .build(); + } + + private void serviceConfig(StrategyConfig.Builder builder) { + builder.serviceBuilder() +// .superServiceClass(BaseService.class) +// .superServiceImplClass(BaseServiceImpl.class) + .formatServiceFileName("I%sService") + .formatServiceImplFileName("%sServiceImpl") + .build(); + } + + private void mapperConfig(StrategyConfig.Builder builder) { + builder.mapperBuilder() +// .superClass(BaseMapper.class) +// .enableMapperAnnotation() +// .enableBaseResultMap() +// .enableBaseColumnList() +// .cache(MyMapperCache.class) + .formatMapperFileName("%sMapper") + .formatXmlFileName("%sMapper") + .build(); + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CustomMetaObjectHandler.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CustomMetaObjectHandler.java new file mode 100644 index 0000000..6e15634 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CustomMetaObjectHandler.java @@ -0,0 +1,24 @@ +package com.agileboot.infrastructure.mybatisplus; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import java.util.Date; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +/** + * @author valarchie + */ +@Component +public class CustomMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + this.setFieldValByName("createTime", new Date(), metaObject); + } + + @Override + public void updateFill(MetaObject metaObject) { + this.setFieldValByName("updateTime", new Date(), metaObject); + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/RsaKeyPairGenerator.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/RsaKeyPairGenerator.java new file mode 100644 index 0000000..283f8da --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/RsaKeyPairGenerator.java @@ -0,0 +1,19 @@ +package com.agileboot.infrastructure.security; + +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.RSA; + +public class RsaKeyPairGenerator { + public static void main(String[] args) { + + RSA rsa = SecureUtil.rsa(); + + String privateKeyBase64 = rsa.getPrivateKeyBase64(); + String publicKeyBase64 = rsa.getPublicKeyBase64(); + + System.out.println(privateKeyBase64); + System.out.println(publicKeyBase64); + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/filter/JwtAuthenticationTokenFilter.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..c15aec6 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,46 @@ +package com.agileboot.infrastructure.security.filter; + +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.service.TokenService; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * token过滤器 验证token有效性 + * 继承OncePerRequestFilter类的话 可以确保只执行filter一次, 避免执行多次 + * @author ruoyi + */ +@Component +@Slf4j +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { + + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + LoginUser loginUser = tokenService.getLoginUser(request); + if (loginUser != null && AuthenticationUtils.getAuthentication() == null) { + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, + null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + log.info("request process in jwt token filter."); + + chain.doFilter(request, response); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/handle/AuthenticationEntryPointImpl.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..aaa55bd --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,29 @@ +package com.agileboot.infrastructure.security.handle; + +import cn.hutool.json.JSONUtil; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.error.ErrorCode.Client; +import com.agileboot.common.utils.ServletHolderUtil; +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +/** + * 认证失败处理类 返回未授权 + * + * @author ruoyi + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable { + + private static final long serialVersionUID = -8970718410437077606L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { + ResponseDTO responseDTO = ResponseDTO.fail(Client.COMMON_NO_AUTHORIZATION, request.getRequestURI()); + ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(responseDTO)); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/handle/LogoutSuccessHandlerImpl.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 0000000..67925df --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,46 @@ +package com.agileboot.infrastructure.security.handle; + +import cn.hutool.json.JSONUtil; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.utils.ServletHolderUtil; +import com.agileboot.infrastructure.thread.AsyncTaskFactory; +import com.agileboot.infrastructure.thread.ThreadPoolManager; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.service.TokenService; +import com.agileboot.orm.enums.LoginStatusEnum; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +/** + * 自定义退出处理类 返回成功 + * 在SecurityConfig类当中 定义了/logout 路径对应处理逻辑 + * + * @author ruoyi + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { + + @Autowired + private TokenService tokenService; + + /** + * 退出处理 + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + LoginUser loginUser = tokenService.getLoginUser(request); + if (loginUser != null) { + String userName = loginUser.getUsername(); + // 删除用户缓存记录 + tokenService.deleteLoginUser(loginUser.getToken()); + // 记录用户退出日志 + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask( + userName, LoginStatusEnum.LOGOUT, LoginStatusEnum.LOGOUT.description())); + } + ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(ResponseDTO.ok())); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonHtmlXssSerializer.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonHtmlXssSerializer.java new file mode 100644 index 0000000..3553634 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonHtmlXssSerializer.java @@ -0,0 +1,37 @@ +package com.agileboot.infrastructure.security.xss; + +import cn.hutool.http.HtmlUtil; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; + +public class JsonHtmlXssSerializer extends JsonDeserializer { + + public JsonHtmlXssSerializer() { + super(); + } + + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + if( value != null) { + // 去除掉html标签 如果想要转义的话 可使用 HtmlUtil.escape() + return HtmlUtil.cleanHtmlTag(value); + } + return null; + } + + @Override + public Class handledType() { + return String.class; + } + +// @Override +// public void deserialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { +// if (value != null) { +// String escapeStr = HtmlUtil.escape(value); +// gen.writeString(escapeStr); +// } +// } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonXssConfiguration.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonXssConfiguration.java new file mode 100644 index 0000000..8adfc97 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonXssConfiguration.java @@ -0,0 +1,21 @@ +package com.agileboot.infrastructure.security.xss; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +/** + * @author valarchie + */ +@Configuration +public class JsonXssConfiguration { + + @Bean + Jackson2ObjectMapperBuilder objectMapperBuilder() { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); + builder.deserializers(new JsonHtmlXssSerializer()); + return builder; + } + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/AsyncTaskFactory.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/AsyncTaskFactory.java new file mode 100644 index 0000000..e8a6c0a --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/AsyncTaskFactory.java @@ -0,0 +1,76 @@ +package com.agileboot.infrastructure.thread; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.agileboot.common.utils.ServletHolderUtil; +import com.agileboot.common.utils.ip.IpRegionUtil; +import com.agileboot.orm.entity.SysLoginInfoEntity; +import com.agileboot.orm.entity.SysOperationLogEntity; +import com.agileboot.orm.enums.LoginStatusEnum; +import com.agileboot.orm.service.ISysLoginInfoService; +import com.agileboot.orm.service.ISysOperationLogService; +import eu.bitwalker.useragentutils.UserAgent; +import lombok.extern.slf4j.Slf4j; + +/** + * 异步工厂(产生任务用) + * + * @author ruoyi + */ +@Slf4j +public class AsyncTaskFactory { + + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param loginStatusEnum 状态 + * @param message 消息 + * @return 任务task + */ + public static Runnable loginInfoTask(final String username, final LoginStatusEnum loginStatusEnum, final String message) { + // 优化一下这个类 + final UserAgent userAgent = UserAgent.parseUserAgentString( + ServletHolderUtil.getRequest().getHeader("User-Agent")); + // 获取客户端浏览器 + final String browser = userAgent.getBrowser() != null ? userAgent.getBrowser().getName() : ""; + final String ip = ServletUtil.getClientIP(ServletHolderUtil.getRequest()); + final String address = IpRegionUtil.getBriefLocationByIp(ip); + // 获取客户端操作系统 + final String os = userAgent.getOperatingSystem() != null ? userAgent.getOperatingSystem().getName() : ""; + + log.info("ip: {}, address: {}, username: {}, loginStatusEnum: {}, message: {}", ip, address, username, + loginStatusEnum, message); + return () -> { + // 封装对象 + SysLoginInfoEntity loginInfo = new SysLoginInfoEntity(); + loginInfo.setUsername(username); + loginInfo.setIpAddress(ip); + loginInfo.setLoginLocation(address); + loginInfo.setBrowser(browser); + loginInfo.setOperationSystem(os); + loginInfo.setMsg(message); + loginInfo.setLoginTime(DateUtil.date()); + loginInfo.setStatus(loginStatusEnum.getValue()); + // 插入数据 + SpringUtil.getBean(ISysLoginInfoService.class).save(loginInfo); + }; + } + + /** + * 操作日志记录 + * + * @param operationLog 操作日志信息 + * @return 任务task + */ + public static Runnable recordOperationLog(final SysOperationLogEntity operationLog) { + return () -> { + // 远程查询操作地点 + operationLog.setOperatorLocation(IpRegionUtil.getBriefLocationByIp(operationLog.getOperatorIp())); + SpringUtil.getBean(ISysOperationLogService.class).save(operationLog); + }; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ShutdownHook.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ShutdownHook.java new file mode 100644 index 0000000..ea24a42 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ShutdownHook.java @@ -0,0 +1,32 @@ +package com.agileboot.infrastructure.thread; + +import javax.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author ruoyi + */ +@Component +@Slf4j +public class ShutdownHook { + + @PreDestroy + public void destroy() { + shutdownAllThreadPool(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAllThreadPool() { + try { + log.info("close thread pool"); + ThreadPoolManager.shutdown(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadConfig.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadConfig.java new file mode 100644 index 0000000..3d5e794 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadConfig.java @@ -0,0 +1,18 @@ +package com.agileboot.infrastructure.thread; + +public class ThreadConfig { + + public final static int CORE_POOL_SIZE = 50; + + public final static int MAX_POOL_SIZE = 200; + + public final static int QUEUE_CAPACITY = 1000; + + public final static int KEEP_ALIVE_SECONDS = 300; + + /** + * 操作延迟10毫秒 + */ + public final static int OPERATE_DELAY_TIME = 10; + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadPoolManager.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadPoolManager.java new file mode 100644 index 0000000..1e529d1 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadPoolManager.java @@ -0,0 +1,76 @@ +package com.agileboot.infrastructure.thread; + +import java.util.TimerTask; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; + +/** + * 异步任务管理器 + * + * @author valarchie + */ +@Slf4j +public class ThreadPoolManager { + + private final static ThreadPoolExecutor THREAD_EXECUTOR = new ThreadPoolExecutor( + ThreadConfig.CORE_POOL_SIZE, ThreadConfig.MAX_POOL_SIZE, + ThreadConfig.KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, + new SynchronousQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy()); + + private final static ScheduledExecutorService SCHEDULED_EXECUTOR = new ScheduledThreadPoolExecutor( + ThreadConfig.CORE_POOL_SIZE, + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) { + @Override + protected void afterExecute(Runnable r, Throwable t) { + if (t == null && r instanceof Future) { + try { + Future future = (Future) r; + if (future.isDone()) { + future.get(); + } + } catch (CancellationException ce) { + t = ce; + } catch (ExecutionException ee) { + t = ee.getCause(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + if (t != null) { + log.error(t.getMessage(), t); + } + } + }; + + + /** + * 执行schedule任务 + */ + public static void schedule(TimerTask task) { + SCHEDULED_EXECUTOR.schedule(task, ThreadConfig.OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 执行异步任务任务 + */ + public static void execute(Runnable task) { + THREAD_EXECUTOR.execute(task); + } + + /** + * 停止任务线程池 + */ + public static void shutdown() { + THREAD_EXECUTOR.shutdown(); + SCHEDULED_EXECUTOR.shutdown(); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/OnlineUser.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/OnlineUser.java new file mode 100644 index 0000000..0fbcc84 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/OnlineUser.java @@ -0,0 +1,53 @@ +package com.agileboot.infrastructure.web.domain; + +import lombok.Data; + +/** + * 当前在线会话 + * + * @author ruoyi + */ +@Data +public class OnlineUser { + + /** + * 会话编号 + */ + private String tokenId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 用户名称 + */ + private String userName; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地址 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 登录时间 + */ + private Long loginTime; + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/CaptchaDTO.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/CaptchaDTO.java new file mode 100644 index 0000000..5620408 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/CaptchaDTO.java @@ -0,0 +1,15 @@ +package com.agileboot.infrastructure.web.domain.login; + +import lombok.Data; + +/** + * @author valarchie + */ +@Data +public class CaptchaDTO { + + private Boolean isCaptchaOn; + private String uuid; + private String img; + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/LoginInfo.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/LoginInfo.java new file mode 100644 index 0000000..68ff8d5 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/LoginInfo.java @@ -0,0 +1,28 @@ +package com.agileboot.infrastructure.web.domain.login; + +import lombok.Data; + +@Data +public class LoginInfo { + + /** + * 登录IP地址 + */ + private String ipAddress; + + /** + * 登录地点 + */ + private String location; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String operationSystem; + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/LoginUser.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/LoginUser.java new file mode 100644 index 0000000..9ca2507 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/LoginUser.java @@ -0,0 +1,129 @@ +package com.agileboot.infrastructure.web.domain.login; + +import com.agileboot.common.core.base.BaseUser; +import com.agileboot.orm.entity.SysUserEntity; +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.Collection; +import java.util.Set; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * 登录用户身份权限 + * @author valarchie + */ +@Data +@NoArgsConstructor +public class LoginUser extends BaseUser implements UserDetails { + + private static final long serialVersionUID = 1L; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + + private LoginInfo loginInfo = new LoginInfo(); + + /** + * 权限列表 + */ + private Set menuPermissions; + + private String roleKey; + + private SysUserEntity entity; + + + public LoginUser(Set permissions) { + this.menuPermissions = permissions; + } + + public LoginUser(SysUserEntity entity, String roleKey, Set permissions) { + setUsername(entity.getUsername()); + setUserId(entity.getUserId()); + setDeptId(entity.getDeptId()); + setRoleId(entity.getRoleId()); + this.menuPermissions = permissions; + this.roleKey = roleKey; + this.entity = entity; + } + + @JsonIgnore + @Override + public String getPassword() { + return entity.getPassword(); + } + + + /** + * 账户是否未过期,过期无法验证 + * 未实现此功能 + */ + @JsonIgnore + @Override + public boolean isAccountNonExpired() { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * 未实现此功能 + */ + @JsonIgnore + @Override + public boolean isAccountNonLocked() { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * 未实现此功能 + */ + @JsonIgnore + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * 未实现此功能 + */ + @JsonIgnore + @Override + public boolean isEnabled() { + return true; + } + + @Override + public Collection getAuthorities() { + return null; + } + + /** + * 是否为管理员 + * @return 结果 + */ + public boolean isAdmin() { + return isAdmin(getUserId()); + } + + public static boolean isAdmin(Long userId) { + return userId != null && 1L == userId; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/Role.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/Role.java new file mode 100644 index 0000000..477ba96 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/Role.java @@ -0,0 +1,37 @@ +package com.agileboot.infrastructure.web.domain.login; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.enums.DataScopeEnum; +import com.agileboot.orm.enums.interfaces.BasicEnumUtil; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Role { + + public Role(SysRoleEntity entity) { + if (entity != null) { + this.roleId = entity.getRoleId(); + this.roleName = entity.getRoleName(); + this.dataScope = BasicEnumUtil.fromValue(DataScopeEnum.class, entity.getDataScope()); + + if(StrUtil.isNotEmpty(entity.getDeptIdSet())) { + this.deptIdSet = StrUtil.split(entity.getDeptIdSet(), ",").stream() + .map(Convert::toLong).collect( Collectors.toSet()); + } + } + } + + private Long roleId; + private String roleName; + private DataScopeEnum dataScope; + private Set deptIdSet; + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/CpuInfo.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/CpuInfo.java new file mode 100644 index 0000000..c78e808 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/CpuInfo.java @@ -0,0 +1,64 @@ +package com.agileboot.infrastructure.web.domain.server; + +import cn.hutool.core.util.NumberUtil; +import lombok.Data; + +/** + * CPU相关信息 + * + * @author ruoyi + */ +@Data +public class CpuInfo { + + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public double getTotal() { + return NumberUtil.round(total * 100, 2).doubleValue(); + } + + public double getSys() { + return NumberUtil.div(sys * 100, total, 2); + } + + public double getUsed() { + return NumberUtil.div(used * 100, total, 2); + } + + public double getWait() { + return NumberUtil.div(wait * 100, total, 2); + } + + public double getFree() { + return NumberUtil.div(free * 100, total, 2); + } +} + diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/DiskInfo.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/DiskInfo.java new file mode 100644 index 0000000..02ae414 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/DiskInfo.java @@ -0,0 +1,48 @@ +package com.agileboot.infrastructure.web.domain.server; + +import lombok.Data; + +/** + * 系统文件相关信息 + * + * @author ruoyi + */ +@Data +public class DiskInfo { + + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/JvmInfo.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/JvmInfo.java new file mode 100644 index 0000000..2df6737 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/JvmInfo.java @@ -0,0 +1,92 @@ +package com.agileboot.infrastructure.web.domain.server; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.NumberUtil; +import com.agileboot.common.constant.Constants; +import java.lang.management.ManagementFactory; +import lombok.Data; + +/** + * JVM相关信息 + * + * @author ruoyi + */ +@Data +public class JvmInfo { + + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + public double getTotal() { + return NumberUtil.div(total, Constants.MB, 2); + } + + public double getMax() { + return NumberUtil.div(max, Constants.MB, 2); + } + + public double getFree() { + return NumberUtil.div(free, Constants.MB, 2); + } + + public double getUsed() { + return NumberUtil.div(total - free, Constants.MB, 2); + } + + public double getUsage() { + return NumberUtil.div((total - free) * 100, total, 2); + } + + /** + * 获取JDK名称 + */ + public String getName() { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + /** + * JDK启动时间 + */ + public String getStartTime() { + return DateUtil.format(DateUtil.date(ManagementFactory.getRuntimeMXBean().getStartTime()), + DatePattern.NORM_DATETIME_PATTERN); + } + + /** + * JDK运行时间 + */ + public String getRunTime() { + return DateUtil.formatBetween(DateUtil.date(ManagementFactory.getRuntimeMXBean().getStartTime()), + DateUtil.date()); + } + + /** + * 运行参数 + */ + public String getInputArgs() { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/MemoryInfo.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/MemoryInfo.java new file mode 100644 index 0000000..6cf096e --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/MemoryInfo.java @@ -0,0 +1,45 @@ +package com.agileboot.infrastructure.web.domain.server; + +import cn.hutool.core.util.NumberUtil; +import com.agileboot.common.constant.Constants; +import lombok.Data; + +/** + * 內存相关信息 + * + * @author valarchie + */ +@Data +public class MemoryInfo { + + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() { + return NumberUtil.div(total, Constants.GB, 2); + } + + public double getUsed() { + return NumberUtil.div(used, Constants.GB, 2); + } + + public double getFree() { + return NumberUtil.div(free, Constants.GB, 2); + } + + public double getUsage() { + return NumberUtil.div(used * 100, total, 2); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/ServerInfo.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/ServerInfo.java new file mode 100644 index 0000000..6e7c75d --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/ServerInfo.java @@ -0,0 +1,175 @@ +package com.agileboot.infrastructure.web.domain.server; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.NumberUtil; +import com.agileboot.common.constant.Constants; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import lombok.Data; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +/** + * 服务器相关信息 + * + * @author ruoyi + * @author valarchie + */ +@Data +public class ServerInfo { + + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private CpuInfo cpuInfo = new CpuInfo(); + + /** + * 內存相关信息 + */ + private MemoryInfo memoryInfo = new MemoryInfo(); + + /** + * JVM相关信息 + */ + private JvmInfo jvmInfo = new JvmInfo(); + + /** + * 服务器相关信息 + */ + private SystemInfo systemInfo = new SystemInfo(); + + /** + * 磁盘相关信息 + */ + private List diskInfos = new LinkedList(); + + public static ServerInfo fillInfo() { + ServerInfo serverInfo = new ServerInfo(); + + oshi.SystemInfo si = new oshi.SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + + serverInfo.fillCpuInfo(hal.getProcessor()); + serverInfo.fillMemoryInfo(hal.getMemory()); + serverInfo.fillSystemInfo(); + serverInfo.fillJvmInfo(); + serverInfo.fillDiskInfos(si.getOperatingSystem()); + + return serverInfo; + } + + /** + * 设置CPU信息 + */ + private void fillCpuInfo(CentralProcessor processor) { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softIrq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long ioWait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + ioWait + irq + softIrq + steal; + cpuInfo.setCpuNum(processor.getLogicalProcessorCount()); + cpuInfo.setTotal(totalCpu); + cpuInfo.setSys(cSys); + cpuInfo.setUsed(user); + cpuInfo.setWait(ioWait); + cpuInfo.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void fillMemoryInfo(GlobalMemory memory) { + memoryInfo.setTotal(memory.getTotal()); + memoryInfo.setUsed(memory.getTotal() - memory.getAvailable()); + memoryInfo.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void fillSystemInfo() { + Properties props = System.getProperties(); + + systemInfo.setComputerName(NetUtil.getLocalHostName()); + systemInfo.setComputerIp(NetUtil.getLocalhost().getHostAddress()); + systemInfo.setOsName(props.getProperty("os.name")); + systemInfo.setOsArch(props.getProperty("os.arch")); + systemInfo.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void fillJvmInfo() { + Properties props = System.getProperties(); + jvmInfo.setTotal(Runtime.getRuntime().totalMemory()); + jvmInfo.setMax(Runtime.getRuntime().maxMemory()); + jvmInfo.setFree(Runtime.getRuntime().freeMemory()); + jvmInfo.setVersion(props.getProperty("java.version")); + jvmInfo.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void fillDiskInfos(OperatingSystem os) { + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + DiskInfo diskInfo = new DiskInfo(); + diskInfo.setDirName(fs.getMount()); + diskInfo.setSysTypeName(fs.getType()); + diskInfo.setTypeName(fs.getName()); + diskInfo.setTotal(convertFileSize(total)); + diskInfo.setFree(convertFileSize(free)); + diskInfo.setUsed(convertFileSize(used)); + diskInfo.setUsage(NumberUtil.div(used * 100, total, 4)); + diskInfos.add(diskInfo); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) { + if (size >= Constants.GB) { + return String.format("%.1f GB", (float) size / Constants.GB); + } + + if (size >= Constants.MB) { + float ratio = (float) size / Constants.MB; + return String.format(ratio > 100 ? "%.0f MB" : "%.1f MB", Constants.MB); + } + + if (size >= Constants.KB) { + float ratio = (float) size / Constants.KB; + return String.format(ratio > 100 ? "%.0f KB" : "%.1f KB", Constants.KB); + } + + return String.format("%d B", size); + + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/SystemInfo.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/SystemInfo.java new file mode 100644 index 0000000..841d382 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/SystemInfo.java @@ -0,0 +1,38 @@ +package com.agileboot.infrastructure.web.domain.server; + +import lombok.Data; + +/** + * 系统相关信息 + * + * @author ruoyi + */ +@Data +public class SystemInfo { + + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/LoginService.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/LoginService.java new file mode 100644 index 0000000..469e9a2 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/LoginService.java @@ -0,0 +1,203 @@ +package com.agileboot.infrastructure.web.service; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.extra.servlet.ServletUtil; +import com.agileboot.common.config.AgileBootConfig; +import com.agileboot.common.constant.Constants.Captcha; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.utils.ServletHolderUtil; +import com.agileboot.common.utils.i18n.MessageUtils; +import com.agileboot.infrastructure.cache.guava.GuavaCacheService; +import com.agileboot.infrastructure.cache.redis.RedisCacheService; +import com.agileboot.infrastructure.thread.AsyncTaskFactory; +import com.agileboot.infrastructure.thread.ThreadPoolManager; +import com.agileboot.infrastructure.web.domain.login.CaptchaDTO; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.enums.LoginStatusEnum; +import com.agileboot.orm.enums.SystemConfigEnum; +import com.google.code.kaptcha.Producer; +import java.awt.image.BufferedImage; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import org.springframework.util.FastByteArrayOutputStream; + +/** + * 登录校验方法 + * + * @author ruoyi + */ +@Component +@Slf4j +public class LoginService { + + private final TokenService tokenService; + + private final RedisCacheService redisCacheService; + + private final GuavaCacheService guavaCacheService; + + @Resource + private AuthenticationManager authenticationManager; + + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + public LoginService(TokenService tokenService, + RedisCacheService redisCacheService, GuavaCacheService guavaCacheService) { + this.tokenService = tokenService; + this.redisCacheService = redisCacheService; + this.guavaCacheService = guavaCacheService; + } + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) { + // 验证码开关 + if (isCaptchaOn()) { + validateCaptcha(username, code, uuid); + } + // 用户验证 + Authentication authentication = null; + try { + + byte[] decryptBytes = SecureUtil.rsa(AgileBootConfig.getRsaPrivateKey(), null) + .decrypt(Base64.decode(password), KeyType.PrivateKey); + + String decryptPassword = StrUtil.str(decryptBytes, CharsetUtil.CHARSET_UTF_8); + + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(username, decryptPassword)); + } catch (Exception e) { + if (e instanceof BadCredentialsException) { + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL, + MessageUtils.message("user.password.not.match"))); + throw new ApiException(ErrorCode.Business.LOGIN_WRONG_USER_PASSWORD); + } else { + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL, e.getMessage())); + throw new ApiException(e.getCause(), ErrorCode.Business.LOGIN_ERROR, e.getMessage()); + } + } + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_SUCCESS, + LoginStatusEnum.LOGIN_SUCCESS.description())); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 获取验证码 data + * @return + */ + public CaptchaDTO getCaptchaImg() { + CaptchaDTO captchaDTO = new CaptchaDTO(); + + boolean isCaptchaOn = isCaptchaOn(); + captchaDTO.setIsCaptchaOn(isCaptchaOn); + + if (isCaptchaOn) { + String expression, answer = null; + BufferedImage image = null; + + // 生成验证码 + String captchaType = AgileBootConfig.getCaptchaType(); + if (Captcha.MATH_TYPE.equals(captchaType)) { + String capText = captchaProducerMath.createText(); + String[] expressionAndAnswer = capText.split("@"); + expression = expressionAndAnswer[0]; + answer = expressionAndAnswer[1]; + image = captchaProducerMath.createImage(expression); + } + + if (Captcha.CHAR_TYPE.equals(captchaType)) { + expression = answer = captchaProducer.createText(); + image = captchaProducer.createImage(expression); + } + + if (image == null) { + throw new ApiException(ErrorCode.Internal.LOGIN_CAPTCHA_GENERATE_FAIL); + } + + // 保存验证码信息 + String uuid = IdUtil.simpleUUID(); + + redisCacheService.captchaCache.set(uuid, answer); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + ImgUtil.writeJpg(image, os); + + captchaDTO.setUuid(uuid); + captchaDTO.setImg(Base64.encode(os.toByteArray())); + + } + + return captchaDTO; + } + + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + */ + public void validateCaptcha(String username, String code, String uuid) { + String captcha = redisCacheService.captchaCache.getObjectById(uuid); + redisCacheService.captchaCache.delete(uuid); + if (captcha == null) { + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL, + ErrorCode.Business.CAPTCHA_CODE_EXPIRE.message())); + throw new ApiException(ErrorCode.Business.CAPTCHA_CODE_EXPIRE); + } + if (!code.equalsIgnoreCase(captcha)) { + ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL, + ErrorCode.Business.CAPTCHA_CODE_WRONG.message())); + throw new ApiException(ErrorCode.Business.CAPTCHA_CODE_WRONG); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) { + SysUserEntity entity = new SysUserEntity(); + entity.setUserId(userId); + entity.setLoginIp(ServletUtil.getClientIP(ServletHolderUtil.getRequest())); + entity.setLoginDate(DateUtil.date()); + entity.updateById(); + } + + private boolean isCaptchaOn() { + return Convert.toBool(guavaCacheService.configCache.get(SystemConfigEnum.CAPTCHA.getValue())); + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/PermissionService.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/PermissionService.java new file mode 100644 index 0000000..12dd647 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/PermissionService.java @@ -0,0 +1,160 @@ +package com.agileboot.infrastructure.web.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.agileboot.infrastructure.cache.guava.GuavaCacheService; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.infrastructure.web.domain.login.Role; +import com.agileboot.infrastructure.web.util.AuthenticationUtils; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.enums.DataScopeEnum; +import com.agileboot.orm.service.ISysDeptService; +import com.agileboot.orm.service.ISysUserService; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * @author ruoyi valarchie + */ +@Service("ss") +public class PermissionService { + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysUserService userService; + + @Autowired + private GuavaCacheService guavaCacheService; + + /** + * 所有权限标识 + */ + private static final String ALL_PERMISSION = "*:*:*"; + + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPerm(String permission) { + if (StrUtil.isEmpty(permission)) { + return false; + } + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + if (loginUser == null || CollUtil.isEmpty(loginUser.getMenuPermissions())) { + return false; + } + return hasPermissions(loginUser.getMenuPermissions(), permission); + } + + + /** + * 通过userId 校验当前用户 对 目标用户是否有操作权限 + * @param userId + * @return + */ + public boolean checkDataScopeWithUserId(Long userId) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + + if (loginUser == null) { + return false; + } + Role role = guavaCacheService.roleCache.get(loginUser.getRoleId() + ""); + SysUserEntity targetUser = userService.getById(userId); + + return checkDataScope(loginUser, role, targetUser.getDeptId(), userId); + } + + /** + * 通过userId 校验当前用户 对 目标用户是否有操作权限 + * @param userIds + * @return + */ + public boolean checkDataScopeWithUserIds(List userIds) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + + if (loginUser == null) { + return false; + } + Role role = guavaCacheService.roleCache.get(loginUser.getRoleId() + ""); + + if (CollUtil.isNotEmpty(userIds)) { + for (Long userId : userIds) { + SysUserEntity targetUser = userService.getById(userId); + boolean checkResult = checkDataScope(loginUser, role, targetUser.getDeptId(), userId); + if (!checkResult) { + return false; + } + } + } + + return true; + } + + public boolean checkDataScopeWithDeptId(Long deptId) { + LoginUser loginUser = AuthenticationUtils.getLoginUser(); + if (loginUser == null) { + return false; + } + Role role = guavaCacheService.roleCache.get(loginUser.getRoleId() + ""); + + return checkDataScope(loginUser, role, deptId, null); + } + + public boolean checkDataScope(LoginUser loginUser, Role role, Long targetDeptId, Long targetUserId) { + + if (targetDeptId == null && role.getDataScope() != DataScopeEnum.ALL) { + return false; + } + + if(role.getDataScope() == DataScopeEnum.ALL) { + return true; + } + + if (role.getDataScope() == DataScopeEnum.SELF_DEFINE && + CollUtil.safeContains(role.getDeptIdSet(), targetDeptId)) { + return true; + } + + if(role.getDataScope() == DataScopeEnum.CURRENT_DEPT && Objects.equals(loginUser.getDeptId(), targetDeptId)) { + return true; + } + + if (role.getDataScope() == DataScopeEnum.CURRENT_DEPT_AND_CHILDREN_DEPT && + deptService.isChildOfTargetDeptId(loginUser.getDeptId(), targetDeptId)) { + return true; + } + + if (role.getDataScope() == DataScopeEnum.ONLY_SELF + && targetUserId != null + && Objects.equals(loginUser.getUserId(), targetUserId)) { + return true; + } + + return false; + } + + + + + + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set permissions, String permission) { + return permissions.contains(ALL_PERMISSION) || permissions.contains(StrUtil.trim(permission)); + } +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/TokenService.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/TokenService.java new file mode 100644 index 0000000..eb18ce4 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/TokenService.java @@ -0,0 +1,214 @@ +package com.agileboot.infrastructure.web.service; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import com.agileboot.common.constant.Constants.Token; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.common.utils.ServletHolderUtil; +import com.agileboot.common.utils.ip.IpRegionUtil; +import com.agileboot.infrastructure.cache.redis.CacheKeyEnum; +import com.agileboot.infrastructure.cache.redis.RedisCacheService; +import com.agileboot.infrastructure.web.domain.login.LoginInfo; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * token验证处理 + * + * @author ruoyi + */ +@Component +@Slf4j +public class TokenService { + + /** + * 自定义令牌标识 + */ + @Value("${token.header}") + private String header; + + /** + * 令牌秘钥 + */ + @Value("${token.secret}") + private String secret; + + /** + * 令牌有效期 + */ + @Value("${token.expireTime}") + private int expireTime; + + /** + * 自动刷新token的时间 + */ + @Value("${token.autoRefreshTime}") + private long autoRefreshTime; + + @Autowired + private RedisCacheService redisCacheService; + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) { + // 获取请求携带的令牌 + String token = getToken(request); + if (StrUtil.isNotEmpty(token)) { + try { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Token.LOGIN_USER_KEY); + + return redisCacheService.loginUserCache.getCachedObjectById(uuid); + } catch (Exception e) { + log.error("fail to get cached user from redis", e); + throw new ApiException(e, ErrorCode.UNKNOWN_ERROR); + } + } + return null; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) { + if (loginUser != null && StrUtil.isNotEmpty(loginUser.getToken())) { + refreshToken(loginUser); + } + } + + /** + * 删除用户身份信息 + */ + public void deleteLoginUser(String token) { + if (StrUtil.isNotEmpty(token)) { + redisCacheService.loginUserCache.delete(token); + } + } + + /** + * 创建令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createToken(LoginUser loginUser) { + String token = IdUtil.fastUUID(); + loginUser.setToken(token); + setUserAgent(loginUser); + refreshToken(loginUser); + + Map claims = new HashMap<>(); + claims.put(Token.LOGIN_USER_KEY, token); + return createToken(claims); + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新token + * @param loginUser + */ + public void verifyToken(LoginUser loginUser) { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= TimeUnit.MINUTES.toMillis(autoRefreshTime)) { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + CacheKeyEnum.LOGIN_USER_KEY.timeUnit() + .toMillis(CacheKeyEnum.LOGIN_USER_KEY.expiration())); + // 根据uuid将loginUser缓存 + redisCacheService.loginUserCache.set(loginUser.getToken(), loginUser); + + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) { + UserAgent userAgent = UserAgent.parseUserAgentString(ServletHolderUtil.getRequest().getHeader("User-Agent")); + String ip = ServletUtil.getClientIP(ServletHolderUtil.getRequest()); + if (loginUser.getLoginInfo() == null) { + loginUser.setLoginInfo(new LoginInfo()); + } + loginUser.getLoginInfo().setIpAddress(ip); + loginUser.getLoginInfo().setLocation(IpRegionUtil.getBriefLocationByIp(ip)); + loginUser.getLoginInfo().setBrowser(userAgent.getBrowser().getName()); + loginUser.getLoginInfo().setOperationSystem(userAgent.getOperatingSystem().getName()); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map claims) { + return Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims parseToken(String token) { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + /** + * 从令牌中获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) { + Claims claims = parseToken(token); + return claims.getSubject(); + } + + /** + * 获取请求token + * + * @return token + */ + private String getToken(HttpServletRequest request) { + String token = request.getHeader(header); + if (StrUtil.isNotEmpty(token) && token.startsWith(Token.TOKEN_PREFIX)) { + token = StrUtil.stripIgnoreCase(token, Token.TOKEN_PREFIX, null); + } + return token; + } + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/UserDetailsServiceImpl.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..e663d40 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,90 @@ +package com.agileboot.infrastructure.web.service; + +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.enums.UserStatusEnum; +import com.agileboot.orm.service.ISysUserService; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +/** + * 用户验证处理 + * + * @author ruoyi valarchie + */ +@Service +@Slf4j +public class UserDetailsServiceImpl implements UserDetailsService { + + @Autowired + private ISysUserService userService; + + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + SysUserEntity userEntity = userService.getUserByUserName(username); + if (userEntity == null) { + log.info("登录用户:{} 不存在.", username); + throw new ApiException(ErrorCode.Business.USER_NON_EXIST, username); + } + if (!Objects.equals(UserStatusEnum.NORMAL.getValue(), userEntity.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new ApiException(ErrorCode.Business.USER_IS_DISABLE, username); + } + String roleKey = getRoleKey(userEntity.getUserId()); + Set menuPermissions = getMenuPermissions(userEntity.getUserId()); +// SysRoleEntity roleById = roleService.getById(userEntity.getRoleId()); +// +// Role role = new Role(); +// if(roleById != null) { +// role = new Role(roleById); +// } + + return new LoginUser(userEntity, roleKey, menuPermissions); + } + + /** + * 获取角色数据权限 + * @param userId 用户信息 + * @return 角色权限信息 + */ + public String getRoleKey(Long userId) { + // 管理员拥有所有权限 + if (LoginUser.isAdmin(userId)) { + return "admin"; + } + + SysRoleEntity roleEntity = userService.getRoleOfUser(userId); + return roleEntity == null ? "" : roleEntity.getRoleKey(); + } + + /** + * 获取菜单数据权限 + * + * @param userId 用户信息 + * @return 菜单权限信息 + */ + public Set getMenuPermissions(Long userId) { + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (LoginUser.isAdmin(userId)) { + perms.add("*:*:*"); + } else { + perms.addAll(userService.selectMenuPermsByUserId(userId)); + } + return perms; + } + + + +} diff --git a/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/util/AuthenticationUtils.java b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/util/AuthenticationUtils.java new file mode 100644 index 0000000..b3f6306 --- /dev/null +++ b/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/util/AuthenticationUtils.java @@ -0,0 +1,109 @@ +package com.agileboot.infrastructure.web.util; + + +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.infrastructure.web.domain.login.LoginUser; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +/** + * 安全服务工具类 + * + * @author valarchie + */ +public class AuthenticationUtils { + + /** + * 用户ID + **/ + public static Long getUserId() { + try { + return getLoginUser().getUserId(); + } catch (Exception e) { + throw new ApiException(ErrorCode.Business.USER_FAIL_TO_GET_USER_ID); + } + } + + /** + * 获取部门ID + **/ + public static Long getDeptId() { + try { + return getLoginUser().getDeptId(); + } catch (Exception e) { + throw new ApiException(ErrorCode.Business.USER_FAIL_TO_GET_DEPT_ID); + } + } + + /** + * 获取用户账户 + **/ + public static String getUsername() { + try { + return getLoginUser().getUsername(); + } catch (Exception e) { + throw new ApiException(ErrorCode.Business.USER_FAIL_TO_GET_ACCOUNT); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() { + try { + return (LoginUser) getAuthentication().getPrincipal(); + } catch (Exception e) { + throw new ApiException(ErrorCode.Business.USER_FAIL_TO_GET_USER_INFO); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) { + return userId != null && 1L == userId; + } + + /** + * 是否为管理员角色 + * @return 结果 + */ + public static boolean isAdminRole(Long roleId) { + return roleId != null && 1L == roleId; + } +} diff --git a/agileboot-infrastructure/src/main/resources/application-dev.yml b/agileboot-infrastructure/src/main/resources/application-dev.yml new file mode 100644 index 0000000..cd05fdb --- /dev/null +++ b/agileboot-infrastructure/src/main/resources/application-dev.yml @@ -0,0 +1,79 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://localhost:33066/agileboot_copy?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: Wds123123# + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: agileboot + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 49153 + # 数据库索引 + database: 0 + # 密码 + password: redispw + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms diff --git a/agileboot-infrastructure/src/main/resources/application-test.yml b/agileboot-infrastructure/src/main/resources/application-test.yml new file mode 100644 index 0000000..607683f --- /dev/null +++ b/agileboot-infrastructure/src/main/resources/application-test.yml @@ -0,0 +1,79 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: password + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: agileboot + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 + password: + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms diff --git a/agileboot-infrastructure/src/main/resources/application.yml b/agileboot-infrastructure/src/main/resources/application.yml new file mode 100644 index 0000000..dc3e264 --- /dev/null +++ b/agileboot-infrastructure/src/main/resources/application.yml @@ -0,0 +1,130 @@ +# 项目相关配置 +agileboot: + # 名称 + name: AgileBoot + # 版本 + version: 1.0.0 + # 版权年份 + copyright-year: 2022 + # 实例演示开关 + demo-enabled: true + # 文件路径 示例( Windows配置D:/agileboot/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: D:/agileboot/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数组计算 char 字符验证 + captchaType: math + rsaPrivateKey: MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKHoeQr6sIzfsUDMIdXK2FNzqnG+vnnUrTsJGF1f82MJ10cC5Aw53/ntHl+IjCBU0R8KwPVwjJLoDwhaPgO0ktHa3b0l6E+mqIVIniE20Nws3E0mJhFwJC/IxB7JAg8TkuNzflzvZjAO1ACwgkKGxq7Iutz4TocLeCfUkObXwc1fAgMBAAECgYAWwCzqDwnp8bDdkxGaEhPNvi4QJ6ZqRilFZ2TGEiqIGyTl9JEI6sT/QIOJFw3hqSltfDxbAMKwDe221b9rE9+hZhE2rrpwcTKuehob9Z8CObYeUHR9HG7Qb2tYRElvSCWo74iz2zajXAvJLjIE4MPuPYqXC5zOabH+EJ/eaOzVwQJBANmRkMlb+qzp1GWuqFMHP+5MeYhFwUHVX7fxKNA24oHldX8zjPIZ6d3vaRfliTvxOaz1T80acvJkkb7zHBmaW38CQQC+gfF8Lg+nvBY/S3wfOPL8FcntP16jdFhNNZmbOxq72ZmCfl5Zk5cYNBc4rDSrd9Sj4TkLLug+wrK6Wr117P4hAkBOVxnZR2NVy8SM8HzvmJauiZ7hMKzLtbcHlrBpeLnKqALM0JUZv7b0EPa4ghAOI2fvHU2kvrdRDGFmbkdZ+LilAkBnX8eT5MKl+A/yZJmDr7laRNB/poVKGNXZf55Md3P4Pwlnn/6+iLHSdmGrZPZnnOyLyKjVgqyPccLeEGMCXIlBAkAt2OMwss16OH2x79OcfBrabU5iCVbDHg56JYGbWP8KcPfvspxtL/4TdACRsa+yCMcI6L29Q4wn791SEEnE834a + + + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8080 + servlet: + # 应用的访问路径 + context-path: / + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 + +# 日志配置 +logging: + level: + # 记得配置到包名 + com.agileboot: debug + org.springframework: info + +# Spring配置 +spring: + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + # 有些版本的 IDEA 会自动设置properties的编码为IOS-8859-1 请在IDEA配置里设置成UTF8 + encoding: UTF-8 + profiles: + active: test + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 10MB + # 设置总上传的文件大小 + max-request-size: 20MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + # compatible with swagger + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + jackson: + deserialization: + fail-on-unknown-properties: false + serialization: + write-dates-as-timestamps: false + date-format: yyyy-MM-dd HH:mm:ss + + + +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 TODO 记得更换 + secret: sdhfkjshBN6rr32df38 + # 令牌有效期(默认30分钟) + expireTime: 30 + # 自动刷新token的时间 + autoRefreshTime: 20 + +# MyBatis配置 +mybatis-plus: + # 搜索指定包别名 + typeAliasesPackage: com.agileboot.orm.* + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configuration: + cacheEnabled: true + useGeneratedKeys: true + defaultExecutorType: SIMPLE + logImpl: org.apache.ibatis.logging.slf4j.Slf4jImpl + global-config: + refresh: true + + +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /dev-api + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* + + diff --git a/agileboot-infrastructure/src/main/resources/banner.txt b/agileboot-infrastructure/src/main/resources/banner.txt new file mode 100644 index 0000000..d92938d --- /dev/null +++ b/agileboot-infrastructure/src/main/resources/banner.txt @@ -0,0 +1,8 @@ +Application Version: ${agileboot.version} +Spring Boot Version: ${spring-boot.version} + _ _ _ ____ _ + / \ __ _ (_)| | ___ | __ ) ___ ___ | |_ + / _ \ / _` || || | / _ \| _ \ / _ \ / _ \ | __| + / ___ \| (_| || || || __/| |_) || (_) || (_) || |_ + /_/ \_\\__, ||_||_| \___||____/ \___/ \___/ \__| + |___/ diff --git a/agileboot-infrastructure/src/main/resources/i18n/messages.properties b/agileboot-infrastructure/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..5a39cd4 --- /dev/null +++ b/agileboot-infrastructure/src/main/resources/i18n/messages.properties @@ -0,0 +1,46 @@ +#错误消息 有些版本的 IDEA 会自动设置properties的编码为IOS-8859-1 请设置成UTF8 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] + +#10301_DB_INTERNAL_ERROR=数据库异常: {0} + +10001_INVALID_PARAMETER=参数有误 + + +40203_CAPTCHA_CODE_WRONG=验证码错误 +40204_CAPTCHA_CODE_EXPIRE=验证码过期 + diff --git a/agileboot-infrastructure/src/main/resources/logback.xml b/agileboot-infrastructure/src/main/resources/logback.xml new file mode 100644 index 0000000..3100614 --- /dev/null +++ b/agileboot-infrastructure/src/main/resources/logback.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/agileboot-infrastructure/src/main/resources/mybatis/mybatis-config-deprecated.xml b/agileboot-infrastructure/src/main/resources/mybatis/mybatis-config-deprecated.xml new file mode 100644 index 0000000..cd83f8b --- /dev/null +++ b/agileboot-infrastructure/src/main/resources/mybatis/mybatis-config-deprecated.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/agileboot-infrastructure/src/test/java/com/agileboot/framework/config/CaptchaMathTextCreatorTest.java b/agileboot-infrastructure/src/test/java/com/agileboot/framework/config/CaptchaMathTextCreatorTest.java new file mode 100644 index 0000000..1433a96 --- /dev/null +++ b/agileboot-infrastructure/src/test/java/com/agileboot/framework/config/CaptchaMathTextCreatorTest.java @@ -0,0 +1,27 @@ +package com.agileboot.framework.config; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.math.Calculator; +import cn.hutool.core.util.StrUtil; +import com.agileboot.infrastructure.config.captcha.CaptchaMathTextCreator; +import org.junit.Assert; +import org.junit.Test; + +public class CaptchaMathTextCreatorTest { + + @Test + public void test() { + CaptchaMathTextCreator captchaMathTextCreator = new CaptchaMathTextCreator(); + for (int i = 0; i < 50; i++) { + validateExpressionAndResult(captchaMathTextCreator.getText()); + } + } + + private void validateExpressionAndResult(String expression) { + String[] expressionAndResult = expression.split("@"); + Assert.assertEquals(expressionAndResult.length, 2); + System.out.println(expressionAndResult[0] + " answer is " + expressionAndResult[1]); + String safeExpression = StrUtil.removeSuffix(expressionAndResult[0], "=?"); + Assert.assertEquals(Convert.toInt(Calculator.conversion(safeExpression)) + "", expressionAndResult[1]); + } +} diff --git a/agileboot-infrastructure/src/test/java/com/agileboot/orm/enums/interfaces/BasicEnumUtilTest.java b/agileboot-infrastructure/src/test/java/com/agileboot/orm/enums/interfaces/BasicEnumUtilTest.java new file mode 100644 index 0000000..294ef38 --- /dev/null +++ b/agileboot-infrastructure/src/test/java/com/agileboot/orm/enums/interfaces/BasicEnumUtilTest.java @@ -0,0 +1,20 @@ +package com.agileboot.orm.enums.interfaces; + + +import com.agileboot.orm.enums.dictionary.CommonAnswerEnum; +import org.junit.Assert; +import org.junit.Test; + +public class BasicEnumUtilTest { + + @Test + public void testFromValue() { + + CommonAnswerEnum yes = BasicEnumUtil.fromValue(CommonAnswerEnum.class, 1); + CommonAnswerEnum no = BasicEnumUtil.fromValue(CommonAnswerEnum.class, 0); + + Assert.assertEquals(yes.description(), "是"); + Assert.assertEquals(no.description(), "否"); + + } +} diff --git a/agileboot-orm/pom.xml b/agileboot-orm/pom.xml new file mode 100644 index 0000000..eec544c --- /dev/null +++ b/agileboot-orm/pom.xml @@ -0,0 +1,44 @@ + + + + agileboot + com.agileboot + 1.0.0 + + 4.0.0 + + agileboot-orm + + + ORM-DB模块 + + + + + com.agileboot + agileboot-common + + + + + com.baomidou + mybatis-plus-generator + + + + + + + + + src/main/java + + **/*.xml + + + + + + diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysConfigEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysConfigEntity.java new file mode 100644 index 0000000..e5b05a6 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysConfigEntity.java @@ -0,0 +1,64 @@ +package com.agileboot.orm.entity; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 参数配置表 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_config") +@ApiModel(value = "SysConfigEntity对象", description = "参数配置表") +public class SysConfigEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("参数主键") + @TableId(value = "config_id", type = IdType.AUTO) + private Integer configId; + + @ApiModelProperty("配置名称") + @TableField("config_name") + private String configName; + + @ApiModelProperty("配置键名") + @TableField("config_key") + private String configKey; + + @ApiModelProperty("可选的选项") + @TableField("config_options") + private String configOptions; + + @ApiModelProperty("配置值") + @TableField("config_value") + private String configValue; + + @ApiModelProperty("是否允许修改") + @TableField("is_allow_change") + private Boolean isAllowChange; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + + @Override + public Serializable pkVal() { + return this.configId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysDeptEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysDeptEntity.java new file mode 100644 index 0000000..66db0d8 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysDeptEntity.java @@ -0,0 +1,75 @@ +package com.agileboot.orm.entity; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 部门表 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_dept") +@ApiModel(value = "SysDeptEntity对象", description = "部门表") +public class SysDeptEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("部门id") + @TableId(value = "dept_id", type = IdType.AUTO) + private Long deptId; + + @ApiModelProperty("父部门id") + @TableField("parent_id") + private Long parentId; + + @ApiModelProperty("祖级列表") + @TableField("ancestors") + private String ancestors; + + @ApiModelProperty("部门名称") + @TableField("dept_name") + private String deptName; + + @ApiModelProperty("显示顺序") + @TableField("order_num") + private Integer orderNum; + + @TableField("leader_id") + private Long leaderId; + + @ApiModelProperty("负责人") + @TableField("leader_name") + private String leaderName; + + @ApiModelProperty("联系电话") + @TableField("phone") + private String phone; + + @ApiModelProperty("邮箱") + @TableField("email") + private String email; + + @ApiModelProperty("部门状态(0正常 1停用)") + @TableField("`status`") + private Integer status; + + + @Override + public Serializable pkVal() { + return this.deptId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysLoginInfoEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysLoginInfoEntity.java new file mode 100644 index 0000000..3412e72 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysLoginInfoEntity.java @@ -0,0 +1,79 @@ +package com.agileboot.orm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import java.util.Date; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 系统访问记录 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_login_info") +@ApiModel(value = "SysLoginInfoEntity对象", description = "系统访问记录") +public class SysLoginInfoEntity extends Model { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("访问ID") + @TableId(value = "info_id", type = IdType.AUTO) + private Long infoId; + + @ApiModelProperty("用户账号") + @TableField("username") + private String username; + + @ApiModelProperty("登录IP地址") + @TableField("ip_address") + private String ipAddress; + + @ApiModelProperty("登录地点") + @TableField("login_location") + private String loginLocation; + + @ApiModelProperty("浏览器类型") + @TableField("browser") + private String browser; + + @ApiModelProperty("操作系统") + @TableField("operation_system") + private String operationSystem; + + @ApiModelProperty("登录状态(1成功 0失败)") + @TableField("`status`") + private Integer status; + + @ApiModelProperty("提示消息") + @TableField("msg") + private String msg; + + @ApiModelProperty("访问时间") + @TableField("login_time") + private Date loginTime; + + @ApiModelProperty("逻辑删除") + @TableField("deleted") + @TableLogic + private Boolean deleted; + + + @Override + public Serializable pkVal() { + return this.infoId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysMenuEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysMenuEntity.java new file mode 100644 index 0000000..b9d8506 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysMenuEntity.java @@ -0,0 +1,96 @@ +package com.agileboot.orm.entity; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 菜单权限表 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_menu") +@ApiModel(value = "SysMenuEntity对象", description = "菜单权限表") +public class SysMenuEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("菜单ID") + @TableId(value = "menu_id", type = IdType.AUTO) + private Long menuId; + + @ApiModelProperty("菜单名称") + @TableField("menu_name") + private String menuName; + + @ApiModelProperty("父菜单ID") + @TableField("parent_id") + private Long parentId; + + @ApiModelProperty("显示顺序") + @TableField("order_num") + private Integer orderNum; + + @ApiModelProperty("路由地址") + @TableField("path") + private String path; + + @ApiModelProperty("组件路径") + @TableField("component") + private String component; + + @ApiModelProperty("路由参数") + @TableField("`query`") + private String query; + + @ApiModelProperty("是否为外链(1是 0否)") + @TableField("is_external") + private Boolean isExternal; + + @ApiModelProperty("是否缓存(1缓存 0不缓存)") + @TableField("is_cache") + private Boolean isCache; + + @ApiModelProperty("菜单类型(M=1目录 C=2菜单 F=3按钮)") + @TableField("menu_type") + private Integer menuType; + + @ApiModelProperty("菜单状态(1显示 0隐藏)") + @TableField("is_visible") + private Boolean isVisible; + + @ApiModelProperty("菜单状态(0正常 1停用)") + @TableField("`status`") + private Integer status; + + @ApiModelProperty("权限标识") + @TableField("perms") + private String perms; + + @ApiModelProperty("菜单图标") + @TableField("icon") + private String icon; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + + @Override + public Serializable pkVal() { + return this.menuId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysNoticeEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysNoticeEntity.java new file mode 100644 index 0000000..b3bd558 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysNoticeEntity.java @@ -0,0 +1,60 @@ +package com.agileboot.orm.entity; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 通知公告表 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_notice") +@ApiModel(value = "SysNoticeEntity对象", description = "通知公告表") +public class SysNoticeEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("公告ID") + @TableId(value = "notice_id", type = IdType.AUTO) + private Integer noticeId; + + @ApiModelProperty("公告标题") + @TableField("notice_title") + private String noticeTitle; + + @ApiModelProperty("公告类型(1通知 2公告)") + @TableField("notice_type") + private Integer noticeType; + + @ApiModelProperty("公告内容") + @TableField("notice_content") + private String noticeContent; + + @ApiModelProperty("公告状态(1正常 0关闭)") + @TableField("`status`") + private Integer status; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + + @Override + public Serializable pkVal() { + return this.noticeId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysOperationLogEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysOperationLogEntity.java new file mode 100644 index 0000000..bac1eff --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysOperationLogEntity.java @@ -0,0 +1,115 @@ +package com.agileboot.orm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import java.util.Date; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 操作日志记录 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_operation_log") +@ApiModel(value = "SysOperationLogEntity对象", description = "操作日志记录") +public class SysOperationLogEntity extends Model { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("日志主键") + @TableId(value = "operation_id", type = IdType.AUTO) + private Long operationId; + + @ApiModelProperty("业务类型(0其它 1新增 2修改 3删除)") + @TableField("business_type") + private Integer businessType; + + @ApiModelProperty("请求方式") + @TableField("request_method") + private Integer requestMethod; + + @ApiModelProperty("请求模块") + @TableField("request_module") + private String requestModule; + + @ApiModelProperty("请求URL") + @TableField("request_url") + private String requestUrl; + + @ApiModelProperty("调用方法") + @TableField("called_method") + private String calledMethod; + + @ApiModelProperty("操作类别(0其它 1后台用户 2手机端用户)") + @TableField("operator_type") + private Integer operatorType; + + @ApiModelProperty("用户ID") + @TableField("user_id") + private Long userId; + + @ApiModelProperty("操作人员") + @TableField("username") + private String username; + + @ApiModelProperty("操作人员ip") + @TableField("operator_ip") + private String operatorIp; + + @ApiModelProperty("操作地点") + @TableField("operator_location") + private String operatorLocation; + + @ApiModelProperty("部门ID") + @TableField("dept_id") + private Long deptId; + + @ApiModelProperty("部门名称") + @TableField("dept_name") + private String deptName; + + @ApiModelProperty("请求参数") + @TableField("operation_param") + private String operationParam; + + @ApiModelProperty("返回参数") + @TableField("operation_result") + private String operationResult; + + @ApiModelProperty("操作状态(1正常 0异常)") + @TableField("`status`") + private Integer status; + + @ApiModelProperty("错误消息") + @TableField("error_stack") + private String errorStack; + + @ApiModelProperty("操作时间") + @TableField("operation_time") + private Date operationTime; + + @ApiModelProperty("逻辑删除") + @TableField("deleted") + @TableLogic + private Boolean deleted; + + + @Override + public Serializable pkVal() { + return this.operationId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysPostEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysPostEntity.java new file mode 100644 index 0000000..87a3472 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysPostEntity.java @@ -0,0 +1,60 @@ +package com.agileboot.orm.entity; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 岗位信息表 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_post") +@ApiModel(value = "SysPostEntity对象", description = "岗位信息表") +public class SysPostEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("岗位ID") + @TableId(value = "post_id", type = IdType.AUTO) + private Long postId; + + @ApiModelProperty("岗位编码") + @TableField("post_code") + private String postCode; + + @ApiModelProperty("岗位名称") + @TableField("post_name") + private String postName; + + @ApiModelProperty("显示顺序") + @TableField("post_sort") + private Integer postSort; + + @ApiModelProperty("状态(1正常 0停用)") + @TableField("`status`") + private Integer status; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + + @Override + public Serializable pkVal() { + return this.postId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysRoleEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysRoleEntity.java new file mode 100644 index 0000000..981a942 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysRoleEntity.java @@ -0,0 +1,68 @@ +package com.agileboot.orm.entity; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 角色信息表 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_role") +@ApiModel(value = "SysRoleEntity对象", description = "角色信息表") +public class SysRoleEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("角色ID") + @TableId(value = "role_id", type = IdType.AUTO) + private Long roleId; + + @ApiModelProperty("角色名称") + @TableField("role_name") + private String roleName; + + @ApiModelProperty("角色权限字符串") + @TableField("role_key") + private String roleKey; + + @ApiModelProperty("显示顺序") + @TableField("role_sort") + private Integer roleSort; + + @ApiModelProperty("数据范围(1:全部数据权限 2:自定数据权限 3: 本部门数据权限 4: 本部门及以下数据权限 5: 本人权限)") + @TableField("data_scope") + private Integer dataScope; + + @ApiModelProperty("角色所拥有的部门数据权限") + @TableField("dept_id_set") + private String deptIdSet; + + @ApiModelProperty("角色状态(1正常 0停用)") + @TableField("`status`") + private Integer status; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + + @Override + public Serializable pkVal() { + return this.roleId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysRoleMenuEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysRoleMenuEntity.java new file mode 100644 index 0000000..01b4322 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysRoleMenuEntity.java @@ -0,0 +1,44 @@ +package com.agileboot.orm.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 角色和菜单关联表 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_role_menu") +@ApiModel(value = "SysRoleMenuXEntity对象", description = "角色和菜单关联表") +public class SysRoleMenuEntity extends Model { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("角色ID") + @TableId(value = "role_id", type = IdType.AUTO) + private Long roleId; + + @ApiModelProperty("菜单ID") + @TableField("menu_id") + private Long menuId; + + + @Override + public Serializable pkVal() { + return this.menuId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysUserEntity.java b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysUserEntity.java new file mode 100644 index 0000000..cbc8003 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/entity/SysUserEntity.java @@ -0,0 +1,101 @@ +package com.agileboot.orm.entity; + +import com.agileboot.common.core.base.BaseEntity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.Serializable; +import java.util.Date; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 用户信息表 + *

+ * + * @author valarchie + * @since 2022-10-02 + */ +@Getter +@Setter +@TableName("sys_user") +@ApiModel(value = "SysUserEntity对象", description = "用户信息表") +public class SysUserEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("用户ID") + @TableId(value = "user_id", type = IdType.AUTO) + private Long userId; + + @ApiModelProperty("职位id") + @TableField("post_id") + private Long postId; + + @ApiModelProperty("角色id") + @TableField("role_id") + private Long roleId; + + @ApiModelProperty("部门ID") + @TableField("dept_id") + private Long deptId; + + @ApiModelProperty("用户账号") + @TableField("username") + private String username; + + @ApiModelProperty("用户昵称") + @TableField("nick_name") + private String nickName; + + @ApiModelProperty("用户类型(00系统用户)") + @TableField("user_type") + private Integer userType; + + @ApiModelProperty("用户邮箱") + @TableField("email") + private String email; + + @ApiModelProperty("手机号码") + @TableField("phone_number") + private String phoneNumber; + + @ApiModelProperty("用户性别(0男 1女 2未知)") + @TableField("sex") + private Integer sex; + + @ApiModelProperty("头像地址") + @TableField("avatar") + private String avatar; + + @ApiModelProperty("密码") + @TableField("`password`") + private String password; + + @ApiModelProperty("帐号状态(1正常 2停用 3冻结)") + @TableField("`status`") + private Integer status; + + @ApiModelProperty("最后登录IP") + @TableField("login_ip") + private String loginIp; + + @ApiModelProperty("最后登录时间") + @TableField("login_date") + private Date loginDate; + + @ApiModelProperty("备注") + @TableField("remark") + private String remark; + + + @Override + public Serializable pkVal() { + return this.userId; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/BusinessType.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/BusinessType.java new file mode 100644 index 0000000..438c947 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/BusinessType.java @@ -0,0 +1,74 @@ +package com.agileboot.orm.enums; + +import com.agileboot.orm.enums.interfaces.BasicEnum; + +/** + * 业务操作类型 + * + * @author valarchie + */ +public enum BusinessType implements BasicEnum { + /** + * 其它 + */ + OTHER(0, "其他操作"), + + /** + * 新增 + */ + INSERT(1, "新增"), + + /** + * 修改 + */ + UPDATE(2, "修改"), + + /** + * 删除 + */ + DELETE(3, "删除"), + + /** + * 授权 + */ + GRANT(4, "授权"), + + /** + * 导出 + */ + EXPORT(5, "导出"), + + /** + * 导入 + */ + IMPORT(6, "导入"), + + /** + * 强退 + */ + FORCE(7,"强退"), + + /** + * 清空数据 + */ + CLEAN(8, "清空"), + ; + + private final int value; + private final String description; + + BusinessType(int value, String description) { + this.value = value; + this.description = description; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/DataScopeEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/DataScopeEnum.java new file mode 100644 index 0000000..7ae0faf --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/DataScopeEnum.java @@ -0,0 +1,42 @@ +package com.agileboot.orm.enums; + +import com.agileboot.orm.enums.interfaces.BasicEnum; + +/** + * 对应sys_role表的data_scope字段 + * @author valarchie + */ +public enum DataScopeEnum implements BasicEnum { + + /** + * 数据权限范围 + */ + ALL(1, "所有数据权限"), + SELF_DEFINE(2, "自定义数据权限"), + CURRENT_DEPT(3, "本部门数据权限"), + CURRENT_DEPT_AND_CHILDREN_DEPT(4, "本部门以及子孙部门数据权限"), + ONLY_SELF(5, "仅本人数据权限"); + + private final int value; + private final String description; + + DataScopeEnum(int value, String description) { + this.value = value; + this.description = description; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + public String getDescription() { + return description; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/LoginStatusEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/LoginStatusEnum.java new file mode 100644 index 0000000..b575431 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/LoginStatusEnum.java @@ -0,0 +1,36 @@ +package com.agileboot.orm.enums; + +import com.agileboot.orm.enums.interfaces.BasicEnum; + +/** + * 用户状态 + * + * @author valarchie + */ +public enum LoginStatusEnum implements BasicEnum { + /** + * status of user + */ + LOGIN_SUCCESS(1, "登录成功"), + LOGOUT(2, "退出成功"), + REGISTER(3, "注册"), + LOGIN_FAIL(0, "登录失败"); + + private final int value; + private final String msg; + + LoginStatusEnum(int status, String msg) { + this.value = status; + this.msg = msg; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return msg; + } +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/MenuComponentEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/MenuComponentEnum.java new file mode 100644 index 0000000..ccae489 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/MenuComponentEnum.java @@ -0,0 +1,34 @@ +package com.agileboot.orm.enums; + +import com.agileboot.orm.enums.interfaces.BasicEnum; + +/** + * @author valarchie + */ +public enum MenuComponentEnum implements BasicEnum { + + /** + * 菜单组件类型 + */ + LAYOUT(1,"Layout"), + PARENT_VIEW(2,"ParentView"), + INNER_LINK(3,"InnerLink"); + + private final int value; + private final String description; + + MenuComponentEnum(int value, String description) { + this.value = value; + this.description = description; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/MenuTypeEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/MenuTypeEnum.java new file mode 100644 index 0000000..2fb5cd6 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/MenuTypeEnum.java @@ -0,0 +1,37 @@ +package com.agileboot.orm.enums; + +import com.agileboot.orm.enums.interfaces.BasicEnum; + +/** + * @author valarchie + * 对应 sys_menu表的menu_type字段 + */ +public enum MenuTypeEnum implements BasicEnum { + + /** + * 菜单类型 + */ + DIRECTORY(1, "目录"), + MENU(2, "菜单"), + BUTTON(3, "按钮"); + + private final int value; + private final String description; + + MenuTypeEnum(int value, String description) { + this.value = value; + this.description = description; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/OperatorTypeEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/OperatorTypeEnum.java new file mode 100644 index 0000000..f20d34f --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/OperatorTypeEnum.java @@ -0,0 +1,36 @@ +package com.agileboot.orm.enums; + +import com.agileboot.orm.enums.interfaces.BasicEnum; + +/** + * Http Method + */ +public enum OperatorTypeEnum implements BasicEnum { + + /** + * 菜单类型 + */ + OTHER(1, "其他"), + WEB(2, "Web用户"), + MOBILE(3, "手机端用户"); + + private final int value; + private final String description; + + OperatorTypeEnum(int value, String description) { + this.value = value; + this.description = description; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/RequestMethodEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/RequestMethodEnum.java new file mode 100644 index 0000000..27ba4b6 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/RequestMethodEnum.java @@ -0,0 +1,38 @@ +package com.agileboot.orm.enums; + +import com.agileboot.orm.enums.interfaces.BasicEnum; + +/** + * Http Method + */ +public enum RequestMethodEnum implements BasicEnum { + + /** + * 菜单类型 + */ + GET(1, "GET"), + POST(2, "POST"), + PUT(3, "PUT"), + DELETE(4, "DELETE"), + UNKNOWN(-1, "UNKNOWN"); + + private final int value; + private final String description; + + RequestMethodEnum(int value, String description) { + this.value = value; + this.description = description; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/SystemConfigEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/SystemConfigEnum.java new file mode 100644 index 0000000..8709673 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/SystemConfigEnum.java @@ -0,0 +1,40 @@ +package com.agileboot.orm.enums; + +import com.agileboot.orm.enums.interfaces.BasicEnum; + +/** + * 系统配置 + * @author valarchie + * 对应 sys_config表的config_key字段 + */ +public enum SystemConfigEnum implements BasicEnum { + + /** + * 菜单类型 + */ + SKIN_THEME("sys.index.skinName", "系统皮肤主题"), + INIT_PASSWORD("sys.user.initPassword", "初始密码"), + SIDE_BAR_THEME("sys.index.sideTheme", "侧边栏开关"), + CAPTCHA("sys.account.captchaOnOff", "验证码开关"), + REGISTER("sys.account.registerUser", "注册开放功能"); + + private final String value; + private final String description; + + SystemConfigEnum(String value, String description) { + this.value = value; + this.description = description; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/UserStatusEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/UserStatusEnum.java new file mode 100644 index 0000000..bc1fc02 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/UserStatusEnum.java @@ -0,0 +1,32 @@ +package com.agileboot.orm.enums; + +/** + * 对应sys_user的status字段 + * @author valarchie + */ +public enum UserStatusEnum { + + /** + * 用户账户状态 + */ + NORMAL(1, "正常"), + DISABLED(2, "禁用"), + FROZEN(3, "冻结"); + + private final int value; + private final String description; + + UserStatusEnum(int value, String description) { + this.value = value; + this.description = description; + } + + public int getValue() { + return value; + } + + public String getDescription() { + return description; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/BusinessTypeEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/BusinessTypeEnum.java new file mode 100644 index 0000000..4c08630 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/BusinessTypeEnum.java @@ -0,0 +1,51 @@ +package com.agileboot.orm.enums.dictionary; + +import com.agileboot.orm.enums.interfaces.DictionaryEnum; + +/** + * 对应sys_operation_log的business_type + * @author valarchie + */ +public enum BusinessTypeEnum implements DictionaryEnum { + + /** + * 操作类型 + */ + ADD(1, "添加", CssTag.INFO), + MODIFY(2, "修改", CssTag.INFO), + DELETE(3, "删除", CssTag.DANGER), + GRANT(4, "授权", CssTag.PRIMARY), + EXPORT(5, "导出", CssTag.WARNING), + IMPORT(6, "导入", CssTag.WARNING), + FORCE_LOGOUT(7, "强退", CssTag.DANGER); + + private final int value; + private final String description; + private final String cssTag; + + BusinessTypeEnum(int value, String description, String cssTag) { + this.value = value; + this.description = description; + this.cssTag = cssTag; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + @Override + public String cssTag() { + return cssTag; + } + + public static String getDictName() { + return "sys_oper_type"; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CommonAnswerEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CommonAnswerEnum.java new file mode 100644 index 0000000..1ed363d --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CommonAnswerEnum.java @@ -0,0 +1,52 @@ +package com.agileboot.orm.enums.dictionary; + +import com.agileboot.orm.enums.interfaces.DictionaryEnum; + +/** + * 系统内代表是与否的枚举 + * @author valarchie + */ +public enum CommonAnswerEnum implements DictionaryEnum { + /** + * 是与否 + */ + YES(1, "是", CssTag.PRIMARY), + NO(0, "否", CssTag.DANGER); + + private final int value; + private final String description; + private final String cssTag; + + CommonAnswerEnum(int value, String description, String cssTag) { + this.value = value; + this.description = description; + this.cssTag = cssTag; + } + + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + + @Override + public String cssTag() { + return cssTag; + } + + public static CommonAnswerEnum getDefault() { + return YES; + } + + public static String getDictName() { + return "sys_yes_no"; + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CommonStatusEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CommonStatusEnum.java new file mode 100644 index 0000000..1a7b517 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CommonStatusEnum.java @@ -0,0 +1,48 @@ +package com.agileboot.orm.enums.dictionary; + +import com.agileboot.orm.enums.interfaces.DictionaryEnum; + +/** + * 除非表有特殊指明的话,一般用这个枚举代表 status字段 + * @author valarchie + */ +public enum CommonStatusEnum implements DictionaryEnum { + /** + * 开关状态 + */ + ENABLE(1, "正常", CssTag.PRIMARY), + DISABLE(0, "停用", CssTag.DANGER); + + private final int value; + private final String description; + private final String cssTag; + + CommonStatusEnum(int value, String description, String cssTag) { + this.value = value; + this.description = description; + this.cssTag = cssTag; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + @Override + public String cssTag() { + return cssTag; + } + + public static CommonStatusEnum getDefault() { + return ENABLE; + } + + public static String getDictName() { + return "sys_normal_disable"; + } +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CssTag.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CssTag.java new file mode 100644 index 0000000..8b4b8ce --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CssTag.java @@ -0,0 +1,15 @@ +package com.agileboot.orm.enums.dictionary; + +/** + * Css 样式 + */ +public class CssTag { + + public static final String NONE = ""; + public static final String PRIMARY = "primary"; + public static final String DANGER = "danger"; + public static final String WARNING = "warning"; + public static final String SUCCESS = "success"; + public static final String INFO = "info"; + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/GenderEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/GenderEnum.java new file mode 100644 index 0000000..f4f7ce3 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/GenderEnum.java @@ -0,0 +1,52 @@ +package com.agileboot.orm.enums.dictionary; + +import com.agileboot.orm.enums.interfaces.DictionaryEnum; + +/** + * 对应sys_user的sex字段 + * + * @author valarchie + */ +public enum GenderEnum implements DictionaryEnum { + + /** + * 用户性别 + */ + MALE(1, "男", CssTag.NONE), + FEMALE(2, "女", CssTag.NONE), + UNKNOWN(0, "未知", CssTag.NONE); + + private final int value; + private final String description; + private final String cssTag; + + GenderEnum(int value, String description, String cssTag) { + this.value = value; + this.description = description; + this.cssTag = cssTag; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + @Override + public String cssTag() { + return cssTag; + } + + public static GenderEnum getDefault() { + return MALE; + } + + public static String getDictName() { + return "sys_user_sex"; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/NoticeStatusEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/NoticeStatusEnum.java new file mode 100644 index 0000000..1227b71 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/NoticeStatusEnum.java @@ -0,0 +1,49 @@ +package com.agileboot.orm.enums.dictionary; + +import com.agileboot.orm.enums.interfaces.DictionaryEnum; + +/** + * 对应sys_notice的 status字段 + * @author valarchie + */ +public enum NoticeStatusEnum implements DictionaryEnum { + + /** + * 通知状态 + */ + OPEN(1, "正常", CssTag.PRIMARY), + CLOSE(0, "关闭", CssTag.DANGER); + + private final int value; + private final String description; + private final String cssTag; + + NoticeStatusEnum(int value, String description, String cssTag) { + this.value = value; + this.description = description; + this.cssTag = cssTag; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + @Override + public String cssTag() { + return cssTag; + } + + public static NoticeStatusEnum getDefault() { + return OPEN; + } + + public static String getDictName() { + return "sys_notice_status"; + } +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/NoticeTypeEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/NoticeTypeEnum.java new file mode 100644 index 0000000..d20871f --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/NoticeTypeEnum.java @@ -0,0 +1,49 @@ +package com.agileboot.orm.enums.dictionary; + +import com.agileboot.orm.enums.interfaces.DictionaryEnum; + +/** + * 对应sys_notice的 notice_type字段 + * @author valarchie + */ +public enum NoticeTypeEnum implements DictionaryEnum { + + /** + * 通知类型 + */ + NOTIFICATION(1, "通知", CssTag.WARNING), + ANNOUNCEMENT(2, "公告", CssTag.SUCCESS); + + private final int value; + private final String description; + private final String cssTag; + + NoticeTypeEnum(int value, String description, String cssTag) { + this.value = value; + this.description = description; + this.cssTag = cssTag; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + @Override + public String cssTag() { + return cssTag; + } + + public static NoticeTypeEnum getDefault() { + return NOTIFICATION; + } + + public static String getDictName() { + return "sys_notice_type"; + } +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/OperationStatusEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/OperationStatusEnum.java new file mode 100644 index 0000000..f0e26ca --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/OperationStatusEnum.java @@ -0,0 +1,46 @@ +package com.agileboot.orm.enums.dictionary; + +import com.agileboot.orm.enums.interfaces.DictionaryEnum; + +/** + * 对应sys_operation_log的status字段 + * @author valarchie + */ +public enum OperationStatusEnum implements DictionaryEnum { + + /** + * 操作状态 + */ + SUCCESS(1, "成功", CssTag.PRIMARY), + FAIL(0, "失败", CssTag.DANGER); + + private final int value; + private final String description; + private final String cssTag; + + OperationStatusEnum(int value, String description, String cssTag) { + this.value = value; + this.description = description; + this.cssTag = cssTag; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + @Override + public String cssTag() { + return cssTag; + } + + public static String getDictName() { + return "sys_common_status"; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/VisibleStatusEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/VisibleStatusEnum.java new file mode 100644 index 0000000..4a206ba --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/VisibleStatusEnum.java @@ -0,0 +1,50 @@ +package com.agileboot.orm.enums.dictionary; + +import com.agileboot.orm.enums.interfaces.DictionaryEnum; + +/** + * 对应sys_menu表的is_visible字段 + * @author valarchie + */ +public enum VisibleStatusEnum implements DictionaryEnum { + + /** + * 显示与否 + */ + SHOW(1, "显示", CssTag.PRIMARY), + HIDE(0, "隐藏", CssTag.DANGER); + + private final int value; + private final String description; + private final String cssTag; + + VisibleStatusEnum(int value, String description, String cssTag) { + this.value = value; + this.description = description; + this.cssTag = cssTag; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String description() { + return description; + } + + @Override + public String cssTag() { + return cssTag; + } + + public static VisibleStatusEnum getDefault() { + return SHOW; + } + + public static String getDictName() { + return "sys_show_hide"; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/BasicEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/BasicEnum.java new file mode 100644 index 0000000..ece23a2 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/BasicEnum.java @@ -0,0 +1,23 @@ +package com.agileboot.orm.enums.interfaces; + +/** + * @author valarchie + * 普通的枚举 接口 + * @param + */ +public interface BasicEnum{ + + + /** + * @return 获取枚举的值 + */ + T getValue(); + + /** + * + * @return 获取枚举的描述 + */ + String description(); + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/BasicEnumUtil.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/BasicEnumUtil.java new file mode 100644 index 0000000..6246ca3 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/BasicEnumUtil.java @@ -0,0 +1,55 @@ +package com.agileboot.orm.enums.interfaces; + +import cn.hutool.core.convert.Convert; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import java.util.Objects; + +public class BasicEnumUtil { + + public static String UNKNOWN = "未知"; + + public static > E fromValueSafely(Class enumClass, Object value) { + E target = null; + + for (Object enumConstant : enumClass.getEnumConstants()) { + BasicEnum basicEnum = (BasicEnum) enumConstant; + if (Objects.equals(basicEnum.getValue(), value)) { + target = (E) basicEnum; + } + } + + return target; + } + + public static > E fromValue(Class enumClass, Object value) { + E target = null; + + for (Object enumConstant : enumClass.getEnumConstants()) { + BasicEnum basicEnum = (BasicEnum) enumConstant; + if (Objects.equals(basicEnum.getValue(), value)) { + target = (E) basicEnum; + } + } + + if (target == null) { + throw new ApiException(ErrorCode.Internal.GET_ENUM_FAILED, enumClass.getSimpleName()); + } + + return target; + } + + public static > String getDescriptionByBool(Class enumClass, Boolean bool) { + Integer value = Convert.toInt(bool, 0); + return getDescriptionByValue(enumClass, value); + } + + public static > String getDescriptionByValue(Class enumClass, Object value) { + E basicEnum = fromValueSafely(enumClass, value); + if (basicEnum != null) { + return ((BasicEnum) basicEnum).description(); + } + return UNKNOWN; + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/DictionaryEnum.java b/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/DictionaryEnum.java new file mode 100644 index 0000000..213daca --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/DictionaryEnum.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.enums.interfaces; + +/** + * 字典类型 接口 + * @author valarchie + */ +public interface DictionaryEnum extends BasicEnum { + + /** + * + * @return 获取css标签 + */ + String cssTag(); + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysConfigMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysConfigMapper.java new file mode 100644 index 0000000..7e130c1 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysConfigMapper.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysConfigEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 参数配置表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-09 + */ +public interface SysConfigMapper extends BaseMapper { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysDeptMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysDeptMapper.java new file mode 100644 index 0000000..f82d413 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysDeptMapper.java @@ -0,0 +1,33 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysDeptEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; +import org.apache.ibatis.annotations.Select; + +/** + *

+ * 部门表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface SysDeptMapper extends BaseMapper { + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Select("SELECT d.dept_id " + + "FROM sys_dept d " + + " LEFT JOIN sys_role r ON d.dept_id = r.dept_id " + + "WHERE r.role_id = #{roleId} and d.deleted = 0 " + + "ORDER BY d.parent_id, d.order_num") + List selectDeptListByRoleId(Long roleId); + + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysLoginInfoMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysLoginInfoMapper.java new file mode 100644 index 0000000..7f163bc --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysLoginInfoMapper.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysLoginInfoEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 系统访问记录 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-06 + */ +public interface SysLoginInfoMapper extends BaseMapper { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysMenuMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysMenuMapper.java new file mode 100644 index 0000000..262ba60 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysMenuMapper.java @@ -0,0 +1,50 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysMenuEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + *

+ * 菜单权限表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface SysMenuMapper extends BaseMapper { + + /** + * 根据用户查询出所有菜单 + * @param userId + * @return + */ + @Select("SELECT DISTINCT m.* " + + "FROM sys_menu m " + + " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id " + + " LEFT JOIN sys_user u ON rm.role_id = u.role_id " + + "WHERE u.user_id = #{userId} " + + " AND m.status = 1 " + + " AND m.deleted = 0 " + + "ORDER BY m.parent_id, m.order_num") + List selectMenuListByUserId(@Param("userId")Long userId); + + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + @Select("SELECT DISTINCT m.menu_id " + + "FROM sys_menu m " + + " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id " + + "WHERE rm.role_id = #{roleId} " + + " AND m.deleted = 0 " + + "GROUP BY m.menu_id " + + "ORDER BY m.parent_id, m.order_num") + List selectMenuListByRoleId(@Param("roleId") Long roleId); + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysNoticeMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysNoticeMapper.java new file mode 100644 index 0000000..62203bb --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysNoticeMapper.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysNoticeEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 通知公告表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface SysNoticeMapper extends BaseMapper { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysOperationLogMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysOperationLogMapper.java new file mode 100644 index 0000000..d7fcf83 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysOperationLogMapper.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysOperationLogEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 操作日志记录 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-08 + */ +public interface SysOperationLogMapper extends BaseMapper { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysPostMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysPostMapper.java new file mode 100644 index 0000000..9b90b66 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysPostMapper.java @@ -0,0 +1,18 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysPostEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 岗位信息表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface SysPostMapper extends BaseMapper { + + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysRoleMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysRoleMapper.java new file mode 100644 index 0000000..aded2c1 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysRoleMapper.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysRoleEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 角色信息表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface SysRoleMapper extends BaseMapper { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysRoleMenuMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..bde4d24 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysRoleMenuMapper.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysRoleMenuEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 角色和菜单关联表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface SysRoleMenuMapper extends BaseMapper { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysUserMapper.java b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysUserMapper.java new file mode 100644 index 0000000..6a0ce4e --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysUserMapper.java @@ -0,0 +1,105 @@ +package com.agileboot.orm.mapper; + +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.result.SearchUserDO; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.Set; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + *

+ * 用户信息表 Mapper 接口 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface SysUserMapper extends BaseMapper { + + /** + * 根据用户ID查询角色 + * + * @param userId 用户名 + * @return 角色列表 + */ + @Select("SELECT DISTINCT r.* " + + "FROM sys_role r " + + " LEFT JOIN sys_user u ON u.role_id = r.role_id " + + "WHERE r.deleted = 0 " + + " AND u.user_id = #{userId}") + List selectRolesByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userId 用户名 + * @return 结果 + */ + @Select("SELECT p.* " + + "FROM sys_post p " + + " LEFT JOIN sys_user u ON p.post_id = u.post_id " + + "WHERE u.user_id = #{userId} " + + " AND p.deleted = 0") + List selectPostsByUserId(Long userId); + + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Select("SELECT DISTINCT m.perms " + + "FROM sys_menu m " + + " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id " + + " LEFT JOIN sys_user u ON rm.role_id = u.role_id " + + " LEFT JOIN sys_role r ON r.role_id = u.role_id " + + "WHERE m.status = 1 AND m.deleted = 0 " + + " AND r.status = 1 AND r.deleted = 0 " + + " AND u.user_id = #{userId}") + Set selectMenuPermsByUserId(Long userId); + + + @Select("SELECT DISTINCT u.user_id, u.dept_id, u.username, u.nick_name, u.email " + + " , u.phone_number, u.status, u.create_time " + + "FROM sys_user u " + + " LEFT JOIN sys_dept d ON u.dept_id = d.dept_id " + + " LEFT JOIN sys_role r ON r.role_id = u.role_id " + + " ${ew.customSqlSegment}") + List selectRoleAssignedUserList(Page page, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @return 用户信息集合信息 + */ + @Select("SELECT DISTINCT u.user_id, u.dept_id, u.username, u.nick_name, u.email " + + " , u.phone_number, u.status, u.create_time " + + "FROM sys_user u " + + " LEFT JOIN sys_dept d ON u.dept_id = d.dept_id " + + " LEFT JOIN sys_role r ON r.role_id = u.role_id" + + " ${ew.customSqlSegment}") + List selectRoleUnassignedUserList(Page page, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据条件分页查询用户列表 + * + * @return 用户信息集合信息 + */ + @Select("SELECT u.*, d.dept_name, d.leader_name " + + "FROM sys_user u " + + " LEFT JOIN sys_dept d ON u.dept_id = d.dept_id " + + "${ew.customSqlSegment}") + List selectUserList(Page page, + @Param(Constants.WRAPPER) Wrapper queryWrapper); + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/query/AbstractPageQuery.java b/agileboot-orm/src/main/java/com/agileboot/orm/query/AbstractPageQuery.java new file mode 100644 index 0000000..4cb15e3 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/query/AbstractPageQuery.java @@ -0,0 +1,25 @@ +package com.agileboot.orm.query; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import javax.validation.constraints.Max; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +public abstract class AbstractPageQuery extends AbstractQuery { + + public static final int MAX_PAGE_NUM = 200; + public static final int MAX_PAGE_SIZE = 500; + + @Max(MAX_PAGE_NUM) + protected Integer pageNum = 1; + @Max(MAX_PAGE_SIZE) + protected Integer pageSize = 10; + + public Page toPage() { + return new Page<>(pageNum, pageSize); + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/query/AbstractQuery.java b/agileboot-orm/src/main/java/com/agileboot/orm/query/AbstractQuery.java new file mode 100644 index 0000000..7e1d7cd --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/query/AbstractQuery.java @@ -0,0 +1,85 @@ +package com.agileboot.orm.query; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.agileboot.common.utils.time.DatePicker; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import java.util.Date; +import java.util.Map; +import lombok.Data; + +/** + * @author valarchie + */ +@Data +public abstract class AbstractQuery { + + protected String orderByColumn; + + protected String isAsc; + + private Date beginTime; + + private Date endTime; + + private static final String ASC = "ascending"; + private static final String DESC = "descending"; + + + + /** + * 比如原本的字段是 user_id 可以改成 u.user_id 以适应不同表的查询 + */ + protected Map filedOverride = MapUtil.empty(); + + /** + * 生成query conditions + * @return + */ + public abstract QueryWrapper toQueryWrapper(); + + /** + * 如果有特殊的表名.前缀 就使用特殊的表名前缀 + * @param columnName + * @return + */ + public String columnName(String columnName) { + if (filedOverride.get(columnName) != null) { + return filedOverride.get(columnName); + } + return columnName; + } + + public void addSortCondition(QueryWrapper queryWrapper) { + if(queryWrapper != null) { + queryWrapper.orderBy(StrUtil.isNotBlank(orderByColumn), convertSortDirection(), + StrUtil.toUnderlineCase(orderByColumn)); + + } + } + + @SuppressWarnings("unchecked") + public void addTimeCondition(QueryWrapper queryWrapper, String fieldName) { + if(queryWrapper!=null) { + queryWrapper + .ge(beginTime != null, fieldName, DatePicker.getBeginOfTheDay(beginTime)) + .le(endTime != null, fieldName, DatePicker.getEndOfTheDay(endTime)); + } + } + + + public boolean convertSortDirection() { + boolean orderDirection = true; + if (StrUtil.isNotEmpty(isAsc)) { + // 兼容前端排序类型 + if (ASC.equals(isAsc)) { + orderDirection = true; + } else if (DESC.equals(isAsc)) { + orderDirection = false; + } + } + return orderDirection; + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/result/DictionaryData.java b/agileboot-orm/src/main/java/com/agileboot/orm/result/DictionaryData.java new file mode 100644 index 0000000..754afa6 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/result/DictionaryData.java @@ -0,0 +1,27 @@ +package com.agileboot.orm.result; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.enums.interfaces.DictionaryEnum; +import lombok.Data; + +/** + * 字典模型类 + * @author valarchie + */ +@Data +public class DictionaryData { + + private String label; + private String value; + private String cssTag; + + @SuppressWarnings("rawtypes") + public DictionaryData(DictionaryEnum enumType) { + if (enumType != null) { + this.label = enumType.description(); + this.value = StrUtil.toString(enumType.getValue()); + this.cssTag = enumType.cssTag(); + } + } + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/result/SearchUserDO.java b/agileboot-orm/src/main/java/com/agileboot/orm/result/SearchUserDO.java new file mode 100644 index 0000000..ef28a22 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/result/SearchUserDO.java @@ -0,0 +1,15 @@ +package com.agileboot.orm.result; + +import com.agileboot.orm.entity.SysUserEntity; +import lombok.Data; + +/** + * 如果Entity的字段和复杂查询不匹配时 自定义类来接收 + */ +@Data +public class SearchUserDO extends SysUserEntity { + + private String deptName; + private String deptLeader; + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysConfigService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysConfigService.java new file mode 100644 index 0000000..807c992 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysConfigService.java @@ -0,0 +1,18 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysConfigEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 参数配置表 服务类 + *

+ * + * @author valarchie + * @since 2022-06-09 + */ +public interface ISysConfigService extends IService { + + String getConfigValueByKey(String key); + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysDeptService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysDeptService.java new file mode 100644 index 0000000..bb9d15e --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysDeptService.java @@ -0,0 +1,37 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysDeptEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 部门表 服务类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface ISysDeptService extends IService { + + + /** + * 检测部门名称是否一致 + * @param deptName + * @param deptId + * @param parentId + * @return + */ + boolean checkDeptNameUnique(String deptName, Long deptId, Long parentId); + + /** + * 检测部门底下是否还有正在使用中的子部门 + * @param deptId + * @return + */ + boolean existChildrenDeptById(Long deptId, Boolean enabled); + + boolean isChildOfTargetDeptId(Long ancestorId, Long childId); + + boolean hasChildDeptById(Long deptId); + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysLoginInfoService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysLoginInfoService.java new file mode 100644 index 0000000..70844fc --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysLoginInfoService.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysLoginInfoEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 系统访问记录 服务类 + *

+ * + * @author valarchie + * @since 2022-06-06 + */ +public interface ISysLoginInfoService extends IService { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysMenuService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysMenuService.java new file mode 100644 index 0000000..e34eb11 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysMenuService.java @@ -0,0 +1,72 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysMenuEntity; +import com.baomidou.mybatisplus.extension.service.IService; +import java.util.List; + +/** + *

+ * 菜单权限表 服务类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface ISysMenuService extends IService { + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + List selectMenuListByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + List selectMenuListByRoleId(Long roleId); + + /** + * 校验菜单名称是否唯一 + * @param menuName + * @param menuId + * @param parentId + * @return + */ + boolean checkMenuNameUnique(String menuName, Long menuId, Long parentId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + boolean hasChildByMenuId(Long menuId); + + /** + * 查询菜单是否存在角色 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + boolean checkMenuExistRole(Long menuId); + + /** + * + * @param userId 用户ID + * @return 根据用户列出不含按钮的所有菜单 + */ + List listMenuListWithoutButtonByUserId(Long userId); + + /** + * + * @return 所有不带按钮的菜单 + */ + List listMenuListWithoutButton(); + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysNoticeService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysNoticeService.java new file mode 100644 index 0000000..4cccac4 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysNoticeService.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysNoticeEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 通知公告表 服务类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface ISysNoticeService extends IService { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysOperationLogService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysOperationLogService.java new file mode 100644 index 0000000..3757a45 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysOperationLogService.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysOperationLogEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 操作日志记录 服务类 + *

+ * + * @author valarchie + * @since 2022-06-08 + */ +public interface ISysOperationLogService extends IService { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysPostService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysPostService.java new file mode 100644 index 0000000..9de39e8 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysPostService.java @@ -0,0 +1,36 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysPostEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 岗位信息表 服务类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface ISysPostService extends IService { + + /** + * 校验岗位名称 + * @return 结果 + */ + boolean checkPostNameUnique(Long postId, String postName); + + /** + * 校验岗位编码 + * @return 结果 + */ + boolean checkPostCodeUnique(Long postId, String postCode); + + + /** + * 检测职位是否分配给用户 + * @param postId 职位id + * @return + */ + boolean isAssignedToUser(Long postId); + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysRoleMenuService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysRoleMenuService.java new file mode 100644 index 0000000..d9b361f --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysRoleMenuService.java @@ -0,0 +1,16 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysRoleMenuEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 角色和菜单关联表 服务类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface ISysRoleMenuService extends IService { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysRoleService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysRoleService.java new file mode 100644 index 0000000..12bdce7 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysRoleService.java @@ -0,0 +1,30 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysRoleEntity; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 角色信息表 服务类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface ISysRoleService extends IService { + + + /** + * 校验角色名称是否唯一 + * @return 结果 + */ + boolean checkRoleNameUnique(Long roleId, String roleName); + + /** + * 校验角色权限是否唯一 + * @return 结果 + */ + boolean checkRoleKeyUnique(Long roleId, String roleKey); + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysUserService.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysUserService.java new file mode 100644 index 0000000..d3c84f3 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/ISysUserService.java @@ -0,0 +1,98 @@ +package com.agileboot.orm.service; + +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.query.AbstractPageQuery; +import com.agileboot.orm.result.SearchUserDO; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import java.util.Set; + +/** + *

+ * 用户信息表 服务类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +public interface ISysUserService extends IService { + + boolean checkDeptExistUser(Long deptId); + + boolean checkExistUserLinkToRole(Long roleId); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + boolean checkUserNameUnique(String userName); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userId + * @return 结果 + */ + SysRoleEntity getRoleOfUser(Long userId); + + /** + * 根据用户ID查询用户所属岗位组 + * + * @param userId + * @return 结果 + */ + SysPostEntity getPostOfUser(Long userId); + + /** + * 校验手机号码是否唯一 + * @return 结果 + */ + boolean checkPhoneUnique(String phone, Long userId); + + /** + * 校验email是否唯一 + * @return 结果 + */ + boolean checkEmailUnique(String email, Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + Set selectMenuPermsByUserId(Long userId); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + SysUserEntity getUserByUserName(String userName); + + Page selectAllocatedList(AbstractPageQuery query); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param query 查询参数 + * @return 用户信息集合信息 + */ + Page selectUnallocatedList(AbstractPageQuery query); + + /** + * 根据条件分页查询用户列表 + * + * @param query 查询参数 + * @return 用户信息集合信息 + */ + Page selectUserList(AbstractPageQuery query); + + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysConfigServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..eca0379 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,38 @@ +package com.agileboot.orm.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysConfigEntity; +import com.agileboot.orm.mapper.SysConfigMapper; +import com.agileboot.orm.service.ISysConfigService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 参数配置表 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-09 + */ +@Service +public class SysConfigServiceImpl extends ServiceImpl implements + ISysConfigService { + + @Override + public String getConfigValueByKey(String key) { + if (StrUtil.isBlank(key)) { + return StrUtil.EMPTY; + } + QueryWrapper sysNoticeWrapper = new QueryWrapper<>(); + sysNoticeWrapper.eq("config_key", key); + SysConfigEntity one = this.getOne(sysNoticeWrapper); + if (one == null || one.getConfigValue() == null) { + return StrUtil.EMPTY; + } + return one.getConfigValue(); + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysDeptServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..6e17f14 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,58 @@ +package com.agileboot.orm.service.impl; + +import com.agileboot.orm.entity.SysDeptEntity; +import com.agileboot.orm.mapper.SysDeptMapper; +import com.agileboot.orm.service.ISysDeptService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 部门表 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +@Service +public class SysDeptServiceImpl extends ServiceImpl implements ISysDeptService { + + + @Override + public boolean checkDeptNameUnique(String deptName, Long deptId, Long parentId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("dept_name", deptName) + .ne(deptId != null, "dept_id", deptId) + .eq(parentId != null, "parent_id", parentId); + + SysDeptEntity one = this.getOne(queryWrapper); + return one != null; + } + + + @Override + public boolean existChildrenDeptById(Long deptId, Boolean enabled) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq( "dept_id", deptId) + .eq(enabled != null, "status", 1) + .apply( "FIND_IN_SET (dept_id , ancestors)"); + return this.baseMapper.exists(queryWrapper); + } + + @Override + public boolean isChildOfTargetDeptId(Long ancestorId, Long childId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.apply("dept_id = '" + childId + "' or FIND_IN_SET ( " + ancestorId + " , ancestors)"); + return this.baseMapper.exists(queryWrapper); + } + + @Override + public boolean hasChildDeptById(Long deptId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(deptId != null, "parent_id", deptId); + return baseMapper.exists(queryWrapper); + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysLoginInfoServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysLoginInfoServiceImpl.java new file mode 100644 index 0000000..6bd746a --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysLoginInfoServiceImpl.java @@ -0,0 +1,21 @@ +package com.agileboot.orm.service.impl; + +import com.agileboot.orm.entity.SysLoginInfoEntity; +import com.agileboot.orm.mapper.SysLoginInfoMapper; +import com.agileboot.orm.service.ISysLoginInfoService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 系统访问记录 服务实现类 + *

+ * + * @author valarchie + * @since 2022-07-10 + */ +@Service +public class SysLoginInfoServiceImpl extends ServiceImpl implements + ISysLoginInfoService { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysMenuServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..e876074 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,111 @@ +package com.agileboot.orm.service.impl; + +import com.agileboot.orm.entity.SysMenuEntity; +import com.agileboot.orm.entity.SysRoleMenuEntity; +import com.agileboot.orm.enums.MenuTypeEnum; +import com.agileboot.orm.mapper.SysMenuMapper; +import com.agileboot.orm.mapper.SysRoleMapper; +import com.agileboot.orm.mapper.SysRoleMenuMapper; +import com.agileboot.orm.service.ISysMenuService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * 菜单权限表 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +@Service +public class SysMenuServiceImpl extends ServiceImpl implements ISysMenuService { + + @Autowired + private SysRoleMapper roleMapper; + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + @Override + public List selectMenuListByRoleId(Long roleId) { + return this.baseMapper.selectMenuListByRoleId(roleId); + } + + @Override + public boolean checkMenuNameUnique(String menuName, Long menuId, Long parentId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("menu_name", menuName) + .ne(menuId!=null, "menu_id", menuId) + .eq(parentId != null, "parent_id", parentId); + return this.baseMapper.exists(queryWrapper); + } + + + + @Override + public boolean hasChildByMenuId(Long menuId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id", menuId); + return baseMapper.exists(queryWrapper); + } + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean checkMenuExistRole(Long menuId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("menu_id", menuId); + return roleMenuMapper.exists(queryWrapper); + } + + @Override + public List listMenuListWithoutButtonByUserId(Long userId) { + List sysMenuList = this.baseMapper.selectMenuListByUserId(userId); + + if (sysMenuList == null) { + return null; + } + + return sysMenuList.stream() + .filter(menu -> !Objects.equals(menu.getMenuType(), MenuTypeEnum.BUTTON.getValue())) + .collect(Collectors.toList()); + } + + @Override + public List listMenuListWithoutButton() { + List sysMenuList = this.list(); + + if (sysMenuList == null) { + return null; + } + + return sysMenuList.stream() + .filter(menu -> !MenuTypeEnum.BUTTON.getValue().equals(menu.getMenuType())) + .collect(Collectors.toList()); + } + + @Override + public List selectMenuListByUserId(Long userId) { + return baseMapper.selectMenuListByUserId(userId); + } + + + + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysNoticeServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 0000000..c6741b4 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,20 @@ +package com.agileboot.orm.service.impl; + +import com.agileboot.orm.entity.SysNoticeEntity; +import com.agileboot.orm.mapper.SysNoticeMapper; +import com.agileboot.orm.service.ISysNoticeService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 通知公告表 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +@Service +public class SysNoticeServiceImpl extends ServiceImpl implements ISysNoticeService { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysOperationLogServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysOperationLogServiceImpl.java new file mode 100644 index 0000000..80a157a --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysOperationLogServiceImpl.java @@ -0,0 +1,21 @@ +package com.agileboot.orm.service.impl; + +import com.agileboot.orm.entity.SysOperationLogEntity; +import com.agileboot.orm.mapper.SysOperationLogMapper; +import com.agileboot.orm.service.ISysOperationLogService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 操作日志记录 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-08 + */ +@Service +public class SysOperationLogServiceImpl extends ServiceImpl implements + ISysOperationLogService { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysPostServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysPostServiceImpl.java new file mode 100644 index 0000000..38bbe0f --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysPostServiceImpl.java @@ -0,0 +1,58 @@ +package com.agileboot.orm.service.impl; + +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.mapper.SysPostMapper; +import com.agileboot.orm.mapper.SysUserMapper; +import com.agileboot.orm.service.ISysPostService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * 岗位信息表 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +@Service +public class SysPostServiceImpl extends ServiceImpl implements ISysPostService { + + @Autowired + private SysUserMapper userMapper; + + /** + * 校验岗位名称是否唯一 + * + * @param postName 岗位名称 + * @return 结果 + */ + @Override + public boolean checkPostNameUnique(Long postId, String postName) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.ne(postId != null, "post_id", postId) + .eq("post_name", postName); + return baseMapper.exists(queryWrapper); + } + + @Override + public boolean checkPostCodeUnique(Long postId, String postCode) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.ne(postId != null, "post_id", postId) + .eq("post_code", postCode); + return baseMapper.exists(queryWrapper); + } + + + @Override + public boolean isAssignedToUser(Long postId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("post_id", postId); + return userMapper.exists(queryWrapper); + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysRoleMenuServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysRoleMenuServiceImpl.java new file mode 100644 index 0000000..948b938 --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysRoleMenuServiceImpl.java @@ -0,0 +1,21 @@ +package com.agileboot.orm.service.impl; + +import com.agileboot.orm.entity.SysRoleMenuEntity; +import com.agileboot.orm.mapper.SysRoleMenuMapper; +import com.agileboot.orm.service.ISysRoleMenuService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 角色和菜单关联表 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +@Service +public class SysRoleMenuServiceImpl extends ServiceImpl implements + ISysRoleMenuService { + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysRoleServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..675a36a --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,43 @@ +package com.agileboot.orm.service.impl; + +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.mapper.SysRoleMapper; +import com.agileboot.orm.service.ISysRoleMenuService; +import com.agileboot.orm.service.ISysRoleService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

+ * 角色信息表 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +@Service +public class SysRoleServiceImpl extends ServiceImpl implements ISysRoleService { + + @Autowired + ISysRoleMenuService roleMenuService; + + @Override + public boolean checkRoleNameUnique(Long roleId, String roleName) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.ne(roleId != null, "role_id", roleId) + .eq("role_name", roleName); + return this.baseMapper.exists(queryWrapper); + } + + @Override + public boolean checkRoleKeyUnique(Long roleId, String roleKey) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.ne(roleId != null, "role_id", roleId) + .eq("role_key", roleKey); + return this.baseMapper.exists(queryWrapper); + } + + +} diff --git a/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysUserServiceImpl.java b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..d68c83e --- /dev/null +++ b/agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysUserServiceImpl.java @@ -0,0 +1,127 @@ +package com.agileboot.orm.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.agileboot.orm.entity.SysPostEntity; +import com.agileboot.orm.entity.SysRoleEntity; +import com.agileboot.orm.entity.SysUserEntity; +import com.agileboot.orm.mapper.SysUserMapper; +import com.agileboot.orm.query.AbstractPageQuery; +import com.agileboot.orm.result.SearchUserDO; +import com.agileboot.orm.service.ISysUserService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.stereotype.Service; + +/** + *

+ * 用户信息表 服务实现类 + *

+ * + * @author valarchie + * @since 2022-06-16 + */ +@Service +public class SysUserServiceImpl extends ServiceImpl implements ISysUserService { + + + @Override + public boolean checkDeptExistUser(Long deptId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("dept_id", deptId); + return this.count(queryWrapper) > 0; + } + + @Override + public boolean checkExistUserLinkToRole(Long roleId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("role_id", roleId); + return this.count(queryWrapper) > 0; + } + + @Override + public boolean checkUserNameUnique(String username) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("username", username); + return this.baseMapper.exists(queryWrapper); + } + + + @Override + public SysRoleEntity getRoleOfUser(Long userId) { + List list = baseMapper.selectRolesByUserId(userId); + return list.isEmpty() ? null : list.get(0); + } + + @Override + public SysPostEntity getPostOfUser(Long userId) { + List list = baseMapper.selectPostsByUserId(userId); + return list.isEmpty() ? null : list.get(0); + } + + @Override + public boolean checkPhoneUnique(String phone, Long userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.ne(userId != null, "user_id", userId) + .eq("phone_number", phone); + return baseMapper.exists(queryWrapper); + } + + @Override + public boolean checkEmailUnique(String email, Long userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.ne(userId != null, "user_id", userId) + .eq("email", email); + return baseMapper.exists(queryWrapper); + } + + + @Override + public Set selectMenuPermsByUserId(Long userId) { + Set permissionStrList = baseMapper.selectMenuPermsByUserId(userId); + Set singlePermsSet = new HashSet<>(); + for (String perm : permissionStrList) { + if (StrUtil.isNotEmpty(perm)) { + singlePermsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return singlePermsSet; + } + + @Override + public SysUserEntity getUserByUserName(String userName) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("username", userName); + return this.getOne(queryWrapper); + } + + @Override + public Page selectAllocatedList(AbstractPageQuery query) { + Page page = query.toPage(); + List list = baseMapper.selectRoleAssignedUserList(page, query.toQueryWrapper()); + page.setRecords(list); + return page; + } + + @Override + public Page selectUnallocatedList(AbstractPageQuery query) { + Page page = query.toPage(); + List list = baseMapper.selectRoleUnassignedUserList(page, query.toQueryWrapper()); + page.setRecords(list); + return page; + } + + @Override + public Page selectUserList(AbstractPageQuery query) { + Page page = query.toPage(); + List searchUserDOS = baseMapper.selectUserList(page, query.toQueryWrapper()); + page.setRecords(searchUserDOS); + return page; + } + + +} diff --git a/agileboot-orm/src/main/resources/mapper/orm/SysUserMapper.xml b/agileboot-orm/src/main/resources/mapper/orm/SysUserMapper.xml new file mode 100644 index 0000000..bf36b0e --- /dev/null +++ b/agileboot-orm/src/main/resources/mapper/orm/SysUserMapper.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bin/clean.bat b/bin/clean.bat new file mode 100644 index 0000000..d8561e4 --- /dev/null +++ b/bin/clean.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [信息] 清理工程target生成路径。 +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean + +pause diff --git a/bin/package.bat b/bin/package.bat new file mode 100644 index 0000000..735b65e --- /dev/null +++ b/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [信息] 打包Web工程,生成war/jar包文件。 +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean package -Dmaven.test.skip=true + +pause \ No newline at end of file diff --git a/bin/run.bat b/bin/run.bat new file mode 100644 index 0000000..5ae8c92 --- /dev/null +++ b/bin/run.bat @@ -0,0 +1,14 @@ +@echo off +echo. +echo [信息] 使用Jar命令运行Web工程。 +echo. + +cd %~dp0 +cd ../agileboot-admin/target + +set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m + +java -jar %JAVA_OPTS% agileboot-admin.jar + +cd bin +pause diff --git a/bin/ry.bat b/bin/ry.bat new file mode 100644 index 0000000..c5193e0 --- /dev/null +++ b/bin/ry.bat @@ -0,0 +1,67 @@ +@echo off + +rem jar平级目录 +set AppName=agileboot-admin.jar + +rem JVM参数 +set JVM_OPTS="-Dname=%AppName% -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" + + +ECHO. + ECHO. [1] 启动%AppName% + ECHO. [2] 关闭%AppName% + ECHO. [3] 重启%AppName% + ECHO. [4] 启动状态 %AppName% + ECHO. [5] 退 出 +ECHO. + +ECHO.请输入选择项目的序号: +set /p ID= + IF "%id%"=="1" GOTO start + IF "%id%"=="2" GOTO stop + IF "%id%"=="3" GOTO restart + IF "%id%"=="4" GOTO status + IF "%id%"=="5" EXIT +PAUSE +:start + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if defined pid ( + echo %%is running + PAUSE + ) + +start javaw %JVM_OPTS% -jar %AppName% + +echo starting…… +echo Start %AppName% success... +goto:eof + +rem 函数stop通过jps命令查找pid并结束进程 +:stop + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if not defined pid (echo process %AppName% does not exists) else ( + echo prepare to kill %image_name% + echo start kill %pid% ... + rem 根据进程ID,kill进程 + taskkill /f /pid %pid% + ) +goto:eof +:restart + call :stop + call :start +goto:eof +:status + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if not defined pid (echo process %AppName% is dead ) else ( + echo %image_name% is running + ) +goto:eof diff --git a/bin/ry.sh b/bin/ry.sh new file mode 100644 index 0000000..d6a9cf3 --- /dev/null +++ b/bin/ry.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# ./ry.sh start 启动 stop 停止 restart 重启 status 状态 +AppName=ruoyi-admin.jar + +# JVM参数 +JVM_OPTS="-Dname=$AppName -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" +APP_HOME=`pwd` +LOG_PATH=$APP_HOME/logs/$AppName.log + +if [ "$1" = "" ]; +then + echo -e "\033[0;31m 未输入操作名 \033[0m \033[0;34m {start|stop|restart|status} \033[0m" + exit 1 +fi + +if [ "$AppName" = "" ]; +then + echo -e "\033[0;31m 未输入应用名 \033[0m" + exit 1 +fi + +function start() +{ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` + + if [ x"$PID" != x"" ]; then + echo "$AppName is running..." + else + nohup java $JVM_OPTS -jar $AppName > /dev/null 2>&1 & + echo "Start $AppName success..." + fi +} + +function stop() +{ + echo "Stop $AppName" + + PID="" + query(){ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` + } + + query + if [ x"$PID" != x"" ]; then + kill -TERM $PID + echo "$AppName (pid:$PID) exiting..." + while [ x"$PID" != x"" ] + do + sleep 1 + query + done + echo "$AppName exited." + else + echo "$AppName already stopped." + fi +} + +function restart() +{ + stop + sleep 2 + start +} + +function status() +{ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l` + if [ $PID != 0 ];then + echo "$AppName is running..." + else + echo "$AppName is not running..." + fi +} + +case $1 in + start) + start;; + stop) + stop;; + restart) + restart;; + status) + status;; + *) + +esac diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..bcb7fa3 --- /dev/null +++ b/pom.xml @@ -0,0 +1,308 @@ + + + 4.0.0 + + com.agileboot + agileboot + 1.0.0 + + agileboot + http://www.agileboot.vip + 基于 springboot的快速开发系统 + + + 1.0.0 + UTF-8 + UTF-8 + 1.8 + 3.1.1 + 1.2.8 + 1.21 + 3.0.0 + 2.3.2 + 2.2.2 + 1.4.1 + 6.1.6 + 2.11.0 + 1.4 + 3.2.2 + 4.1.2 + 2.3 + 0.9.1 + 5.7.22 + 4.13.1 + 1.18.24 + 3.5.2 + 3.5.1 + + + + + + + + + org.springframework.boot + spring-boot-dependencies + 2.7.0 + pom + import + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + eu.bitwalker + UserAgentUtils + ${bitwalker.version} + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot.version} + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus-generator.version} + + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + + io.springfox + springfox-boot-starter + ${swagger.version} + + + io.swagger + swagger-models + + + + + + + commons-io + commons-io + ${commons.io.version} + + + + + commons-fileupload + commons-fileupload + ${commons.fileupload.version} + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + + commons-collections + commons-collections + ${commons.collections.version} + + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + + com.github.penggle + kaptcha + ${kaptcha.version} + + + + + + com.agileboot + agileboot-admin + ${agileboot.version} + + + + + com.agileboot + agileboot-api + ${agileboot.version} + + + + + com.agileboot + agileboot-domain + ${agileboot.version} + + + + + com.agileboot + agileboot-infrastructure + ${agileboot.version} + + + + + com.agileboot + agileboot-orm + ${agileboot.version} + + + + + com.agileboot + agileboot-common + ${agileboot.version} + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + junit + junit + ${junit.version} + test + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + + + agileboot-admin + agileboot-api + agileboot-common + agileboot-domain + agileboot-infrastructure + agileboot-orm + + pom + + + + + cn.hutool + hutool-all + + + org.projectlombok + lombok + + + junit + junit + test + + + com.baomidou + mybatis-plus-boot-starter + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sql/agileboot_20221007.sql b/sql/agileboot_20221007.sql new file mode 100644 index 0000000..89aaf44 --- /dev/null +++ b/sql/agileboot_20221007.sql @@ -0,0 +1,463 @@ +/* +Navicat MySQL Data Transfer + +Source Server : Local-Mysql +Source Server Version : 80029 +Source Host : localhost:33066 +Source Database : agileboot + +Target Server Type : MYSQL +Target Server Version : 80029 +File Encoding : 65001 + +Date: 2022-10-07 16:05:40 +*/ + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `config_id` int NOT NULL AUTO_INCREMENT COMMENT '参数主键', + `config_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '配置名称', + `config_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '配置键名', + `config_options` varchar(1024) NOT NULL DEFAULT '' COMMENT '可选的选项', + `config_value` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '配置值', + `is_allow_change` tinyint(1) NOT NULL COMMENT '是否允许修改', + `creator_id` bigint DEFAULT NULL COMMENT '创建者ID', + `creator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', + `updater_id` bigint DEFAULT NULL COMMENT '更新者ID', + `updater_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除', + PRIMARY KEY (`config_id`), + UNIQUE KEY `config_key_uniq_idx` (`config_key`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='参数配置表'; + +-- ---------------------------- +-- Records of sys_config +-- ---------------------------- +INSERT INTO `sys_config` VALUES ('1', '主框架页-默认皮肤样式名称', 'sys.index.skinName', '[\"skin-blue\",\"skin-green\",\"skin-purple\",\"skin-red\",\"skin-yellow\"]', 'skin-blue', '1', null, 'admin', null, '', '2022-08-28 22:12:19', '2022-05-21 08:30:55', '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow', '0'); +INSERT INTO `sys_config` VALUES ('2', '用户管理-账号初始密码', 'sys.user.initPassword', '', '1234567', '1', null, 'admin', null, '', '2022-08-28 21:54:19', '2022-05-21 08:30:55', '初始化密码 123456', '0'); +INSERT INTO `sys_config` VALUES ('3', '主框架页-侧边栏主题', 'sys.index.sideTheme', '[\"theme-dark\",\"theme-light\"]', 'theme-dark', '1', null, 'admin', null, '', '2022-08-28 22:12:15', '2022-08-20 08:30:55', '深色主题theme-dark,浅色主题theme-light', '0'); +INSERT INTO `sys_config` VALUES ('4', '账号自助-验证码开关', 'sys.account.captchaOnOff', '[\"true\",\"false\"]', 'false', '0', null, 'admin', null, '', '2022-08-28 22:03:37', '2022-05-21 08:30:55', '是否开启验证码功能(true开启,false关闭)', '0'); +INSERT INTO `sys_config` VALUES ('5', '账号自助-是否开启用户注册功能', 'sys.account.registerUser', '[\"true\",\"false\"]', 'true', '0', null, 'admin', '1', 'admin', '2022-10-05 22:18:57', '2022-05-21 08:30:55', '是否开启注册用户功能(true开启,false关闭)', '0'); + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门id', + `parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父部门id', + `ancestors` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '祖级列表', + `dept_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '部门名称', + `order_num` int NOT NULL DEFAULT '0' COMMENT '显示顺序', + `leader_id` bigint DEFAULT NULL, + `leader_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '负责人', + `phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '联系电话', + `email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '邮箱', + `status` smallint NOT NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', + `creator_id` bigint DEFAULT NULL COMMENT '创建者ID', + `creator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `updater_id` bigint DEFAULT NULL COMMENT '更新者ID', + `updater_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除', + PRIMARY KEY (`dept_id`) +) ENGINE=InnoDB AUTO_INCREMENT=203 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='部门表'; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +INSERT INTO `sys_dept` VALUES ('100', '0', '0', 'AgileBoot科技', '0', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('101', '100', '0,100', '深圳总公司', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('102', '100', '0,100', '长沙分公司', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('103', '101', '0,100,101', '研发部门', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('104', '101', '0,100,101', '市场部门', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('105', '101', '0,100,101', '测试部门', '3', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('106', '101', '0,100,101', '财务部门', '4', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('107', '101', '0,100,101', '运维部门', '5', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('108', '102', '0,100,102', '市场部门', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_dept` VALUES ('109', '102', '0,100,102', '财务部门', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '0', null, 'admin', '2022-05-21 08:30:54', '1', 'admin', '2022-10-05 22:16:49', '0'); + +-- ---------------------------- +-- Table structure for sys_login_info +-- ---------------------------- +DROP TABLE IF EXISTS `sys_login_info`; +CREATE TABLE `sys_login_info` ( + `info_id` bigint NOT NULL AUTO_INCREMENT COMMENT '访问ID', + `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '用户账号', + `ip_address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '浏览器类型', + `operation_system` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '操作系统', + `status` smallint NOT NULL DEFAULT '0' COMMENT '登录状态(1成功 0失败)', + `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '提示消息', + `login_time` datetime DEFAULT NULL COMMENT '访问时间', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除', + PRIMARY KEY (`info_id`) +) ENGINE=InnoDB AUTO_INCREMENT=415 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统访问记录'; + +-- ---------------------------- +-- Records of sys_login_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `menu_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '菜单名称', + `parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父菜单ID', + `order_num` int NOT NULL DEFAULT '0' COMMENT '显示顺序', + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '路由地址', + `component` varchar(255) DEFAULT NULL COMMENT '组件路径', + `query` varchar(255) DEFAULT NULL COMMENT '路由参数', + `is_external` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否为外链(1是 0否)', + `is_cache` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否缓存(1缓存 0不缓存)', + `menu_type` smallint NOT NULL DEFAULT '0' COMMENT '菜单类型(M=1目录 C=2菜单 F=3按钮)', + `is_visible` tinyint(1) NOT NULL DEFAULT '0' COMMENT '菜单状态(1显示 0隐藏)', + `status` smallint NOT NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', + `perms` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '权限标识', + `icon` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '#' COMMENT '菜单图标', + `creator_id` bigint DEFAULT NULL COMMENT '创建者ID', + `creator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `updater_id` bigint DEFAULT NULL COMMENT '更新者ID', + `updater_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '备注', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除', + PRIMARY KEY (`menu_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2007 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='菜单权限表'; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +INSERT INTO `sys_menu` VALUES ('1', '系统管理', '0', '1', 'system', null, '', '0', '1', '1', '1', '1', '', 'system', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '系统管理目录', '0'); +INSERT INTO `sys_menu` VALUES ('2', '系统监控', '0', '2', 'monitor', null, '', '0', '1', '1', '1', '1', '', 'monitor', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '系统监控目录', '0'); +INSERT INTO `sys_menu` VALUES ('3', '系统工具', '0', '3', 'tool', null, '', '0', '1', '1', '1', '1', '', 'tool', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '系统工具目录', '0'); +INSERT INTO `sys_menu` VALUES ('4', 'AgileBoot官网', '0', '4', 'http://ruoyi.vip', null, '', '1', '1', '1', '1', '1', '', 'guide', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '若依官网地址', '0'); +INSERT INTO `sys_menu` VALUES ('100', '用户管理', '1', '1', 'user', 'system/user/index', '', '0', '1', '2', '1', '1', 'system:user:list', 'user', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '用户管理菜单', '0'); +INSERT INTO `sys_menu` VALUES ('101', '角色管理', '1', '2', 'role', 'system/role/index', '', '0', '1', '2', '1', '1', 'system:role:list', 'peoples', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '角色管理菜单', '0'); +INSERT INTO `sys_menu` VALUES ('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', '0', '1', '2', '1', '1', 'system:menu:list', 'tree-table', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '菜单管理菜单', '0'); +INSERT INTO `sys_menu` VALUES ('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', '0', '1', '2', '1', '1', 'system:dept:list', 'tree', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '部门管理菜单', '0'); +INSERT INTO `sys_menu` VALUES ('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', '0', '1', '2', '1', '1', 'system:post:list', 'post', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '岗位管理菜单', '0'); +INSERT INTO `sys_menu` VALUES ('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', '0', '1', '2', '1', '1', 'system:dict:list', 'dict', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '字典管理菜单', '1'); +INSERT INTO `sys_menu` VALUES ('106', '参数设置', '1', '7', 'config', 'system/config/index', '', '0', '1', '2', '1', '1', 'system:config:list', 'edit', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '参数设置菜单', '0'); +INSERT INTO `sys_menu` VALUES ('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', '0', '1', '2', '1', '1', 'system:notice:list', 'message', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '通知公告菜单', '0'); +INSERT INTO `sys_menu` VALUES ('108', '日志管理', '1', '9', 'log', '', '', '0', '1', '1', '1', '1', '', 'log', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '日志管理菜单', '0'); +INSERT INTO `sys_menu` VALUES ('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', '0', '1', '2', '1', '1', 'monitor:online:list', 'online', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '在线用户菜单', '0'); +INSERT INTO `sys_menu` VALUES ('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', '0', '1', '2', '1', '1', 'monitor:job:list', 'job', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '定时任务菜单', '1'); +INSERT INTO `sys_menu` VALUES ('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', '0', '1', '2', '1', '1', 'monitor:druid:list', 'druid', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '数据监控菜单', '0'); +INSERT INTO `sys_menu` VALUES ('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', '0', '1', '2', '1', '1', 'monitor:server:list', 'server', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '服务监控菜单', '0'); +INSERT INTO `sys_menu` VALUES ('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', '0', '1', '2', '1', '1', 'monitor:cache:list', 'redis', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '缓存监控菜单', '0'); +INSERT INTO `sys_menu` VALUES ('114', '表单构建', '3', '1', 'build', 'tool/build/index', '', '0', '1', '2', '1', '1', 'tool:build:list', 'build', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '表单构建菜单', '1'); +INSERT INTO `sys_menu` VALUES ('115', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', '0', '1', '2', '1', '1', 'tool:gen:list', 'code', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '代码生成菜单', '1'); +INSERT INTO `sys_menu` VALUES ('116', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', '0', '1', '2', '1', '1', 'tool:swagger:list', 'swagger', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '系统接口菜单', '0'); +INSERT INTO `sys_menu` VALUES ('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', '0', '1', '2', '1', '1', 'monitor:operlog:list', 'form', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '操作日志菜单', '0'); +INSERT INTO `sys_menu` VALUES ('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '0', '1', '2', '1', '1', 'monitor:logininfor:list', 'logininfor', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '登录日志菜单', '0'); +INSERT INTO `sys_menu` VALUES ('1001', '用户查询', '100', '1', '', '', '', '0', '1', '3', '1', '1', 'system:user:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1002', '用户新增', '100', '2', '', '', '', '0', '1', '3', '1', '1', 'system:user:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1003', '用户修改', '100', '3', '', '', '', '0', '1', '3', '1', '1', 'system:user:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1004', '用户删除', '100', '4', '', '', '', '0', '1', '3', '1', '1', 'system:user:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1005', '用户导出', '100', '5', '', '', '', '0', '1', '3', '1', '1', 'system:user:export', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1006', '用户导入', '100', '6', '', '', '', '0', '1', '3', '1', '1', 'system:user:import', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1007', '重置密码', '100', '7', '', '', '', '0', '1', '3', '1', '1', 'system:user:resetPwd', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1008', '角色查询', '101', '1', '', '', '', '0', '1', '3', '1', '1', 'system:role:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1009', '角色新增', '101', '2', '', '', '', '0', '1', '3', '1', '1', 'system:role:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1010', '角色修改', '101', '3', '', '', '', '0', '1', '3', '1', '1', 'system:role:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1011', '角色删除', '101', '4', '', '', '', '0', '1', '3', '1', '1', 'system:role:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1012', '角色导出', '101', '5', '', '', '', '0', '1', '3', '1', '1', 'system:role:export', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1013', '菜单查询', '102', '1', '', '', '', '0', '1', '3', '1', '1', 'system:menu:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1014', '菜单新增', '102', '2', '', '', '', '0', '1', '3', '1', '1', 'system:menu:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1015', '菜单修改', '102', '3', '', '', '', '0', '1', '3', '1', '1', 'system:menu:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1016', '菜单删除', '102', '4', '', '', '', '0', '1', '3', '1', '1', 'system:menu:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1017', '部门查询', '103', '1', '', '', '', '0', '1', '3', '1', '1', 'system:dept:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1018', '部门新增', '103', '2', '', '', '', '0', '1', '3', '1', '1', 'system:dept:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1019', '部门修改', '103', '3', '', '', '', '0', '1', '3', '1', '1', 'system:dept:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1020', '部门删除', '103', '4', '', '', '', '0', '1', '3', '1', '1', 'system:dept:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1021', '岗位查询', '104', '1', '', '', '', '0', '1', '3', '1', '1', 'system:post:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1022', '岗位新增', '104', '2', '', '', '', '0', '1', '3', '1', '1', 'system:post:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1023', '岗位修改', '104', '3', '', '', '', '0', '1', '3', '1', '1', 'system:post:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1024', '岗位删除', '104', '4', '', '', '', '0', '1', '3', '1', '1', 'system:post:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1025', '岗位导出', '104', '5', '', '', '', '0', '1', '3', '1', '1', 'system:post:export', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1026', '字典查询', '105', '1', '#', '', '', '0', '1', '3', '1', '1', 'system:dict:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1027', '字典新增', '105', '2', '#', '', '', '0', '1', '3', '1', '1', 'system:dict:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1028', '字典修改', '105', '3', '#', '', '', '0', '1', '3', '1', '1', 'system:dict:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1029', '字典删除', '105', '4', '#', '', '', '0', '1', '3', '1', '1', 'system:dict:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1030', '字典导出', '105', '5', '#', '', '', '0', '1', '3', '1', '1', 'system:dict:export', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1031', '参数查询', '106', '1', '#', '', '', '0', '1', '3', '1', '1', 'system:config:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1032', '参数新增', '106', '2', '#', '', '', '0', '1', '3', '1', '1', 'system:config:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1033', '参数修改', '106', '3', '#', '', '', '0', '1', '3', '1', '1', 'system:config:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1034', '参数删除', '106', '4', '#', '', '', '0', '1', '3', '1', '1', 'system:config:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1035', '参数导出', '106', '5', '#', '', '', '0', '1', '3', '1', '1', 'system:config:export', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1036', '公告查询', '107', '1', '#', '', '', '0', '1', '3', '1', '1', 'system:notice:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1037', '公告新增', '107', '2', '#', '', '', '0', '1', '3', '1', '1', 'system:notice:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1038', '公告修改', '107', '3', '#', '', '', '0', '1', '3', '1', '1', 'system:notice:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1039', '公告删除', '107', '4', '#', '', '', '0', '1', '3', '1', '1', 'system:notice:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1040', '操作查询', '500', '1', '#', '', '', '0', '1', '3', '1', '1', 'monitor:operlog:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1041', '操作删除', '500', '2', '#', '', '', '0', '1', '3', '1', '1', 'monitor:operlog:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1042', '日志导出', '500', '4', '#', '', '', '0', '1', '3', '1', '1', 'monitor:operlog:export', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1043', '登录查询', '501', '1', '#', '', '', '0', '1', '3', '1', '1', 'monitor:logininfor:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1044', '登录删除', '501', '2', '#', '', '', '0', '1', '3', '1', '1', 'monitor:logininfor:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1045', '日志导出', '501', '3', '#', '', '', '0', '1', '3', '1', '1', 'monitor:logininfor:export', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1046', '在线查询', '109', '1', '#', '', '', '0', '1', '3', '1', '1', 'monitor:online:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1047', '批量强退', '109', '2', '#', '', '', '0', '1', '3', '1', '1', 'monitor:online:batchLogout', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1048', '单条强退', '109', '3', '#', '', '', '0', '1', '3', '1', '1', 'monitor:online:forceLogout', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '0'); +INSERT INTO `sys_menu` VALUES ('1049', '任务查询', '110', '1', '#', '', '', '0', '1', '3', '1', '1', 'monitor:job:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1050', '任务新增', '110', '2', '#', '', '', '0', '1', '3', '1', '1', 'monitor:job:add', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1051', '任务修改', '110', '3', '#', '', '', '0', '1', '3', '1', '1', 'monitor:job:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1052', '任务删除', '110', '4', '#', '', '', '0', '1', '3', '1', '1', 'monitor:job:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1053', '状态修改', '110', '5', '#', '', '', '0', '1', '3', '1', '1', 'monitor:job:changeStatus', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1054', '任务导出', '110', '7', '#', '', '', '0', '1', '3', '1', '1', 'monitor:job:export', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1055', '生成查询', '115', '1', '#', '', '', '0', '1', '3', '1', '1', 'tool:gen:query', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1056', '生成修改', '115', '2', '#', '', '', '0', '1', '3', '1', '1', 'tool:gen:edit', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1057', '生成删除', '115', '3', '#', '', '', '0', '1', '3', '1', '1', 'tool:gen:remove', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1058', '导入代码', '115', '2', '#', '', '', '0', '1', '3', '1', '1', 'tool:gen:import', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1059', '预览代码', '115', '4', '#', '', '', '0', '1', '3', '1', '1', 'tool:gen:preview', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); +INSERT INTO `sys_menu` VALUES ('1060', '生成代码', '115', '5', '#', '', '', '0', '1', '3', '1', '1', 'tool:gen:code', '#', '0', 'admin', '2022-05-21 08:30:54', null, '', null, '', '1'); + +-- ---------------------------- +-- Table structure for sys_notice +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice`; +CREATE TABLE `sys_notice` ( + `notice_id` int NOT NULL AUTO_INCREMENT COMMENT '公告ID', + `notice_title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '公告标题', + `notice_type` smallint NOT NULL COMMENT '公告类型(1通知 2公告)', + `notice_content` text COMMENT '公告内容', + `status` smallint NOT NULL DEFAULT '0' COMMENT '公告状态(1正常 0关闭)', + `creator_id` bigint NOT NULL COMMENT '创建者ID', + `creator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `updater_id` bigint DEFAULT NULL COMMENT '更新者ID', + `updater_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '备注', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除', + PRIMARY KEY (`notice_id`) +) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='通知公告表'; + +-- ---------------------------- +-- Records of sys_notice +-- ---------------------------- +INSERT INTO `sys_notice` VALUES ('1', '温馨提醒:2018-07-01 AgileBoot新版本发布啦', '2', '新版本内容~~~~~~~~~~', '1', '1', 'admin', '2022-05-21 08:30:55', '1', 'admin', '2022-08-29 20:12:37', '管理员', '0'); +INSERT INTO `sys_notice` VALUES ('2', '维护通知:2018-07-01 AgileBoot系统凌晨维护', '1', '维护内容', '1', '1', 'admin', '2022-05-21 08:30:55', null, '', null, '管理员', '0'); + +-- ---------------------------- +-- Table structure for sys_operation_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_operation_log`; +CREATE TABLE `sys_operation_log` ( + `operation_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `business_type` smallint NOT NULL DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `request_method` smallint NOT NULL DEFAULT '0' COMMENT '请求方式', + `request_module` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '请求模块', + `request_url` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '请求URL', + `called_method` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '调用方法', + `operator_type` smallint NOT NULL DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `user_id` bigint DEFAULT '0' COMMENT '用户ID', + `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作人员', + `operator_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作人员ip', + `operator_location` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作地点', + `dept_id` bigint DEFAULT '0' COMMENT '部门ID', + `dept_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '部门名称', + `operation_param` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '请求参数', + `operation_result` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '返回参数', + `status` smallint NOT NULL DEFAULT '1' COMMENT '操作状态(1正常 0异常)', + `error_stack` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '错误消息', + `operation_time` datetime NOT NULL COMMENT '操作时间', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除', + PRIMARY KEY (`operation_id`) +) ENGINE=InnoDB AUTO_INCREMENT=379 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='操作日志记录'; + +-- ---------------------------- +-- Records of sys_operation_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_post`; +CREATE TABLE `sys_post` ( + `post_id` bigint NOT NULL AUTO_INCREMENT COMMENT '岗位ID', + `post_code` varchar(64) NOT NULL COMMENT '岗位编码', + `post_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '岗位名称', + `post_sort` int NOT NULL COMMENT '显示顺序', + `status` smallint NOT NULL COMMENT '状态(1正常 0停用)', + `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `creator_id` bigint DEFAULT NULL, + `creator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `updater_id` bigint DEFAULT NULL, + `updater_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除', + PRIMARY KEY (`post_id`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='岗位信息表'; + +-- ---------------------------- +-- Records of sys_post +-- ---------------------------- +INSERT INTO `sys_post` VALUES ('1', 'ceo', '董事长', '1', '1', '', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_post` VALUES ('2', 'se', '项目经理', '2', '1', '', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_post` VALUES ('3', 'hr', '人力资源', '3', '1', '', null, 'admin', '2022-05-21 08:30:54', null, '', null, '0'); +INSERT INTO `sys_post` VALUES ('4', 'user', '普通员工', '5', '0', '', null, 'admin', '2022-05-21 08:30:54', '1', 'admin', '2022-09-02 20:50:32', '0'); + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `role_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色权限字符串', + `role_sort` int NOT NULL COMMENT '显示顺序', + `data_scope` smallint DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3: 本部门数据权限 4: 本部门及以下数据权限 5: 本人权限)', + `dept_id_set` varchar(1024) DEFAULT '' COMMENT '角色所拥有的部门数据权限', + `status` smallint NOT NULL COMMENT '角色状态(1正常 0停用)', + `creator_id` bigint DEFAULT NULL COMMENT '创建者ID', + `creator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `updater_id` bigint DEFAULT NULL COMMENT '更新者ID', + `updater_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (`role_id`) +) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色信息表'; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +INSERT INTO `sys_role` VALUES ('1', '超级管理员', 'admin', '1', '1', '', '1', null, 'admin', '2022-05-21 08:30:54', null, '', null, '超级管理员', '0'); +INSERT INTO `sys_role` VALUES ('2', '普通角色', 'common', '3', '2', '', '1', null, 'admin', '2022-05-21 08:30:54', '1', 'admin', '2022-09-22 19:38:45', '普通角色', '0'); + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `role_id` bigint NOT NULL COMMENT '角色ID', + `menu_id` bigint NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`,`menu_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色和菜单关联表'; + +-- ---------------------------- +-- Records of sys_role_menu +-- ---------------------------- +INSERT INTO `sys_role_menu` VALUES ('104', '1'); +INSERT INTO `sys_role_menu` VALUES ('104', '103'); +INSERT INTO `sys_role_menu` VALUES ('104', '104'); +INSERT INTO `sys_role_menu` VALUES ('104', '1008'); +INSERT INTO `sys_role_menu` VALUES ('104', '1010'); +INSERT INTO `sys_role_menu` VALUES ('108', '1'); +INSERT INTO `sys_role_menu` VALUES ('108', '2'); +INSERT INTO `sys_role_menu` VALUES ('108', '3'); +INSERT INTO `sys_role_menu` VALUES ('108', '100'); +INSERT INTO `sys_role_menu` VALUES ('108', '101'); +INSERT INTO `sys_role_menu` VALUES ('108', '102'); +INSERT INTO `sys_role_menu` VALUES ('108', '103'); +INSERT INTO `sys_role_menu` VALUES ('108', '104'); +INSERT INTO `sys_role_menu` VALUES ('108', '106'); +INSERT INTO `sys_role_menu` VALUES ('108', '107'); +INSERT INTO `sys_role_menu` VALUES ('108', '108'); +INSERT INTO `sys_role_menu` VALUES ('108', '109'); +INSERT INTO `sys_role_menu` VALUES ('108', '111'); +INSERT INTO `sys_role_menu` VALUES ('108', '112'); +INSERT INTO `sys_role_menu` VALUES ('108', '113'); +INSERT INTO `sys_role_menu` VALUES ('108', '116'); +INSERT INTO `sys_role_menu` VALUES ('108', '500'); +INSERT INTO `sys_role_menu` VALUES ('108', '501'); +INSERT INTO `sys_role_menu` VALUES ('108', '1001'); +INSERT INTO `sys_role_menu` VALUES ('108', '1002'); +INSERT INTO `sys_role_menu` VALUES ('108', '1003'); +INSERT INTO `sys_role_menu` VALUES ('108', '1004'); +INSERT INTO `sys_role_menu` VALUES ('108', '1005'); +INSERT INTO `sys_role_menu` VALUES ('108', '1006'); +INSERT INTO `sys_role_menu` VALUES ('108', '1007'); +INSERT INTO `sys_role_menu` VALUES ('108', '1008'); +INSERT INTO `sys_role_menu` VALUES ('108', '1009'); +INSERT INTO `sys_role_menu` VALUES ('108', '1010'); +INSERT INTO `sys_role_menu` VALUES ('108', '1011'); +INSERT INTO `sys_role_menu` VALUES ('108', '1012'); +INSERT INTO `sys_role_menu` VALUES ('108', '1013'); +INSERT INTO `sys_role_menu` VALUES ('108', '1014'); +INSERT INTO `sys_role_menu` VALUES ('108', '1015'); +INSERT INTO `sys_role_menu` VALUES ('108', '1016'); +INSERT INTO `sys_role_menu` VALUES ('108', '1017'); +INSERT INTO `sys_role_menu` VALUES ('108', '1018'); +INSERT INTO `sys_role_menu` VALUES ('108', '1019'); +INSERT INTO `sys_role_menu` VALUES ('108', '1020'); +INSERT INTO `sys_role_menu` VALUES ('108', '1021'); +INSERT INTO `sys_role_menu` VALUES ('108', '1022'); +INSERT INTO `sys_role_menu` VALUES ('108', '1023'); +INSERT INTO `sys_role_menu` VALUES ('108', '1024'); +INSERT INTO `sys_role_menu` VALUES ('108', '1025'); +INSERT INTO `sys_role_menu` VALUES ('108', '1031'); +INSERT INTO `sys_role_menu` VALUES ('108', '1032'); +INSERT INTO `sys_role_menu` VALUES ('108', '1033'); +INSERT INTO `sys_role_menu` VALUES ('108', '1034'); +INSERT INTO `sys_role_menu` VALUES ('108', '1035'); +INSERT INTO `sys_role_menu` VALUES ('108', '1036'); +INSERT INTO `sys_role_menu` VALUES ('108', '1037'); +INSERT INTO `sys_role_menu` VALUES ('108', '1038'); +INSERT INTO `sys_role_menu` VALUES ('108', '1039'); +INSERT INTO `sys_role_menu` VALUES ('108', '1040'); +INSERT INTO `sys_role_menu` VALUES ('108', '1041'); +INSERT INTO `sys_role_menu` VALUES ('108', '1042'); +INSERT INTO `sys_role_menu` VALUES ('108', '1043'); +INSERT INTO `sys_role_menu` VALUES ('108', '1044'); +INSERT INTO `sys_role_menu` VALUES ('108', '1045'); +INSERT INTO `sys_role_menu` VALUES ('108', '1046'); +INSERT INTO `sys_role_menu` VALUES ('108', '1047'); +INSERT INTO `sys_role_menu` VALUES ('108', '1048'); +INSERT INTO `sys_role_menu` VALUES ('108', '2000'); +INSERT INTO `sys_role_menu` VALUES ('108', '2001'); + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `post_id` bigint DEFAULT NULL COMMENT '职位id', + `role_id` bigint DEFAULT NULL COMMENT '角色id', + `dept_id` bigint DEFAULT NULL COMMENT '部门ID', + `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户昵称', + `user_type` smallint DEFAULT '0' COMMENT '用户类型(00系统用户)', + `email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '用户邮箱', + `phone_number` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '手机号码', + `sex` smallint DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '头像地址', + `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '密码', + `status` smallint NOT NULL DEFAULT '0' COMMENT '帐号状态(1正常 2停用 3冻结)', + `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime DEFAULT NULL COMMENT '最后登录时间', + `creator_id` bigint DEFAULT NULL COMMENT '更新者ID', + `creator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `updater_id` bigint DEFAULT NULL COMMENT '更新者ID', + `updater_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户信息表'; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES ('1', '1', '1', '103', 'admin', 'valarchie1', '0', 'agileboot@1631.com', '15888888889', '0', '/profile//avatar/20220814122428_blob_81ea3759fd45478ab0d875041afa5a28.jpeg', '$2a$10$rb1wRoEIkLbIknREEN1LH.FGs4g0oOS5t6l5LQ793nRaFO.SPHDHy', '1', '127.0.0.1', '2022-10-06 17:00:06', null, 'admin', '2022-05-21 08:30:54', '1', 'admin', '2022-10-06 17:00:06', '管理员', '0'); +INSERT INTO `sys_user` VALUES ('2', '2', '2', '105', 'ag', 'valarchie', '0', 'agileboot@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '1', '127.0.0.1', '2022-05-21 08:30:54', null, 'admin', '2022-05-21 08:30:54', null, '', null, '测试员', '0'); +INSERT INTO `sys_user` VALUES ('107', '3', '2', '104', 'ag12345', '测试', '0', '222@4234.com', '18859976673', '2', '', '12345678', '1', '', null, '1', 'admin', '2022-10-05 22:01:43', '1', 'admin', '2022-10-05 22:02:03', '12312312', '1'); +INSERT INTO `sys_user` VALUES ('108', '1', '108', '101', 'ag888888', '23423423@42432.com', '0', '234452@42344aa.com', '18849976671', '2', '', '$2a$10$T/K2DSLtnr4yH9z98kx9AOUFlDf4QOBAwBjcY2WNFfDsNxR1NIv8u', '1', '127.0.0.1', '2022-10-05 22:54:41', '1', 'admin', '2022-10-05 22:26:31', '1', 'admin', '2022-10-05 22:54:41', '23424234234234', '0');