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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:.*Style
+
+ http://schemas.android.com/apk/res/android
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:layout_width
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_height
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_weight
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_margin
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginTop
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginBottom
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginStart
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginEnd
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginLeft
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginRight
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_.*
+
+ http://schemas.android.com/apk/res/android
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:padding
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingTop
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingBottom
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingStart
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingEnd
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingLeft
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingRight
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*
+ http://schemas.android.com/apk/res/android
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+ http://schemas.android.com/apk/res-auto
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+ http://schemas.android.com/tools
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+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群: [](https://jq.qq.com/?_wv=1027&k=5bVB1og) [](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [](https://jq.qq.com/?_wv=1027&k=51G72yr) [](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [](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