37 Commits

Author SHA1 Message Date
cuijiawang
b366869a00 gateway 2025-10-13 16:32:54 +08:00
cuijiawang
a8e2887bbf fix 2025-10-11 09:55:31 +08:00
cuijiawang
92b772373b fix: port 2025-09-30 14:54:05 +08:00
cuijiawang
8f433a2594 fix: 启动类 2025-09-30 14:48:56 +08:00
cuijiawang
e0db7a251c Merge branch 'dev-pwd' into dev-codegen 2025-09-30 10:20:40 +08:00
cuijiawang
84bb7410cf fix: 登录校验密码 2025-09-30 10:19:52 +08:00
cuijiawang
2d75478bb7 fix: 代码生成器 系统模板 2025-09-29 16:55:30 +08:00
cuijiawang
5e47425ab5 fix: 代码生成器 未完成的解析器 2025-09-29 14:44:18 +08:00
cuijiawang
a4d6306259 fix: 性能优化 2025-09-28 10:41:05 +08:00
cuijiawang
852eb05cbf fix: 代码生成器 模板内容预览 系统内容 2025-09-28 09:31:09 +08:00
cuijiawang
90f98d66cc feat: 代码生成器 模板内容预览 2025-09-27 19:38:07 +08:00
cuijiawang
bf6e0dd952 feat: 代码生成器 自定义模板 使用次数 2025-09-27 19:01:07 +08:00
cuijiawang
04da9f9c02 feat: 代码生成器 自定义模板 显隐控制 2025-09-27 18:33:33 +08:00
cuijiawang
b4525cd1a6 fix: 代码生成器 自定义模板 逻辑删除问题 2025-09-27 17:50:32 +08:00
cuijiawang
b51f113053 fix: 代码生成器 自定义模板 复制 2025-09-27 17:23:19 +08:00
cuijiawang
685cc3d92c fix: 代码生成器 自定义模板 新增删除 2025-09-27 17:15:32 +08:00
cuijiawang
e512fc29e0 fix: 代码生成器 自定义模板 编辑 2025-09-27 16:38:17 +08:00
cuijiawang
5d19f5589a 代码生成器 模板仓库 模板预览 2025-09-27 16:03:47 +08:00
cuijiawang
d6be017f37 代码生成器 模板仓库 模板描述 2025-09-27 15:22:50 +08:00
cuijiawang
db5bda1bba 代码生成器 模板仓库fix 2025-09-27 14:19:02 +08:00
cuijiawang
c9929c84d2 代码生成器 模板仓库 2025-09-26 17:55:35 +08:00
cuijiawang
f91ecee6cc 代码生成功能 2025-09-25 14:52:14 +08:00
cuijiawang
142c24ca90 ftl 2025-09-25 14:14:03 +08:00
cuijiawang
a59dd25156 Merge branch 'dev' into dev-codegen 2025-09-24 17:26:59 +08:00
cuijiawang
d23500d656 del 2025-09-24 16:49:29 +08:00
cuijiawang
fe8d987527 del 2025-09-24 16:41:06 +08:00
cuijiawang
a3ec97fec1 codegen 2025-09-24 16:38:11 +08:00
cuijiawang
ea37855991 fix user 2025-09-24 11:51:27 +08:00
cuijiawang
c54a0db6ab fix 2025-09-23 17:05:42 +08:00
cuijiawang
f0633dfcea Merge branch 'dev-hzm' into dev
# Conflicts:
#	agileboot-system/agileboot-system-base/src/main/java/com/agileboot/system/user/service/ISysUserService.java
#	agileboot-system/agileboot-system-base/src/main/java/com/agileboot/system/user/service/impl/SysUserServiceImpl.java
2025-09-23 16:58:14 +08:00
cuijiawang
f4ce15986c Merge branch 'dev-role' into dev 2025-09-23 11:50:06 +08:00
cuijiawang
46da855dd4 新增时不填充修改字段 2025-09-23 09:53:22 +08:00
Hzm
b06d2503a4 修复--修改角色菜单不生效 2025-09-22 19:21:44 +08:00
cuijiawang
b2c882f70f 角色增删改查 未完成 2025-09-22 18:09:08 +08:00
cuijiawang
c8899a20a4 fix 2025-09-22 17:56:29 +08:00
cuijiawang
59e5777e28 增加物理删除 2025-09-22 17:54:33 +08:00
cuijiawang
d379f79869 部门增删改查 2025-09-22 11:28:12 +08:00
387 changed files with 7412 additions and 16113 deletions

View File

@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>agileboot</artifactId>
<groupId>com.agileboot</groupId>
<version>1.0.0</version>
</parent>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
<artifactId>agileboot-admin</artifactId>
<description>
web服务入口
</description>
<dependencies>
<!-- 业务领域 -->
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-domain</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.plugin.version}</version>
<!-- 想跑test的话 设置成false -->
<configuration>
<skipTests>false</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,32 +0,0 @@
package com.agileboot.admin;
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 AgileBootAdminApplication {
public static void main(String[] args) {
SpringApplication.run(AgileBootAdminApplication.class, args);
String successMsg = " ____ _ _ __ _ _ \n"
+ " / ___| | |_ __ _ _ __ | |_ _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / _| _ _ | || |\n"
+ " \\___ \\ | __|/ _` || '__|| __| | | | || '_ \\ / __|| | | | / __|/ __|/ _ \\/ __|/ __|| |_ | | | || || |\n"
+ " ___) || |_| (_| || | | |_ | |_| || |_) | \\__ \\| |_| || (__| (__| __/\\__ \\\\__ \\| _|| |_| || ||_|\n"
+ " |____/ \\__|\\__,_||_| \\__| \\__,_|| .__/ |___/ \\__,_| \\___|\\___|\\___||___/|___/|_| \\__,_||_|(_)\n"
+ " |_| ";
System.out.println(successMsg);
}
}

View File

@@ -1,129 +0,0 @@
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 com.agileboot.common.constant.Constants.UploadSubDir;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.common.utils.file.FileUploadUtils;
import com.agileboot.common.utils.jackson.JacksonUtil;
import com.agileboot.domain.common.dto.UploadDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.ArrayList;
import java.util.List;
import jakarta.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;
/**
* 通用请求处理
* TODO 需要重构
* @author valarchie
*/
@Tag(name = "上传API", description = "上传相关接口")
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {
/**
* 通用下载请求
* download接口 其实不是很有必要
* @param fileName 文件名称
*/
@Operation(summary = "下载文件")
@GetMapping("/download")
public ResponseEntity<byte[]> fileDownload(String fileName, HttpServletResponse response) {
try {
if (!FileUploadUtils.isAllowDownload(fileName)) {
// 返回类型是ResponseEntity 不能捕获异常, 需要手动将错误填到 ResponseEntity
ResponseDTO<Object> fail = ResponseDTO.fail(
new ApiException(Business.COMMON_FILE_NOT_ALLOWED_TO_DOWNLOAD, fileName));
return new ResponseEntity<>(JacksonUtil.to(fail).getBytes(), null, HttpStatus.OK);
}
String filePath = FileUploadUtils.getFileAbsolutePath(UploadSubDir.DOWNLOAD_PATH, fileName);
HttpHeaders downloadHeader = FileUploadUtils.getDownloadHeader(fileName);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
return new ResponseEntity<>(FileUtil.readBytes(filePath), downloadHeader, HttpStatus.OK);
} catch (Exception e) {
log.error("下载文件失败", e);
return null;
}
}
/**
* 通用上传请求(单个)
*/
@Operation(summary = "单个上传文件")
@PostMapping("/upload")
public ResponseDTO<UploadDTO> uploadFile(MultipartFile file) {
if (file == null) {
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY);
}
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(UploadSubDir.UPLOAD_PATH, 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);
}
/**
* 通用上传请求(多个)
*/
@Operation(summary = "多个上传文件")
@PostMapping("/uploads")
public ResponseDTO<List<UploadDTO>> uploadFiles(List<MultipartFile> files) {
if (CollUtil.isEmpty(files)) {
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY);
}
List<UploadDTO> uploads = new ArrayList<>();
for (MultipartFile file : files) {
if (file != null) {
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(UploadSubDir.UPLOAD_PATH, 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);
}
}

View File

@@ -1,139 +0,0 @@
package com.agileboot.admin.controller.common;
import cn.hutool.core.util.StrUtil;
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.Business;
import com.agileboot.domain.common.dto.CurrentLoginUserDTO;
import com.agileboot.domain.common.dto.TokenDTO;
import com.agileboot.domain.system.menu.MenuApplicationService;
import com.agileboot.domain.system.menu.dto.RouterDTO;
import com.agileboot.domain.system.user.UserApplicationService;
import com.agileboot.domain.system.user.command.AddUserCommand;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.CacheType;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.LimitType;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.admin.customize.service.login.dto.CaptchaDTO;
import com.agileboot.admin.customize.service.login.dto.ConfigDTO;
import com.agileboot.admin.customize.service.login.command.LoginCommand;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimitKey;
import com.agileboot.admin.customize.service.login.LoginService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
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.RestController;
/**
* 首页
*
* @author valarchie
*/
@Tag(name = "登录API", description = "登录相关接口")
@RestController
@RequiredArgsConstructor
public class LoginController {
private final LoginService loginService;
private final MenuApplicationService menuApplicationService;
private final UserApplicationService userApplicationService;
private final AgileBootConfig agileBootConfig;
/**
* 访问首页,提示语
*/
@Operation(summary = "首页")
@GetMapping("/")
@RateLimit(key = RateLimitKey.TEST_KEY, time = 10, maxCount = 5, cacheType = CacheType.Map,
limitType = LimitType.GLOBAL)
public String index() {
return StrUtil.format("欢迎使用{}后台管理框架当前版本v{},请通过前端地址访问。",
agileBootConfig.getName(), agileBootConfig.getVersion());
}
/**
* 获取系统的内置配置
*
* @return 配置信息
*/
@GetMapping("/getConfig")
public ResponseDTO<ConfigDTO> getConfig() {
ConfigDTO configDTO = loginService.getConfig();
return ResponseDTO.ok(configDTO);
}
/**
* 生成验证码
*/
@Operation(summary = "验证码")
@RateLimit(key = RateLimitKey.LOGIN_CAPTCHA_KEY, time = 10, maxCount = 10, cacheType = CacheType.REDIS,
limitType = LimitType.IP)
@GetMapping("/captchaImage")
public ResponseDTO<CaptchaDTO> getCaptchaImg() {
CaptchaDTO captchaImg = loginService.generateCaptchaImg();
return ResponseDTO.ok(captchaImg);
}
/**
* 登录方法
*
* @param loginCommand 登录信息
* @return 结果
*/
@Operation(summary = "登录")
@PostMapping("/login")
public ResponseDTO<TokenDTO> login(@RequestBody LoginCommand loginCommand) {
// 生成令牌
String token = loginService.login(loginCommand);
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser);
return ResponseDTO.ok(new TokenDTO(token, currentUserDTO));
}
/**
* 获取用户信息
*
* @return 用户信息
*/
@Operation(summary = "获取当前登录用户信息")
@GetMapping("/getLoginUserInfo")
public ResponseDTO<CurrentLoginUserDTO> getLoginUserInfo() {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser);
return ResponseDTO.ok(currentUserDTO);
}
/**
* 获取路由信息
* TODO 如果要在前端开启路由缓存的话 需要在ServerConfig.json 中 设置CachingAsyncRoutes=true 避免一直重复请求路由接口
* @return 路由信息
*/
@Operation(summary = "获取用户对应的菜单路由", description = "用于动态生成路由")
@GetMapping("/getRouters")
public ResponseDTO<List<RouterDTO>> getRouters() {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
List<RouterDTO> routerTree = menuApplicationService.getRouterTree(loginUser);
return ResponseDTO.ok(routerTree);
}
@Operation(summary = "注册接口", description = "暂未实现")
@PostMapping("/register")
public ResponseDTO<Void> register(@RequestBody AddUserCommand command) {
return ResponseDTO.fail(new ApiException(Business.COMMON_UNSUPPORTED_OPERATION));
}
}

View File

@@ -1,82 +0,0 @@
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.cache.CacheCenter;
import com.agileboot.domain.system.monitor.MonitorApplicationService;
import com.agileboot.domain.system.monitor.dto.OnlineUserDTO;
import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO;
import com.agileboot.domain.system.monitor.dto.ServerInfo;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
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 valarchie
*/
@Tag(name = "监控API", description = "监控相关信息")
@RestController
@RequestMapping("/monitor")
@RequiredArgsConstructor
public class MonitorController extends BaseController {
private final MonitorApplicationService monitorApplicationService;
@Operation(summary = "Redis信息")
@PreAuthorize("@permission.has('monitor:cache:list')")
@GetMapping("/cacheInfo")
public ResponseDTO<RedisCacheInfoDTO> getRedisCacheInfo() {
RedisCacheInfoDTO redisCacheInfo = monitorApplicationService.getRedisCacheInfo();
return ResponseDTO.ok(redisCacheInfo);
}
@Operation(summary = "服务器信息")
@PreAuthorize("@permission.has('monitor:server:list')")
@GetMapping("/serverInfo")
public ResponseDTO<ServerInfo> getServerInfo() {
ServerInfo serverInfo = monitorApplicationService.getServerInfo();
return ResponseDTO.ok(serverInfo);
}
/**
* 获取在线用户列表
*
* @param ipAddress ip地址
* @param username 用户名
* @return 分页处理后的在线用户信息
*/
@Operation(summary = "在线用户列表")
@PreAuthorize("@permission.has('monitor:online:list')")
@GetMapping("/onlineUsers")
public ResponseDTO<PageDTO<OnlineUserDTO>> onlineUsers(String ipAddress, String username) {
List<OnlineUserDTO> onlineUserList = monitorApplicationService.getOnlineUserList(username, ipAddress);
return ResponseDTO.ok(new PageDTO<>(onlineUserList));
}
/**
* 强退用户
*/
@Operation(summary = "强退用户")
@PreAuthorize("@permission.has('monitor:online:forceLogout')")
@AccessLog(title = "在线用户", businessType = BusinessTypeEnum.FORCE_LOGOUT)
@DeleteMapping("/onlineUser/{tokenId}")
public ResponseDTO<Void> logoutOnlineUser(@PathVariable String tokenId) {
CacheCenter.loginUserCache.delete(tokenId);
return ResponseDTO.ok();
}
}

View File

@@ -1,88 +0,0 @@
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.cache.CacheCenter;
import com.agileboot.domain.system.config.ConfigApplicationService;
import com.agileboot.domain.system.config.command.ConfigUpdateCommand;
import com.agileboot.domain.system.config.dto.ConfigDTO;
import com.agileboot.domain.system.config.query.ConfigQuery;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
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")
@Validated
@RequiredArgsConstructor
@Tag(name = "配置API", description = "配置相关的增删查改")
public class SysConfigController extends BaseController {
private final ConfigApplicationService configApplicationService;
/**
* 获取参数配置列表
*/
@Operation(summary = "参数列表", description = "分页获取配置参数列表")
@PreAuthorize("@permission.has('system:config:list')")
@GetMapping("/configs")
public ResponseDTO<PageDTO<ConfigDTO>> list(ConfigQuery query) {
PageDTO<ConfigDTO> page = configApplicationService.getConfigList(query);
return ResponseDTO.ok(page);
}
/**
* 根据参数编号获取详细信息
*/
@PreAuthorize("@permission.has('system:config:query')")
@GetMapping(value = "/config/{configId}")
@Operation(summary = "配置信息", description = "配置的详细信息")
public ResponseDTO<ConfigDTO> getInfo(@NotNull @Positive @PathVariable Long configId) {
ConfigDTO config = configApplicationService.getConfigInfo(configId);
return ResponseDTO.ok(config);
}
/**
* 修改参数配置
*/
@PreAuthorize("@permission.has('system:config:edit')")
@AccessLog(title = "参数管理", businessType = BusinessTypeEnum.MODIFY)
@Operation(summary = "配置修改", description = "配置修改")
@PutMapping(value = "/config/{configId}")
public ResponseDTO<Void> edit(@NotNull @Positive @PathVariable Long configId, @RequestBody ConfigUpdateCommand config) {
config.setConfigId(configId);
configApplicationService.updateConfig(config);
return ResponseDTO.ok();
}
/**
* 刷新参数缓存
*/
@Operation(summary = "刷新配置缓存")
@PreAuthorize("@permission.has('system:config:remove')")
@AccessLog(title = "参数管理", businessType = BusinessTypeEnum.CLEAN)
@DeleteMapping("/configs/cache")
public ResponseDTO<Void> refreshCache() {
CacheCenter.configCache.invalidateAll();
return ResponseDTO.ok();
}
}

View File

@@ -1,111 +0,0 @@
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.dept.DeptApplicationService;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.command.UpdateDeptCommand;
import com.agileboot.domain.system.dept.dto.DeptDTO;
import com.agileboot.domain.system.dept.query.DeptQuery;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
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")
@Validated
@RequiredArgsConstructor
@Tag(name = "部门API", description = "部门相关的增删查改")
public class SysDeptController extends BaseController {
private final DeptApplicationService deptApplicationService;
/**
* 获取部门列表
*/
@Operation(summary = "部门列表")
@PreAuthorize("@permission.has('system:dept:list')")
@GetMapping("/depts")
public ResponseDTO<List<DeptDTO>> list(DeptQuery query) {
List<DeptDTO> deptList = deptApplicationService.getDeptList(query);
return ResponseDTO.ok(deptList);
}
/**
* 根据部门编号获取详细信息
*/
@Operation(summary = "部门详情")
@PreAuthorize("@permission.has('system:dept:query')")
@GetMapping(value = "/dept/{deptId}")
public ResponseDTO<DeptDTO> getInfo(@PathVariable Long deptId) {
DeptDTO dept = deptApplicationService.getDeptInfo(deptId);
return ResponseDTO.ok(dept);
}
/**
* 获取部门下拉树列表
*/
@Operation(summary = "获取部门树级结构")
@GetMapping("/depts/dropdown")
public ResponseDTO<List<Tree<Long>>> dropdownList() {
List<Tree<Long>> deptTree = deptApplicationService.getDeptTree();
return ResponseDTO.ok(deptTree);
}
/**
* 新增部门
*/
@Operation(summary = "新增部门")
@PreAuthorize("@permission.has('system:dept:add')")
@AccessLog(title = "部门管理", businessType = BusinessTypeEnum.ADD)
@PostMapping("/dept")
public ResponseDTO<Void> add(@RequestBody AddDeptCommand addCommand) {
deptApplicationService.addDept(addCommand);
return ResponseDTO.ok();
}
/**
* 修改部门
*/
@Operation(summary = "修改部门")
@PreAuthorize("@permission.has('system:dept:edit') AND @dataScope.checkDeptId(#updateCommand.deptId)")
@AccessLog(title = "部门管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/dept/{deptId}")
public ResponseDTO<Void> edit(@PathVariable("deptId")Long deptId, @RequestBody UpdateDeptCommand updateCommand) {
updateCommand.setDeptId(deptId);
deptApplicationService.updateDept(updateCommand);
return ResponseDTO.ok();
}
/**
* 删除部门
*/
@Operation(summary = "删除部门")
@PreAuthorize("@permission.has('system:dept:remove') AND @dataScope.checkDeptId(#deptId)")
@AccessLog(title = "部门管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping("/dept/{deptId}")
public ResponseDTO<Void> remove(@PathVariable @NotNull Long deptId) {
deptApplicationService.removeDept(deptId);
return ResponseDTO.ok();
}
}

View File

@@ -1,120 +0,0 @@
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.command.BulkOperationCommand;
import com.agileboot.domain.system.log.LogApplicationService;
import com.agileboot.domain.system.log.dto.LoginLogDTO;
import com.agileboot.domain.system.log.query.LoginLogQuery;
import com.agileboot.domain.system.log.dto.OperationLogDTO;
import com.agileboot.domain.system.log.query.OperationLogQuery;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 系统访问记录
*
* @author valarchie
*/
@Tag(name = "日志API", description = "日志相关API")
@RestController
@RequestMapping("/logs")
@Validated
@RequiredArgsConstructor
public class SysLogsController extends BaseController {
private final LogApplicationService logApplicationService;
@Operation(summary = "登录日志列表")
@PreAuthorize("@permission.has('monitor:logininfor:list')")
@GetMapping("/loginLogs")
public ResponseDTO<PageDTO<LoginLogDTO>> loginInfoList(LoginLogQuery query) {
PageDTO<LoginLogDTO> pageDTO = logApplicationService.getLoginInfoList(query);
return ResponseDTO.ok(pageDTO);
}
@Operation(summary = "登录日志导出", description = "将登录日志导出到excel")
@AccessLog(title = "登录日志", businessType = BusinessTypeEnum.EXPORT)
@PreAuthorize("@permission.has('monitor:logininfor:export')")
@GetMapping("/loginLogs/excel")
public void loginInfosExcel(HttpServletResponse response, LoginLogQuery query) {
PageDTO<LoginLogDTO> pageDTO = logApplicationService.getLoginInfoList(query);
CustomExcelUtil.writeToResponse(pageDTO.getRows(), LoginLogDTO.class, response);
}
@Operation(summary = "删除登录日志")
@PreAuthorize("@permission.has('monitor:logininfor:remove')")
@AccessLog(title = "登录日志", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping("/loginLogs")
public ResponseDTO<Void> removeLoginInfos(@RequestParam @NotNull @NotEmpty List<Long> ids) {
logApplicationService.deleteLoginInfo(new BulkOperationCommand<>(ids));
return ResponseDTO.ok();
}
@Operation(summary = "操作日志列表")
@PreAuthorize("@permission.has('monitor:operlog:list')")
@GetMapping("/operationLogs")
public ResponseDTO<PageDTO<OperationLogDTO>> operationLogs(OperationLogQuery query) {
PageDTO<OperationLogDTO> pageDTO = logApplicationService.getOperationLogList(query);
return ResponseDTO.ok(pageDTO);
}
// @GetMapping("/download")
// public ResponseEntity<InputStreamResource> downloadFile() throws IOException {
// // 从文件系统或其他位置获取文件输入流
// File file = new File("path/to/file");
// InputStream inputStream = new FileInputStream(file);
// CustomExcelUtil.wri
//
// // 创建一个 InputStreamResource 对象,将文件输入流包装在其中
// InputStreamResource resource = new InputStreamResource(inputStream);
//
// // 返回 ResponseEntity 对象,其中包含 InputStreamResource 对象和文件名
// return ResponseEntity.ok()
// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
// .contentType(MediaType.APPLICATION_OCTET_STREAM)
// .contentLength(file.length())
// .body(resource);
// }
/**
* 可否改成以上的形式 TODO
* @param response
* @param query
*/
@Operation(summary = "操作日志导出")
@AccessLog(title = "操作日志", businessType = BusinessTypeEnum.EXPORT)
@PreAuthorize("@permission.has('monitor:operlog:export')")
@GetMapping("/operationLogs/excel")
public void operationLogsExcel(HttpServletResponse response, OperationLogQuery query) {
PageDTO<OperationLogDTO> pageDTO = logApplicationService.getOperationLogList(query);
CustomExcelUtil.writeToResponse(pageDTO.getRows(), OperationLogDTO.class, response);
}
@Operation(summary = "删除操作日志")
@AccessLog(title = "操作日志", businessType = BusinessTypeEnum.DELETE)
@PreAuthorize("@permission.has('monitor:operlog:remove')")
@DeleteMapping("/operationLogs")
public ResponseDTO<Void> removeOperationLogs(@RequestParam List<Long> operationIds) {
logApplicationService.deleteOperationLog(new BulkOperationCommand<>(operationIds));
return ResponseDTO.ok();
}
}

View File

@@ -1,120 +0,0 @@
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.menu.MenuApplicationService;
import com.agileboot.domain.system.menu.command.AddMenuCommand;
import com.agileboot.domain.system.menu.command.UpdateMenuCommand;
import com.agileboot.domain.system.menu.dto.MenuDTO;
import com.agileboot.domain.system.menu.dto.MenuDetailDTO;
import com.agileboot.domain.system.menu.query.MenuQuery;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.RequiredArgsConstructor;
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
*/
@Tag(name = "菜单API", description = "菜单相关的增删查改")
@RestController
@RequestMapping("/system/menus")
@Validated
@RequiredArgsConstructor
public class SysMenuController extends BaseController {
private final MenuApplicationService menuApplicationService;
/**
* 获取菜单列表
*/
@Operation(summary = "菜单列表")
@PreAuthorize("@permission.has('system:menu:list')")
@GetMapping
public ResponseDTO<List<MenuDTO>> menuList(MenuQuery menuQuery) {
List<MenuDTO> menuList = menuApplicationService.getMenuList(menuQuery);
return ResponseDTO.ok(menuList);
}
/**
* 根据菜单编号获取详细信息
*/
@Operation(summary = "菜单详情")
@PreAuthorize("@permission.has('system:menu:query')")
@GetMapping(value = "/{menuId}")
public ResponseDTO<MenuDetailDTO> menuInfo(@PathVariable @NotNull @PositiveOrZero Long menuId) {
MenuDetailDTO menu = menuApplicationService.getMenuInfo(menuId);
return ResponseDTO.ok(menu);
}
/**
* 获取菜单下拉树列表
*/
@Operation(summary = "菜单列表(树级)", description = "菜单树级下拉框")
@GetMapping("/dropdown")
public ResponseDTO<List<Tree<Long>>> dropdownList() {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
List<Tree<Long>> dropdownList = menuApplicationService.getDropdownList(loginUser);
return ResponseDTO.ok(dropdownList);
}
/**
* 新增菜单
* 需支持一级菜单以及 多级菜单 子菜单为一个 或者 多个的情况
* 隐藏菜单不显示 以及rank排序
* 内链 和 外链
*/
@Operation(summary = "添加菜单")
@PreAuthorize("@permission.has('system:menu:add')")
@AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@RequestBody AddMenuCommand addCommand) {
menuApplicationService.addMenu(addCommand);
return ResponseDTO.ok();
}
/**
* 修改菜单
*/
@Operation(summary = "编辑菜单")
@PreAuthorize("@permission.has('system:menu:edit')")
@AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{menuId}")
public ResponseDTO<Void> edit(@PathVariable("menuId") Long menuId, @RequestBody UpdateMenuCommand updateCommand) {
updateCommand.setMenuId(menuId);
menuApplicationService.updateMenu(updateCommand);
return ResponseDTO.ok();
}
/**
* 删除菜单
*/
@Operation(summary = "删除菜单")
@PreAuthorize("@permission.has('system:menu:remove')")
@AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping("/{menuId}")
public ResponseDTO<Void> remove(@PathVariable("menuId") Long menuId) {
menuApplicationService.remove(menuId);
return ResponseDTO.ok();
}
}

View File

@@ -1,122 +0,0 @@
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.command.BulkOperationCommand;
import com.agileboot.domain.system.notice.NoticeApplicationService;
import com.agileboot.domain.system.notice.command.NoticeAddCommand;
import com.agileboot.domain.system.notice.command.NoticeUpdateCommand;
import com.agileboot.domain.system.notice.dto.NoticeDTO;
import com.agileboot.domain.system.notice.query.NoticeQuery;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.infrastructure.annotations.unrepeatable.Unrepeatable;
import com.agileboot.infrastructure.annotations.unrepeatable.Unrepeatable.CheckType;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.baomidou.dynamic.datasource.annotation.DS;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 公告 信息操作处理
*
* @author valarchie
*/
@Tag(name = "公告API", description = "公告相关的增删查改")
@RestController
@RequestMapping("/system/notices")
@Validated
@RequiredArgsConstructor
public class SysNoticeController extends BaseController {
private final NoticeApplicationService noticeApplicationService;
/**
* 获取通知公告列表
*/
@Operation(summary = "公告列表")
@PreAuthorize("@permission.has('system:notice:list')")
@GetMapping
public ResponseDTO<PageDTO<NoticeDTO>> list(NoticeQuery query) {
PageDTO<NoticeDTO> pageDTO = noticeApplicationService.getNoticeList(query);
return ResponseDTO.ok(pageDTO);
}
/**
* 获取通知公告列表
* 从从库获取数据 例子 仅供参考
*/
@Operation(summary = "公告列表(从数据库从库获取)", description = "演示主从库的例子")
@DS("slave")
@PreAuthorize("@permission.has('system:notice:list')")
@GetMapping("/database/slave")
public ResponseDTO<PageDTO<NoticeDTO>> listFromSlave(NoticeQuery query) {
PageDTO<NoticeDTO> pageDTO = noticeApplicationService.getNoticeList(query);
return ResponseDTO.ok(pageDTO);
}
/**
* 根据通知公告编号获取详细信息
*/
@Operation(summary = "公告详情")
@PreAuthorize("@permission.has('system:notice:query')")
@GetMapping(value = "/{noticeId}")
public ResponseDTO<NoticeDTO> getInfo(@PathVariable @NotNull @Positive Long noticeId) {
return ResponseDTO.ok(noticeApplicationService.getNoticeInfo(noticeId));
}
/**
* 新增通知公告
*/
@Operation(summary = "添加公告")
@Unrepeatable(interval = 60, checkType = CheckType.SYSTEM_USER)
@PreAuthorize("@permission.has('system:notice:add')")
@AccessLog(title = "通知公告", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@RequestBody NoticeAddCommand addCommand) {
noticeApplicationService.addNotice(addCommand);
return ResponseDTO.ok();
}
/**
* 修改通知公告
*/
@Operation(summary = "修改公告")
@PreAuthorize("@permission.has('system:notice:edit')")
@AccessLog(title = "通知公告", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{noticeId}")
public ResponseDTO<Void> edit(@PathVariable Long noticeId, @RequestBody NoticeUpdateCommand updateCommand) {
updateCommand.setNoticeId(noticeId);
noticeApplicationService.updateNotice(updateCommand);
return ResponseDTO.ok();
}
/**
* 删除通知公告
*/
@Operation(summary = "删除公告")
@PreAuthorize("@permission.has('system:notice:remove')")
@AccessLog(title = "通知公告", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping
public ResponseDTO<Void> remove(@RequestParam List<Integer> noticeIds) {
noticeApplicationService.deleteNotice(new BulkOperationCommand<>(noticeIds));
return ResponseDTO.ok();
}
}

View File

@@ -1,122 +0,0 @@
package com.agileboot.admin.controller.system;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
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.enums.common.BusinessTypeEnum;
import com.agileboot.common.utils.poi.CustomExcelUtil;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.system.post.PostApplicationService;
import com.agileboot.domain.system.post.command.AddPostCommand;
import com.agileboot.domain.system.post.command.UpdatePostCommand;
import com.agileboot.domain.system.post.dto.PostDTO;
import com.agileboot.domain.system.post.query.PostQuery;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 岗位信息操作处理
*
* @author ruoyi
*/
@Tag(name = "职位API", description = "职位相关的增删查改")
@RestController
@RequestMapping("/system/post")
@Validated
@RequiredArgsConstructor
public class SysPostController extends BaseController {
private final PostApplicationService postApplicationService;
/**
* 获取岗位列表
*/
@Operation(summary = "职位列表")
@PreAuthorize("@permission.has('system:post:list')")
@GetMapping("/list")
public ResponseDTO<PageDTO<PostDTO>> list(PostQuery query) {
PageDTO<PostDTO> pageDTO = postApplicationService.getPostList(query);
return ResponseDTO.ok(pageDTO);
}
/**
* 导出查询到的所有岗位信息到excel文件
* @param response http响应
* @param query 查询参数
* @author Kevin Zhang
* @date 2023-10-02
*/
@Operation(summary = "职位列表导出")
@AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.EXPORT)
@PreAuthorize("@permission.has('system:post:export')")
@GetMapping("/excel")
public void export(HttpServletResponse response, PostQuery query) {
List<PostDTO> all = postApplicationService.getPostListAll(query);
CustomExcelUtil.writeToResponse(all, PostDTO.class, response);
}
/**
* 根据岗位编号获取详细信息
*/
@Operation(summary = "职位详情")
@PreAuthorize("@permission.has('system:post:query')")
@GetMapping(value = "/{postId}")
public ResponseDTO<PostDTO> getInfo(@PathVariable Long postId) {
PostDTO post = postApplicationService.getPostInfo(postId);
return ResponseDTO.ok(post);
}
/**
* 新增岗位
*/
@Operation(summary = "添加职位")
@PreAuthorize("@permission.has('system:post:add')")
@AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@RequestBody AddPostCommand addCommand) {
postApplicationService.addPost(addCommand);
return ResponseDTO.ok();
}
/**
* 修改岗位
*/
@Operation(summary = "修改职位")
@PreAuthorize("@permission.has('system:post:edit')")
@AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping
public ResponseDTO<Void> edit(@RequestBody UpdatePostCommand updateCommand) {
postApplicationService.updatePost(updateCommand);
return ResponseDTO.ok();
}
/**
* 删除岗位
*/
@Operation(summary = "删除职位")
@PreAuthorize("@permission.has('system:post:remove')")
@AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping
public ResponseDTO<Void> remove(@RequestParam @NotNull @NotEmpty List<Long> ids) {
postApplicationService.deletePost(new BulkOperationCommand<>(ids));
return ResponseDTO.ok();
}
}

View File

@@ -1,97 +0,0 @@
package com.agileboot.admin.controller.system;
import com.agileboot.common.constant.Constants.UploadSubDir;
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.dto.UploadFileDTO;
import com.agileboot.domain.system.user.UserApplicationService;
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.domain.system.user.dto.UserProfileDTO;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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
*/
@Tag(name = "个人信息API", description = "个人信息相关接口")
@RestController
@RequestMapping("/system/user/profile")
@RequiredArgsConstructor
public class SysProfileController extends BaseController {
private final UserApplicationService userApplicationService;
/**
* 个人信息
*/
@Operation(summary = "获取个人信息")
@GetMapping
public ResponseDTO<UserProfileDTO> profile() {
SystemLoginUser user = AuthenticationUtils.getSystemLoginUser();
UserProfileDTO userProfile = userApplicationService.getUserProfile(user.getUserId());
return ResponseDTO.ok(userProfile);
}
/**
* 修改用户
*/
@Operation(summary = "修改个人信息")
@AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY)
@PutMapping
public ResponseDTO<Void> updateProfile(@RequestBody UpdateProfileCommand command) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
command.setUserId(loginUser.getUserId());
userApplicationService.updateUserProfile(command);
return ResponseDTO.ok();
}
/**
* 重置密码
*/
@Operation(summary = "重置个人密码")
@AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/password")
public ResponseDTO<Void> updatePassword(@RequestBody UpdateUserPasswordCommand command) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
command.setUserId(loginUser.getUserId());
userApplicationService.updatePasswordBySelf(loginUser, command);
return ResponseDTO.ok();
}
/**
* 头像上传
*/
@Operation(summary = "修改个人头像")
@AccessLog(title = "用户头像", businessType = BusinessTypeEnum.MODIFY)
@PostMapping("/avatar")
public ResponseDTO<UploadFileDTO> avatar(@RequestParam("avatarfile") MultipartFile file) {
if (file.isEmpty()) {
throw new ApiException(ErrorCode.Business.USER_UPLOAD_FILE_FAILED);
}
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
String avatarUrl = FileUploadUtils.upload(UploadSubDir.AVATAR_PATH, file);
userApplicationService.updateUserAvatar(new UpdateUserAvatarCommand(loginUser.getUserId(), avatarUrl));
return ResponseDTO.ok(new UploadFileDTO(avatarUrl));
}
}

View File

@@ -1,197 +0,0 @@
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.RoleApplicationService;
import com.agileboot.domain.system.role.command.AddRoleCommand;
import com.agileboot.domain.system.role.command.UpdateDataScopeCommand;
import com.agileboot.domain.system.role.command.UpdateRoleCommand;
import com.agileboot.domain.system.role.command.UpdateStatusCommand;
import com.agileboot.domain.system.role.dto.RoleDTO;
import com.agileboot.domain.system.role.query.AllocatedRoleQuery;
import com.agileboot.domain.system.role.query.RoleQuery;
import com.agileboot.domain.system.role.query.UnallocatedRoleQuery;
import com.agileboot.domain.system.user.dto.UserDTO;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
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
*/
@Tag(name = "角色API", description = "角色相关的增删查改")
@RestController
@RequestMapping("/system/role")
@Validated
@RequiredArgsConstructor
public class SysRoleController extends BaseController {
private final RoleApplicationService roleApplicationService;
@Operation(summary = "角色列表")
@PreAuthorize("@permission.has('system:role:list')")
@GetMapping("/list")
public ResponseDTO<PageDTO<RoleDTO>> list(RoleQuery query) {
PageDTO<RoleDTO> pageDTO = roleApplicationService.getRoleList(query);
return ResponseDTO.ok(pageDTO);
}
@Operation(summary = "角色列表导出")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.EXPORT)
@PreAuthorize("@permission.has('system:role:export')")
@PostMapping("/export")
public void export(HttpServletResponse response, RoleQuery query) {
PageDTO<RoleDTO> pageDTO = roleApplicationService.getRoleList(query);
CustomExcelUtil.writeToResponse(pageDTO.getRows(), RoleDTO.class, response);
}
/**
* 根据角色编号获取详细信息
*/
@Operation(summary = "角色详情")
@PreAuthorize("@permission.has('system:role:query')")
@GetMapping(value = "/{roleId}")
public ResponseDTO<RoleDTO> getInfo(@PathVariable @NotNull Long roleId) {
RoleDTO roleInfo = roleApplicationService.getRoleInfo(roleId);
return ResponseDTO.ok(roleInfo);
}
/**
* 新增角色
*/
@Operation(summary = "添加角色")
@PreAuthorize("@permission.has('system:role:add')")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@RequestBody AddRoleCommand addCommand) {
roleApplicationService.addRole(addCommand);
return ResponseDTO.ok();
}
/**
* 移除角色
*/
@Operation(summary = "删除角色")
@PreAuthorize("@permission.has('system:role:remove')")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping(value = "/{roleId}")
public ResponseDTO<Void> remove(@PathVariable("roleId") List<Long> roleIds) {
roleApplicationService.deleteRoleByBulk(roleIds);
return ResponseDTO.ok();
}
/**
* 修改保存角色
*/
@Operation(summary = "修改角色")
@PreAuthorize("@permission.has('system:role:edit')")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping
public ResponseDTO<Void> edit(@Validated @RequestBody UpdateRoleCommand updateCommand) {
roleApplicationService.updateRole(updateCommand);
return ResponseDTO.ok();
}
/**
* 修改保存数据权限
*/
@Operation(summary = "修改角色数据权限")
@PreAuthorize("@permission.has('system:role:edit')")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{roleId}/dataScope")
public ResponseDTO<Void> dataScope(@PathVariable("roleId") Long roleId,
@RequestBody UpdateDataScopeCommand command) {
command.setRoleId(roleId);
roleApplicationService.updateDataScope(command);
return ResponseDTO.ok();
}
/**
* 角色状态修改
*/
@Operation(summary = "修改角色状态")
@PreAuthorize("@permission.has('system:role:edit')")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{roleId}/status")
public ResponseDTO<Void> changeStatus(@PathVariable("roleId") Long roleId,
@RequestBody UpdateStatusCommand command) {
command.setRoleId(roleId);
roleApplicationService.updateStatus(command);
return ResponseDTO.ok();
}
/**
* 查询已分配用户角色列表
*/
@Operation(summary = "已关联该角色的用户列表")
@PreAuthorize("@permission.has('system:role:list')")
@GetMapping("/{roleId}/allocated/list")
public ResponseDTO<PageDTO<UserDTO>> allocatedUserList(@PathVariable("roleId") Long roleId,
AllocatedRoleQuery query) {
query.setRoleId(roleId);
PageDTO<UserDTO> page = roleApplicationService.getAllocatedUserList(query);
return ResponseDTO.ok(page);
}
/**
* 查询未分配用户角色列表
*/
@Operation(summary = "未关联该角色的用户列表")
@PreAuthorize("@permission.has('system:role:list')")
@GetMapping("/{roleId}/unallocated/list")
public ResponseDTO<PageDTO<UserDTO>> unallocatedUserList(@PathVariable("roleId") Long roleId,
UnallocatedRoleQuery query) {
query.setRoleId(roleId);
PageDTO<UserDTO> page = roleApplicationService.getUnallocatedUserList(query);
return ResponseDTO.ok(page);
}
/**
* 批量取消授权用户
*/
@Operation(summary = "批量解除角色和用户的关联")
@PreAuthorize("@permission.has('system:role:edit')")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.GRANT)
@DeleteMapping("/users/{userIds}/grant/bulk")
public ResponseDTO<Void> deleteRoleOfUserByBulk(@PathVariable("userIds") List<Long> userIds) {
roleApplicationService.deleteRoleOfUserByBulk(userIds);
return ResponseDTO.ok();
}
/**
* 批量选择用户授权
*/
@Operation(summary = "批量添加用户和角色关联")
@PreAuthorize("@permission.has('system:role:edit')")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.GRANT)
@PostMapping("/{roleId}/users/{userIds}/grant/bulk")
public ResponseDTO<Void> addRoleForUserByBulk(@PathVariable("roleId") Long roleId,
@PathVariable("userIds") List<Long> userIds) {
roleApplicationService.addRoleOfUserByBulk(roleId, userIds);
return ResponseDTO.ok();
}
}

View File

@@ -1,169 +0,0 @@
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.command.BulkOperationCommand;
import com.agileboot.domain.system.user.UserApplicationService;
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.domain.system.user.dto.UserDTO;
import com.agileboot.domain.system.user.dto.UserDetailDTO;
import com.agileboot.domain.system.user.query.SearchUserQuery;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.domain.system.user.db.SearchUserDO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
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 valarchie
*/
@Tag(name = "用户API", description = "用户相关的增删查改")
@RestController
@RequestMapping("/system/users")
@RequiredArgsConstructor
public class SysUserController extends BaseController {
private final UserApplicationService userApplicationService;
/**
* 获取用户列表
*/
@Operation(summary = "用户列表")
@PreAuthorize("@permission.has('system:user:list') AND @dataScope.checkDeptId(#query.deptId)")
@GetMapping
public ResponseDTO<PageDTO<UserDTO>> userList(SearchUserQuery<SearchUserDO> query) {
PageDTO<UserDTO> page = userApplicationService.getUserList(query);
return ResponseDTO.ok(page);
}
@Operation(summary = "用户列表导出")
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.EXPORT)
@PreAuthorize("@permission.has('system:user:export')")
@GetMapping("/excel")
public void exportUserByExcel(HttpServletResponse response, SearchUserQuery<SearchUserDO> query) {
PageDTO<UserDTO> userList = userApplicationService.getUserList(query);
CustomExcelUtil.writeToResponse(userList.getRows(), UserDTO.class, response);
}
@Operation(summary = "用户列表导入")
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.IMPORT)
@PreAuthorize("@permission.has('system:user:import')")
@PostMapping("/excel")
public ResponseDTO<Void> importUserByExcel(MultipartFile file) {
List<AddUserCommand> commands = CustomExcelUtil.readFromRequest(AddUserCommand.class, file);
for (AddUserCommand command : commands) {
userApplicationService.addUser(command);
}
return ResponseDTO.ok();
}
/**
* 下载批量导入模板
*/
@Operation(summary = "用户导入excel下载")
@GetMapping("/excelTemplate")
public void downloadExcelTemplate(HttpServletResponse response) {
CustomExcelUtil.writeToResponse(ListUtil.toList(new AddUserCommand()), AddUserCommand.class, response);
}
/**
* 根据用户编号获取详细信息
*/
@Operation(summary = "用户详情")
@PreAuthorize("@permission.has('system:user:query')")
@GetMapping("/{userId}")
public ResponseDTO<UserDetailDTO> getUserDetailInfo(@PathVariable(value = "userId", required = false) Long userId) {
UserDetailDTO userDetailInfo = userApplicationService.getUserDetailInfo(userId);
return ResponseDTO.ok(userDetailInfo);
}
/**
* 新增用户
*/
@Operation(summary = "新增用户")
@PreAuthorize("@permission.has('system:user:add') AND @dataScope.checkDeptId(#command.deptId)")
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@Validated @RequestBody AddUserCommand command) {
userApplicationService.addUser(command);
return ResponseDTO.ok();
}
/**
* 修改用户
*/
@Operation(summary = "修改用户")
@PreAuthorize("@permission.has('system:user:edit') AND @dataScope.checkUserId(#command.userId)")
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{userId}")
public ResponseDTO<Void> edit(@Validated @RequestBody UpdateUserCommand command) {
userApplicationService.updateUser(command);
return ResponseDTO.ok();
}
/**
* 删除用户
*/
@Operation(summary = "删除用户")
@PreAuthorize("@permission.has('system:user:remove') AND @dataScope.checkUserIds(#userIds)")
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping("/{userIds}")
public ResponseDTO<Void> remove(@PathVariable List<Long> userIds) {
BulkOperationCommand<Long> bulkDeleteCommand = new BulkOperationCommand<>(userIds);
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
userApplicationService.deleteUsers(loginUser, bulkDeleteCommand);
return ResponseDTO.ok();
}
/**
* 重置密码
*/
@Operation(summary = "重置用户密码")
@PreAuthorize("@permission.has('system:user:resetPwd') AND @dataScope.checkUserId(#userId)")
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{userId}/password")
public ResponseDTO<Void> resetPassword(@PathVariable Long userId, @RequestBody ResetPasswordCommand command) {
command.setUserId(userId);
userApplicationService.resetUserPassword(command);
return ResponseDTO.ok();
}
/**
* 状态修改
*/
@Operation(summary = "修改用户状态")
@PreAuthorize("@permission.has('system:user:edit') AND @dataScope.checkUserId(#command.userId)")
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{userId}/status")
public ResponseDTO<Void> changeStatus(@PathVariable Long userId, @RequestBody ChangeStatusCommand command) {
command.setUserId(userId);
userApplicationService.changeUserStatus(command);
return ResponseDTO.ok();
}
}

View File

@@ -1,45 +0,0 @@
package com.agileboot.admin.customize.aop.accessLog;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.common.enums.common.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 "";
/**
* 功能
*/
BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
/**
* 操作人类别
*/
OperatorTypeEnum operatorType() default OperatorTypeEnum.WEB;
/**
* 是否保存请求的参数
*/
boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
boolean isSaveResponseData() default false;
}

View File

@@ -1,59 +0,0 @@
package com.agileboot.admin.customize.aop.accessLog;
import com.agileboot.admin.customize.async.AsyncTaskFactory;
import com.agileboot.infrastructure.thread.ThreadPoolManager;
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;
/**
* 操作日志记录处理
*
* @author valarchie
*/
@Aspect
@Component
@Slf4j
public class AccessLogAspect {
/**
* 处理完请求后执行
*
* @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 accessLog, final Exception e, Object jsonResult) {
try {
OperationLogModel operationLog = new OperationLogModel();
operationLog.fillOperatorInfo();
operationLog.fillRequestInfo(joinPoint, accessLog, jsonResult);
operationLog.fillStatus(e);
operationLog.fillAccessLogInfo(accessLog);
// 保存数据库
ThreadPoolManager.execute(AsyncTaskFactory.recordOperationLog(operationLog));
} catch (Exception exp) {
log.error("写入操作日式失败", exp);
}
}
}

View File

@@ -1,159 +0,0 @@
package com.agileboot.admin.customize.aop.accessLog;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.common.enums.common.OperationStatusEnum;
import com.agileboot.common.enums.common.RequestMethodEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.log.db.SysOperationLogEntity;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Map;
/**
* @author valarchie
*/
@Slf4j
public class OperationLogModel extends SysOperationLogEntity {
public static final int MAX_DATA_LENGTH = 512;
HttpServletRequest request = ServletHolderUtil.getRequest();
public void fillOperatorInfo() {
// 获取当前的用户
String ip = ServletHolderUtil.getClientIp();
setOperatorIp(ip);
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
if (loginUser != null) {
this.setUsername(loginUser.getUsername());
}
this.setOperationTime(DateUtil.date());
}
public void fillRequestInfo(final JoinPoint joinPoint, AccessLog accessLog, Object jsonResult) {
this.setRequestUrl(request.getRequestURI());
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
String methodFormat = StrUtil.format("{}.{}()", className, methodName);
this.setCalledMethod(methodFormat);
// 设置请求方式
RequestMethodEnum requestMethodEnum = EnumUtil.fromString(RequestMethodEnum.class,
request.getMethod());
this.setRequestMethod(requestMethodEnum != null ? requestMethodEnum.getValue() : RequestMethodEnum.UNKNOWN.getValue());
// 是否需要保存request参数和值
if (accessLog.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
recordRequestData(joinPoint);
}
// 是否需要保存response参数和值
if (accessLog.isSaveResponseData() && jsonResult != null) {
this.setOperationResult(StrUtil.sub(JSONUtil.toJsonStr(jsonResult), 0, MAX_DATA_LENGTH));
}
}
public void fillAccessLogInfo(AccessLog log) {
// 设置action动作
this.setBusinessType(log.businessType().ordinal());
// 设置标题
this.setRequestModule(log.title());
// 设置操作人类别
this.setOperatorType(log.operatorType().ordinal());
}
public void fillStatus(Exception e) {
if (e != null) {
this.setStatus(OperationStatusEnum.FAIL.getValue());
this.setErrorStack(StrUtil.sub(e.getMessage(), 0, MAX_DATA_LENGTH));
} else {
this.setStatus(OperationStatusEnum.SUCCESS.getValue());
}
}
/**
* 获取请求的参数放到log中
*
* @param joinPoint 方法切面
*/
private void recordRequestData(JoinPoint joinPoint) {
RequestMethodEnum requestMethodEnum = BasicEnumUtil.fromValue(RequestMethodEnum.class,
this.getRequestMethod());
if (requestMethodEnum == RequestMethodEnum.GET || requestMethodEnum == RequestMethodEnum.POST) {
String params = argsArrayToString(joinPoint.getArgs());
this.setOperationParam(StrUtil.sub(params, 0, MAX_DATA_LENGTH));
} else {
Map<?, ?> paramsMap = (Map<?, ?>) request
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
this.setOperationParam(StrUtil.sub(paramsMap.toString(), 0, MAX_DATA_LENGTH));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
StringBuilder params = new StringBuilder();
if (paramsArray != null) {
for (Object o : paramsArray) {
if (o != null && !isCanNotBeParseToJson(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 isCanNotBeParseToJson(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;
}
}

View File

@@ -1,77 +0,0 @@
package com.agileboot.admin.customize.async;
import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.common.utils.ip.IpRegionUtil;
import com.agileboot.common.enums.common.LoginStatusEnum;
import com.agileboot.domain.system.log.db.SysLoginInfoEntity;
import com.agileboot.domain.system.log.db.SysOperationLogEntity;
import com.agileboot.domain.system.log.db.SysLoginInfoService;
import com.agileboot.domain.system.log.db.SysOperationLogService;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
/**
* 异步工厂(产生任务用)
*
* @author ruoyi
*/
@Slf4j
public class AsyncTaskFactory {
private AsyncTaskFactory() {
}
/**
* 记录登录信息
*
* @param username 用户名
* @param loginStatusEnum 状态
* @param message 消息
* @return 任务task
*/
public static Runnable loginInfoTask(final String username, final LoginStatusEnum loginStatusEnum, final String message) {
// 优化一下这个类
final UserAgent userAgent = UserAgent.parseUserAgentString(
ServletHolderUtil.getRequest().getHeader("User-Agent"));
// 获取客户端浏览器
final String browser = userAgent.getBrowser() != null ? userAgent.getBrowser().getName() : "";
final String ip = ServletHolderUtil.getClientIp();
final String address = IpRegionUtil.getBriefLocationByIp(ip);
// 获取客户端操作系统
final String os = userAgent.getOperatingSystem() != null ? userAgent.getOperatingSystem().getName() : "";
log.info("ip: {}, address: {}, username: {}, loginStatusEnum: {}, message: {}", ip, address, username,
loginStatusEnum, message);
return () -> {
// 封装对象
SysLoginInfoEntity loginInfo = new SysLoginInfoEntity();
loginInfo.setUsername(username);
loginInfo.setIpAddress(ip);
loginInfo.setLoginLocation(address);
loginInfo.setBrowser(browser);
loginInfo.setOperationSystem(os);
loginInfo.setMsg(message);
loginInfo.setLoginTime(DateUtil.date());
loginInfo.setStatus(loginStatusEnum.getValue());
// 插入数据
SpringUtil.getBean(SysLoginInfoService.class).save(loginInfo);
};
}
/**
* 操作日志记录
*
* @param operationLog 操作日志信息
* @return 任务task
*/
public static Runnable recordOperationLog(final SysOperationLogEntity operationLog) {
return () -> {
// 远程查询操作地点
operationLog.setOperatorLocation(IpRegionUtil.getBriefLocationByIp(operationLog.getOperatorIp()));
SpringUtil.getBean(SysOperationLogService.class).save(operationLog);
};
}
}

View File

@@ -1,53 +0,0 @@
package com.agileboot.admin.customize.config;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.login.TokenService;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* token过滤器 验证token有效性
* 继承OncePerRequestFilter类的话 可以确保只执行filter一次 避免执行多次
* @author valarchie
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private final TokenService tokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
SystemLoginUser loginUser = tokenService.getLoginUser(request);
if (loginUser != null && AuthenticationUtils.getAuthentication() == null) {
tokenService.refreshToken(loginUser);
// 如果没有将当前登录用户放入到上下文中的话,会认定用户未授权,返回用户未登陆的错误
putCurrentLoginUserIntoContext(request, loginUser);
log.debug("request process in jwt token filter. get login user id: {}", loginUser.getUserId());
}
chain.doFilter(request, response);
}
private void putCurrentLoginUserIntoContext(HttpServletRequest request, SystemLoginUser loginUser) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginUser,
null, loginUser.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}

View File

@@ -1,164 +0,0 @@
package com.agileboot.admin.customize.config;
import cn.hutool.json.JSONUtil;
import com.agileboot.admin.customize.service.login.LoginService;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Client;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.domain.common.cache.RedisCacheService;
import com.agileboot.admin.customize.async.AsyncTaskFactory;
import com.agileboot.infrastructure.thread.ThreadPoolManager;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.login.TokenService;
import com.agileboot.common.enums.common.LoginStatusEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.filter.CorsFilter;
/**
* 主要配置登录流程逻辑涉及以下几个类
* @see UserDetailsServiceImpl#loadUserByUsername 用于登录流程通过用户名加载用户
* @see this#unauthorizedHandler() 用于用户未授权或登录失败处理
* @see this#logOutSuccessHandler 用于退出登录成功后的逻辑
* @see JwtAuthenticationTokenFilter#doFilter token的校验和刷新
* @see LoginService#login 登录逻辑
* @author valarchie
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
private final TokenService tokenService;
private final RedisCacheService redisCache;
/**
* token认证过滤器
*/
private final JwtAuthenticationTokenFilter jwtTokenFilter;
private final UserDetailsService userDetailsService;
/**
* 跨域过滤器
*/
private final CorsFilter corsFilter;
/**
* 登录异常处理类
* 用户未登陆的话 在这个Bean中处理
*/
@Bean
public AuthenticationEntryPoint unauthorizedHandler() {
return (request, response, exception) -> {
ResponseDTO<Object> responseDTO = ResponseDTO.fail(
new ApiException(Client.COMMON_NO_AUTHORIZATION, request.getRequestURI())
);
ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(responseDTO));
};
}
/**
* 退出成功处理类 返回成功
* 在SecurityConfig类当中 定义了/logout 路径对应处理逻辑
*/
@Bean
public LogoutSuccessHandler logOutSuccessHandler() {
return (request, response, authentication) -> {
SystemLoginUser loginUser = tokenService.getLoginUser(request);
if (loginUser != null) {
String userName = loginUser.getUsername();
// 删除用户缓存记录
redisCache.loginUserCache.delete(loginUser.getCachedKey());
// 记录用户退出日志
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(
userName, LoginStatusEnum.LOGOUT, LoginStatusEnum.LOGOUT.description()));
}
ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(ResponseDTO.ok()));
};
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 鉴权管理类
* @see UserDetailsServiceImpl#loadUserByUsername
*/
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder())
.and()
.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// CSRF禁用因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler()).and()
// 基于token所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 以及公共Api的请求允许匿名访问
// 注意: 当携带token请求以下这几个接口时 会返回403的错误
.antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**").anonymous()
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
"/profile/**").permitAll()
// TODO this is danger.
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs","/*/api-docs/swagger-config").anonymous()
.antMatchers("/**/api-docs.yaml" ).anonymous()
.antMatchers("/druid/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
// 禁用 X-Frame-Options 响应头。下面是具体解释:
// X-Frame-Options 是一个 HTTP 响应头,用于防止网页被嵌入到其他网页的 <frame>、<iframe> 或 <object> 标签中,从而可以减少点击劫持攻击的风险
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logOutSuccessHandler());
// 添加JWT filter 需要一开始就通过token识别出登录用户 并放到上下文中 所以jwtFilter需要放前面
httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
return httpSecurity.build();
}
}

View File

@@ -1,222 +0,0 @@
package com.agileboot.admin.customize.service.login;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.common.config.AgileBootConfig;
import com.agileboot.common.constant.Constants.Captcha;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.common.utils.i18n.MessageUtils;
import com.agileboot.domain.common.cache.GuavaCacheService;
import com.agileboot.domain.common.cache.MapCache;
import com.agileboot.domain.common.cache.RedisCacheService;
import com.agileboot.admin.customize.async.AsyncTaskFactory;
import com.agileboot.infrastructure.thread.ThreadPoolManager;
import com.agileboot.admin.customize.service.login.dto.CaptchaDTO;
import com.agileboot.admin.customize.service.login.dto.ConfigDTO;
import com.agileboot.admin.customize.service.login.command.LoginCommand;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.common.enums.common.ConfigKeyEnum;
import com.agileboot.common.enums.common.LoginStatusEnum;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.google.code.kaptcha.Producer;
import java.awt.image.BufferedImage;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
/**
* 登录校验方法
*
* @author ruoyi
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class LoginService {
private final TokenService tokenService;
private final RedisCacheService redisCache;
private final GuavaCacheService guavaCache;
private final AuthenticationManager authenticationManager;
@Resource(name = "captchaProducer")
private Producer captchaProducer;
@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath;
/**
* 登录验证
*
* @param loginCommand 登录参数
* @return 结果
*/
public String login(LoginCommand loginCommand) {
// 验证码开关
if (isCaptchaOn()) {
validateCaptcha(loginCommand.getUsername(), loginCommand.getCaptchaCode(), loginCommand.getCaptchaCodeKey());
}
// 用户验证
Authentication authentication;
String decryptPassword = decryptPassword(loginCommand.getPassword());
try {
// 该方法会去调用UserDetailsServiceImpl#loadUserByUsername 校验用户名和密码 认证鉴权
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
loginCommand.getUsername(), decryptPassword));
} catch (BadCredentialsException e) {
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL,
MessageUtils.message("Business.LOGIN_WRONG_USER_PASSWORD")));
throw new ApiException(e, ErrorCode.Business.LOGIN_WRONG_USER_PASSWORD);
} catch (AuthenticationException e) {
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage()));
throw new ApiException(e, ErrorCode.Business.LOGIN_ERROR, e.getMessage());
} catch (Exception e) {
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage()));
throw new ApiException(e, Business.LOGIN_ERROR, e.getMessage());
}
// 把当前登录用户 放入上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);
// 这里获取的loginUser是UserDetailsServiceImpl#loadUserByUsername方法返回的LoginUser
SystemLoginUser loginUser = (SystemLoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser);
// 生成token
return tokenService.createTokenAndPutUserInCache(loginUser);
}
/**
* 获取验证码 data
*
* @return {@link ConfigDTO}
*/
public ConfigDTO getConfig() {
ConfigDTO configDTO = new ConfigDTO();
boolean isCaptchaOn = isCaptchaOn();
configDTO.setIsCaptchaOn(isCaptchaOn);
configDTO.setDictionary(MapCache.dictionaryCache());
return configDTO;
}
/**
* 获取验证码 data
*
* @return 验证码
*/
public CaptchaDTO generateCaptchaImg() {
CaptchaDTO captchaDTO = new CaptchaDTO();
boolean isCaptchaOn = isCaptchaOn();
captchaDTO.setIsCaptchaOn(isCaptchaOn);
if (isCaptchaOn) {
String expression;
String answer = null;
BufferedImage image = null;
// 生成验证码
String captchaType = AgileBootConfig.getCaptchaType();
if (Captcha.MATH_TYPE.equals(captchaType)) {
String capText = captchaProducerMath.createText();
String[] expressionAndAnswer = capText.split("@");
expression = expressionAndAnswer[0];
answer = expressionAndAnswer[1];
image = captchaProducerMath.createImage(expression);
}
if (Captcha.CHAR_TYPE.equals(captchaType)) {
expression = answer = captchaProducer.createText();
image = captchaProducer.createImage(expression);
}
if (image == null) {
throw new ApiException(ErrorCode.Internal.LOGIN_CAPTCHA_GENERATE_FAIL);
}
// 保存验证码信息
String imgKey = IdUtil.simpleUUID();
redisCache.captchaCache.set(imgKey, answer);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
ImgUtil.writeJpg(image, os);
captchaDTO.setCaptchaCodeKey(imgKey);
captchaDTO.setCaptchaCodeImg(Base64.encode(os.toByteArray()));
}
return captchaDTO;
}
/**
* 校验验证码
*
* @param username 用户名
* @param captchaCode 验证码
* @param captchaCodeKey 验证码对应的缓存key
*/
public void validateCaptcha(String username, String captchaCode, String captchaCodeKey) {
String captcha = redisCache.captchaCache.getObjectById(captchaCodeKey);
redisCache.captchaCache.delete(captchaCodeKey);
if (captcha == null) {
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL,
ErrorCode.Business.LOGIN_CAPTCHA_CODE_EXPIRE.message()));
throw new ApiException(ErrorCode.Business.LOGIN_CAPTCHA_CODE_EXPIRE);
}
if (!captchaCode.equalsIgnoreCase(captcha)) {
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL,
ErrorCode.Business.LOGIN_CAPTCHA_CODE_WRONG.message()));
throw new ApiException(ErrorCode.Business.LOGIN_CAPTCHA_CODE_WRONG);
}
}
/**
* 记录登录信息
* @param loginUser 登录用户
*/
public void recordLoginInfo(SystemLoginUser loginUser) {
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginUser.getUsername(), LoginStatusEnum.LOGIN_SUCCESS,
LoginStatusEnum.LOGIN_SUCCESS.description()));
SysUserEntity entity = redisCache.userCache.getObjectById(loginUser.getUserId());
entity.setLoginIp(ServletHolderUtil.getClientIp());
entity.setLoginDate(DateUtil.date());
entity.updateById();
}
public String decryptPassword(String originalPassword) {
byte[] decryptBytes = SecureUtil.rsa(AgileBootConfig.getRsaPrivateKey(), null)
.decrypt(Base64.decode(originalPassword), KeyType.PrivateKey);
return StrUtil.str(decryptBytes, CharsetUtil.CHARSET_UTF_8);
}
private boolean isCaptchaOn() {
return Convert.toBool(guavaCache.configCache.get(ConfigKeyEnum.CAPTCHA.getValue()));
}
}

View File

@@ -1,163 +0,0 @@
package com.agileboot.admin.customize.service.login;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.constant.Constants.Token;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.common.cache.RedisCacheService;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* token验证处理
*
* @author valarchie
*/
@Component
@Slf4j
@Data
@RequiredArgsConstructor
public class TokenService {
/**
* 自定义令牌标识
*/
@Value("${token.header}")
private String header;
/**
* 令牌秘钥
*/
@Value("${token.secret}")
private String secret;
/**
* 自动刷新token的时间当过期时间不足autoRefreshTime的值的时候会触发刷新用户登录缓存的时间
* 比如这个值是20, 用户是8点登录的 8点半缓存会过期 当过8.10分的时候就少于20分钟了便触发
* 刷新登录用户的缓存时间
*/
@Value("${token.autoRefreshTime}")
private long autoRefreshTime;
private final RedisCacheService redisCache;
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public SystemLoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getTokenFromRequest(request);
if (StrUtil.isNotEmpty(token)) {
try {
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Token.LOGIN_USER_KEY);
return redisCache.loginUserCache.getObjectOnlyInCacheById(uuid);
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException jwtException) {
log.error("parse token failed.", jwtException);
throw new ApiException(jwtException, ErrorCode.Client.INVALID_TOKEN);
} catch (Exception e) {
log.error("fail to get cached user from redis", e);
throw new ApiException(e, ErrorCode.Client.TOKEN_PROCESS_FAILED, e.getMessage());
}
}
return null;
}
/**
* 创建令牌
*
* @param loginUser 用户信息
* @return 令牌
*/
public String createTokenAndPutUserInCache(SystemLoginUser loginUser) {
loginUser.setCachedKey(IdUtil.fastUUID());
redisCache.loginUserCache.set(loginUser.getCachedKey(), loginUser);
return generateToken(MapUtil.of(Token.LOGIN_USER_KEY, loginUser.getCachedKey()));
}
/**
* 当超过20分钟自动刷新token
* @param loginUser 登录用户
*/
public void refreshToken(SystemLoginUser loginUser) {
long currentTime = System.currentTimeMillis();
if (currentTime > loginUser.getAutoRefreshCacheTime()) {
loginUser.setAutoRefreshCacheTime(currentTime + TimeUnit.MINUTES.toMillis(autoRefreshTime));
// 根据uuid将loginUser存入缓存
redisCache.loginUserCache.set(loginUser.getCachedKey(), loginUser);
}
}
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
private String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.getSubject();
}
/**
* 获取请求token
*
* @return token
*/
private String getTokenFromRequest(HttpServletRequest request) {
String token = request.getHeader(header);
if (StrUtil.isNotEmpty(token) && token.startsWith(Token.PREFIX)) {
token = StrUtil.stripIgnoreCase(token, Token.PREFIX, null);
}
return token;
}
}

View File

@@ -1,117 +0,0 @@
package com.agileboot.admin.customize.service.login;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import java.util.Collections;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.infrastructure.user.web.RoleInfo;
import com.agileboot.infrastructure.user.web.DataScopeEnum;
import com.agileboot.common.enums.common.UserStatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.menu.db.SysMenuEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.menu.db.SysMenuService;
import com.agileboot.domain.system.role.db.SysRoleService;
import com.agileboot.domain.system.user.db.SysUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* 自定义加载用户信息通过用户名
* 用于SpringSecurity 登录流程
* 没有办法把这个类 放进loginService中 会在SecurityConfig中造成循环依赖
* @see com.agileboot.infrastructure.config.SecurityConfig#filterChain(HttpSecurity)
* @author valarchie
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final SysUserService userService;
private final SysMenuService menuService;
private final SysRoleService roleService;
private final TokenService tokenService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUserEntity userEntity = userService.getUserByUserName(username);
if (userEntity == null) {
log.info("登录用户:{} 不存在.", username);
throw new ApiException(ErrorCode.Business.USER_NON_EXIST, username);
}
if (!Objects.equals(UserStatusEnum.NORMAL.getValue(), userEntity.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new ApiException(ErrorCode.Business.USER_IS_DISABLE, username);
}
RoleInfo roleInfo = getRoleInfo(userEntity.getRoleId(), userEntity.getIsAdmin());
SystemLoginUser loginUser = new SystemLoginUser(userEntity.getUserId(), userEntity.getIsAdmin(), userEntity.getUsername(),
userEntity.getPassword(), roleInfo, userEntity.getDeptId());
loginUser.fillLoginInfo();
loginUser.setAutoRefreshCacheTime(loginUser.getLoginInfo().getLoginTime()
+ TimeUnit.MINUTES.toMillis(tokenService.getAutoRefreshTime()));
return loginUser;
}
public RoleInfo getRoleInfo(Long roleId, boolean isAdmin) {
if (roleId == null) {
return RoleInfo.EMPTY_ROLE;
}
if (isAdmin) {
LambdaQueryWrapper<SysMenuEntity> menuQuery = Wrappers.lambdaQuery();
menuQuery.select(SysMenuEntity::getMenuId);
List<SysMenuEntity> allMenus = menuService.list(menuQuery);
Set<Long> allMenuIds = allMenus.stream().map(SysMenuEntity::getMenuId).collect(Collectors.toSet());
return new RoleInfo(RoleInfo.ADMIN_ROLE_ID, RoleInfo.ADMIN_ROLE_KEY, DataScopeEnum.ALL, Collections.emptySet(),
RoleInfo.ADMIN_PERMISSIONS, allMenuIds);
}
SysRoleEntity roleEntity = roleService.getById(roleId);
if (roleEntity == null) {
return RoleInfo.EMPTY_ROLE;
}
List<SysMenuEntity> menuList = roleService.getMenuListByRoleId(roleId);
Set<Long> menuIds = menuList.stream().map(SysMenuEntity::getMenuId).collect(Collectors.toSet());
Set<String> permissions = menuList.stream().map(SysMenuEntity::getPermission).collect(Collectors.toSet());
DataScopeEnum dataScopeEnum = BasicEnumUtil.fromValue(DataScopeEnum.class, roleEntity.getDataScope());
Set<Long> deptIdSet = Collections.emptySet();
if (StrUtil.isNotEmpty(roleEntity.getDeptIdSet())) {
deptIdSet = StrUtil.split(roleEntity.getDeptIdSet(), ",").stream()
.map(Convert::toLong).collect(Collectors.toSet());
}
return new RoleInfo(roleId, roleEntity.getRoleKey(), dataScopeEnum, deptIdSet, permissions, menuIds);
}
}

View File

@@ -1,33 +0,0 @@
package com.agileboot.admin.customize.service.login.command;
import lombok.Data;
/**
* 用户登录对象
*
* @author valarchie
*/
@Data
public class LoginCommand {
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
/**
* 验证码
*/
private String captchaCode;
/**
* 唯一标识
*/
private String captchaCodeKey;
}

View File

@@ -1,15 +0,0 @@
package com.agileboot.admin.customize.service.login.dto;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class CaptchaDTO {
private Boolean isCaptchaOn;
private String captchaCodeKey;
private String captchaCodeImg;
}

View File

@@ -1,18 +0,0 @@
package com.agileboot.admin.customize.service.login.dto;
import com.agileboot.common.enums.dictionary.DictionaryData;
import java.util.List;
import java.util.Map;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class ConfigDTO {
private Boolean isCaptchaOn;
private Map<String, List<DictionaryData>> dictionary;
}

View File

@@ -1,66 +0,0 @@
package com.agileboot.admin.customize.service.permission;
import cn.hutool.extra.spring.SpringUtil;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.checker.AllDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.CustomDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.DefaultDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.DeptTreeDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.OnlySelfDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.SingleDeptDataPermissionChecker;
import com.agileboot.infrastructure.user.web.DataScopeEnum;
import com.agileboot.domain.system.dept.db.SysDeptService;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
/**
* 数据权限检测器工厂
* @author valarchie
*/
@Component
public class DataPermissionCheckerFactory {
private static AbstractDataPermissionChecker allChecker;
private static AbstractDataPermissionChecker customChecker;
private static AbstractDataPermissionChecker singleDeptChecker;
private static AbstractDataPermissionChecker deptTreeChecker;
private static AbstractDataPermissionChecker onlySelfChecker;
private static AbstractDataPermissionChecker defaultSelfChecker;
@PostConstruct
public void initAllChecker() {
SysDeptService deptService = SpringUtil.getBean(SysDeptService.class);
allChecker = new AllDataPermissionChecker();
customChecker = new CustomDataPermissionChecker(deptService);
singleDeptChecker = new SingleDeptDataPermissionChecker(deptService);
deptTreeChecker = new DeptTreeDataPermissionChecker(deptService);
onlySelfChecker = new OnlySelfDataPermissionChecker(deptService);
defaultSelfChecker = new DefaultDataPermissionChecker();
}
public static AbstractDataPermissionChecker getChecker(SystemLoginUser loginUser) {
if (loginUser == null) {
return deptTreeChecker;
}
DataScopeEnum dataScope = loginUser.getRoleInfo().getDataScope();
switch (dataScope) {
case ALL:
return allChecker;
case CUSTOM_DEFINE:
return customChecker;
case SINGLE_DEPT:
return singleDeptChecker;
case DEPT_TREE:
return deptTreeChecker;
case ONLY_SELF:
return onlySelfChecker;
default:
return defaultSelfChecker;
}
}
}

View File

@@ -1,70 +0,0 @@
package com.agileboot.admin.customize.service.permission;
import cn.hutool.core.collection.CollUtil;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SysUserService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 数据权限校验服务
* @author valarchie
*/
@Service("dataScope")
@RequiredArgsConstructor
public class DataPermissionService {
private final SysUserService userService;
/**
* 通过userId 校验当前用户 对 目标用户是否有操作权限
*
* @param userId 用户id
* @return 检验结果
*/
public boolean checkUserId(Long userId) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
SysUserEntity targetUser = userService.getById(userId);
if (targetUser == null) {
return true;
}
return checkDataScope(loginUser, targetUser.getDeptId(), userId);
}
/**
* 通过userId 校验当前用户 对 目标用户是否有操作权限
* @param userIds 用户id列表
* @return 校验结果
*/
public boolean checkUserIds(List<Long> userIds) {
if (CollUtil.isNotEmpty(userIds)) {
for (Long userId : userIds) {
boolean checkResult = checkUserId(userId);
if (!checkResult) {
return false;
}
}
}
return true;
}
public boolean checkDeptId(Long deptId) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
return checkDataScope(loginUser, deptId, null);
}
public boolean checkDataScope(SystemLoginUser loginUser, Long targetDeptId, Long targetUserId) {
DataCondition dataCondition = DataCondition.builder().targetDeptId(targetDeptId).targetUserId(targetUserId).build();
AbstractDataPermissionChecker checker = DataPermissionCheckerFactory.getChecker(loginUser);
return checker.check(loginUser, dataCondition);
}
}

View File

@@ -1,48 +0,0 @@
package com.agileboot.admin.customize.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.infrastructure.user.web.RoleInfo;
import java.util.Set;
import org.springframework.stereotype.Service;
/**
*
* @author valarchie
*/
@Service("permission")
public class MenuPermissionService {
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean has(String permission) {
if (StrUtil.isEmpty(permission)) {
return false;
}
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
if (loginUser == null || CollUtil.isEmpty(loginUser.getRoleInfo().getMenuPermissions())) {
return false;
}
return has(loginUser.getRoleInfo().getMenuPermissions(), permission);
}
/**
* 判断是否包含权限
*
* @param permissions 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean has(Set<String> permissions, String permission) {
return permissions.contains(RoleInfo.ALL_PERMISSIONS) || permissions.contains(StrUtil.trim(permission));
}
}

View File

@@ -1,25 +0,0 @@
package com.agileboot.admin.customize.service.permission.model;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.Data;
/**
* 数据权限测试接口
* @author valarchie
*/
@Data
public abstract class AbstractDataPermissionChecker {
private SysDeptService deptService;
/**
* 检测当前用户对于 给定条件的数据 是否有权限
*
* @param loginUser 登录用户
* @param condition 条件
* @return 校验结果
*/
public abstract boolean check(SystemLoginUser loginUser, DataCondition condition);
}

View File

@@ -1,21 +0,0 @@
package com.agileboot.admin.customize.service.permission.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
* 供 DataPermissionChecker使用的 数据条件
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DataCondition {
private Long targetDeptId;
private Long targetUserId;
}

View File

@@ -1,25 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AllDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
return true;
}
}

View File

@@ -1,42 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import cn.hutool.core.collection.CollUtil;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CustomDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
if (condition == null || loginUser == null) {
return false;
}
if (loginUser.getRoleInfo() == null) {
return false;
}
Set<Long> deptIdSet = loginUser.getRoleInfo().getDeptIdSet();
Long targetDeptId = condition.getTargetDeptId();
return condition.getTargetDeptId() != null && CollUtil.safeContains(deptIdSet, targetDeptId);
}
}

View File

@@ -1,25 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class DefaultDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
return false;
}
}

View File

@@ -1,44 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeptTreeDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
if (condition == null || loginUser == null) {
return false;
}
if (loginUser.getDeptId() == null || condition.getTargetDeptId() == null) {
return false;
}
Long currentDeptId = loginUser.getDeptId();
Long targetDeptId = condition.getTargetDeptId();
boolean isContainsTargetDept = deptService.isChildOfTheDept(loginUser.getDeptId(), targetDeptId);
boolean isSameDept = Objects.equals(currentDeptId, targetDeptId);
return isContainsTargetDept || isSameDept;
}
}

View File

@@ -1,40 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlySelfDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
if (condition == null || loginUser == null) {
return false;
}
if (loginUser.getUserId() == null || condition.getTargetUserId() == null) {
return false;
}
Long currentUserId = loginUser.getUserId();
Long targetUserId = condition.getTargetUserId();
return Objects.equals(currentUserId, targetUserId);
}
}

View File

@@ -1,42 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SingleDeptDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
if (condition == null || loginUser == null) {
return false;
}
if (loginUser.getDeptId() == null || condition.getTargetDeptId() == null) {
return false;
}
Long currentDeptId = loginUser.getDeptId();
Long targetDeptId = condition.getTargetDeptId();
return Objects.equals(currentDeptId, targetDeptId);
}
}

View File

@@ -1,104 +0,0 @@
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: agileboot
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic:
primary: master
strict: false
druid:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
datasource:
# 主库数据源
master:
url: jdbc:mysql://mysql2.sqlpub.com:3307/agileboot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&sslMode=REQUIRED
username: ENC(s4kjpEsplGGLeV3YRNvJpJhDSOAO0tEf)
password: ENC(hg/hxmducWsI8u83/eXgAi8yHBDFbB5z0xzwNtBejPc=)
# 从库数据源
# slave:
# url: jdbc:mysql://localhost:33067/agileboot2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# username: root
# password: 12345
# redis 配置
redis:
host: redis
port: 6379
database: 1
password: ENC(s3HU866TUAjzrWStN7kpQQ==)
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
logging:
file:
path: ./logs/agileboot-dev
springdoc:
swagger-ui:
# ***注意*** 开启Swagger UI界面 **安全考虑的话生产环境需要关掉**
# 因为knife4j的一些配置不灵活 所以重新改回springdoc+swagger的组合 真实开发的时候 使用apifox这种工具效率更高
enabled: true
url: ${agileboot.api-prefix}/v3/api-docs
config-url: ${agileboot.api-prefix}/v3/api-docs/swagger-config
# 项目相关配置
agileboot:
# 文件基路径 示例( Windows配置D:\agilebootLinux配置 /home/agileboot
file-base-dir: D:\agileboot
# 前端url请求转发前缀
api-prefix: /dev-api
demo-enabled: false
jasypt:
encryptor:
password: ${JASYPT_ENCRYPTOR_PASSWORD:}

View File

@@ -1,53 +0,0 @@
# 数据源配置
spring:
datasource:
# 驱动
driver-class-name: org.h2.Driver
dynamic:
primary: master
strict: false
datasource:
master:
# h2 内存数据库 内存模式连接配置 库名: agileboot
url: jdbc:h2:mem:agileboot;DB_CLOSE_DELAY=-1;MODE=MySQL
h2:
# 开启console 访问 默认false
console:
enabled: true
settings:
# 开启h2 console 跟踪 方便调试 默认 false
trace: true
# 允许console 远程访问 默认false
web-allow-others: true
# h2 访问路径上下文
path: /h2-console
sql:
init:
platform: mysql
# 初始化数据
schema-locations: classpath:h2sql/agileboot_schema.sql
data-locations: classpath:h2sql/agileboot_data.sql
# redis 配置
redis:
# 地址
host: localhost
# 端口默认为6379
port: 36379
# 数据库索引
database: 0
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms

View File

@@ -1,46 +0,0 @@
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 18080
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# Spring配置 如果需要无Mysql 无Redis直接启动的话 dev改为test
# 生产环境把dev改为prod
spring:
profiles:
active: basic,dev
# 如果需要无Mysql 无Redis直接启动的话 可以将这两个参数置为true, 并且spring.profile.active: dev换成test
# redis的端口可能会被占用如果被占用请自己修改一下端口号
agileboot:
embedded:
mysql: false
redis: false
springdoc:
api-docs:
enabled: true
groups:
enabled: true
group-configs:
- group: '公共API'
packages-to-scan: com.agileboot.admin.controller.common
- group: '内置系统API'
packages-to-scan: com.agileboot.admin.controller.system

View File

@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>agileboot</artifactId>
<groupId>com.agileboot</groupId>
<version>1.0.0</version>
</parent>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
<artifactId>agileboot-api</artifactId>
<description>
外部API
</description>
<dependencies>
<!-- 核心模块-->
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-infrastructure</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用undertow依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- 业务领域 -->
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-domain</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,32 +0,0 @@
package com.agileboot.api;
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的网站
* <a href="http://patorjk.com/software/taag">http://patorjk.com/software/taag</a>
* <a href="http://www.network-science.de/ascii/">http://www.network-science.de/ascii/</a>
* <a href="http://www.degraeve.com/img2txt.php">http://www.degraeve.com/img2txt.php</a>
* <a href="http://life.chacuo.net/convertfont2char">http://life.chacuo.net/convertfont2char</a>
*
* @author valarchie
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@ComponentScan(basePackages = "com.agileboot.*")
public class AgileBooApiApplication {
public static void main(String[] args) {
SpringApplication.run(AgileBooApiApplication.class, args);
String successMsg = " ____ _ _ __ _ _ \n"
+ " / ___| | |_ __ _ _ __ | |_ _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / _| _ _ | || |\n"
+ " \\___ \\ | __|/ _` || '__|| __| | | | || '_ \\ / __|| | | | / __|/ __|/ _ \\/ __|/ __|| |_ | | | || || |\n"
+ " ___) || |_| (_| || | | |_ | |_| || |_) | \\__ \\| |_| || (__| (__| __/\\__ \\\\__ \\| _|| |_| || ||_|\n"
+ " |____/ \\__|\\__,_||_| \\__| \\__,_|| .__/ |___/ \\__,_| \\___|\\___|\\___||___/|___/|_| \\__,_||_|(_)\n"
+ " |_| ";
System.out.println(successMsg);
}
}

View File

@@ -1,25 +0,0 @@
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 valarchie
*/
@RestController
@RequestMapping("/api/order")
public class OrderController extends BaseController {
/**
* 访问首页,提示语
*/
@RequestMapping("/")
public String index() {
return "暂无订单";
}
}

View File

@@ -1,39 +0,0 @@
package com.agileboot.api.controller.app;
import com.agileboot.api.customize.service.JwtTokenService;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import lombok.AllArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 调度日志操作处理
*
* @author ruoyi
*/
@RestController
@RequestMapping("/app")
@AllArgsConstructor
public class AppController extends BaseController {
private final JwtTokenService jwtTokenService;
/**
* 访问首页,提示语
*/
@PreAuthorize("hasAuthority('annie')")
@GetMapping("/list")
public ResponseDTO<?> appLogin() {
return ResponseDTO.ok();
}
}

View File

@@ -1,38 +0,0 @@
package com.agileboot.api.controller.common;
import cn.hutool.core.map.MapUtil;
import com.agileboot.api.customize.service.JwtTokenService;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import java.util.Map;
import lombok.AllArgsConstructor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 调度日志操作处理
*
* @author ruoyi
*/
@RestController
@RequestMapping("/common")
@AllArgsConstructor
public class LoginController extends BaseController {
private final JwtTokenService jwtTokenService;
/**
* 访问首页,提示语
*/
@PostMapping("/app/{appId}/login")
public ResponseDTO<String> appLogin() {
String token = jwtTokenService.generateToken(MapUtil.of("token", "user1"));
return ResponseDTO.ok(token);
}
}

View File

@@ -1,52 +0,0 @@
package com.agileboot.api.customize.config;
import com.agileboot.api.customize.service.JwtTokenService;
import com.agileboot.infrastructure.user.app.AppLoginUser;
import io.jsonwebtoken.Claims;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* token过滤器 验证token有效性
* 继承OncePerRequestFilter类的话 可以确保只执行filter一次 避免执行多次
* @author valarchie
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenService jwtTokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String tokenFromRequest = jwtTokenService.getTokenFromRequest(request);
if (tokenFromRequest != null) {
Claims claims = jwtTokenService.parseToken(tokenFromRequest);
String token = (String) claims.get("token");
// 根据token去查缓存里面 有没有对应的App用户
// 没有的话 再去数据库中查询
if (token != null && token.equals("user1")) {
AppLoginUser loginUser = new AppLoginUser(23232323L, false, "dasdsadsds");
loginUser.grantAppPermission("annie");
UsernamePasswordAuthenticationToken suer1 = new UsernamePasswordAuthenticationToken(loginUser, null,
loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(suer1);
}
}
filterChain.doFilter(request, response);
}
}

View File

@@ -1,85 +0,0 @@
package com.agileboot.api.customize.config;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Client;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.common.utils.jackson.JacksonUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
/**
* 主要配置登录流程逻辑涉及以下几个类
* @author valarchie
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
/**
* token认证过滤器
*/
private final JwtAuthenticationFilter jwtTokenFilter;
/**
* 跨域过滤器
*/
private final CorsFilter corsFilter;
/**
* 登录异常处理类
* 用户未登陆的话 在这个Bean中处理
*/
@Bean
public AuthenticationEntryPoint customAuthenticationEntryPoint() {
return (request, response, exception) -> {
ResponseDTO<Void> responseDTO = ResponseDTO.fail(
new ApiException(Client.COMMON_NO_AUTHORIZATION, request.getRequestURI())
);
ServletHolderUtil.renderString(response, JacksonUtil.to(responseDTO));
};
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
// 不配这个错误处理的话 会直接返回403
.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint())
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 禁用 session
.and()
.authorizeRequests()
.antMatchers("/common/**").permitAll()
.anyRequest().authenticated()
.and()
// 禁用 X-Frame-Options 响应头。下面是具体解释:
// X-Frame-Options 是一个 HTTP 响应头,用于防止网页被嵌入到其他网页的 <frame>、<iframe> 或 <object> 标签中,从而可以减少点击劫持攻击的风险
.headers().frameOptions().disable()
.and()
.formLogin().disable();
httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationFilter.class);
return httpSecurity.build();
}
}

View File

@@ -1,127 +0,0 @@
package com.agileboot.api.customize.service;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.constant.Constants.Token;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.common.cache.RedisCacheService;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* token验证处理
*
* @author valarchie
*/
@Component
@Slf4j
@Data
@RequiredArgsConstructor
public class JwtTokenService {
/**
* 自定义令牌标识
*/
@Value("${token.header}")
private String header;
/**
* 令牌秘钥
*/
@Value("${token.secret}")
private String secret;
private final RedisCacheService redisCache;
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public SystemLoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getTokenFromRequest(request);
if (StrUtil.isNotEmpty(token)) {
try {
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Token.LOGIN_USER_KEY);
return redisCache.loginUserCache.getObjectOnlyInCacheById(uuid);
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException jwtException) {
log.error("parse token failed.", jwtException);
throw new ApiException(jwtException, ErrorCode.Client.INVALID_TOKEN);
} catch (Exception e) {
log.error("fail to get cached user from redis", e);
throw new ApiException(e, ErrorCode.Client.TOKEN_PROCESS_FAILED, e.getMessage());
}
}
return null;
}
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
public String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
public Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
private String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.getSubject();
}
/**
* 获取请求token
*
* @return token
*/
public String getTokenFromRequest(HttpServletRequest request) {
String token = request.getHeader(header);
if (StrUtil.isNotEmpty(token) && token.startsWith(Token.PREFIX)) {
token = StrUtil.stripIgnoreCase(token, Token.PREFIX, null);
}
return token;
}
}

View File

@@ -1,12 +0,0 @@
package com.agileboot.api.customize.util;
public class ApiEncryptor {
public static void main(String[] args) {
}
}

View File

@@ -1,28 +0,0 @@
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8090
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# Spring配置
spring:
profiles:
active: basic,dev

View File

@@ -9,11 +9,6 @@
<artifactId>agileboot-boot-start</artifactId>
<dependencies>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-web</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-system-base</artifactId>
@@ -26,8 +21,32 @@
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-satoken</artifactId>
<artifactId>wol-auth</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-codegenerator</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -7,7 +7,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @Author cuiJiaWang
* @Create 2025-08-12 18:07
*/
@SpringBootApplication
@SpringBootApplication(scanBasePackages = "com.agileboot.*")
public class AgilebootBootApplication {
public static void main(String[] args) {

View File

@@ -59,12 +59,21 @@ spring:
host: 121.41.64.98
port: 6379
password: 'Wyy123123'
sa-token:
# 是否输出操作日志
is-log: true
token-name: Authorization
jasypt:
encryptor:
password: ${JASYPT_ENCRYPTOR_PASSWORD:}
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# 开启内网服务调用鉴权(不允许越过gateway访问内网服务 保障服务安全)
check-same-token: false
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# 是否输出操作日志
is-log: true
# jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz

View File

@@ -1,7 +1,7 @@
server:
port: 8088
port: 18080
servlet:
context-path: /api
context-path: /
tomcat:
uri-encoding: UTF-8 # tomcat的URI编码
accept-count: 1000 # 连接数满后的排队数默认为100
@@ -10,4 +10,4 @@ server:
min-spare: 100 # Tomcat启动初始化的线程数默认值10
spring:
profiles:
active: dev
active: dev

View File

@@ -18,6 +18,7 @@
<module>wol-common-redis</module>
<module>wol-common-json</module>
<module>wol-common-satoken</module>
<module>wol-common-nacos</module>
</modules>
<packaging>pom</packaging>

View File

@@ -52,7 +52,11 @@
<artifactId>wol-common-satoken</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-nacos</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -0,0 +1,21 @@
wol:
mysql:
maser:
url: ${MYSQL_URL:121.41.64.98:3306}
username: ${MYSQL_USERNAME:agileboot}
password: ${MYSQL_PASSWORD:123456}
redis:
database: ${REDIS_DATABASE:3}
host: ${REDIS_HOST:121.41.64.98}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:'Wyy123123'}
nacos:
server:
addr: ${NACOS_ADDR:192.168.110.27:8848}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
namespace: ${NACOS_NAMESPACE:public}
group: ${NACOS_GROUP:DEFAULT_GROUP}
jasypt:
encryptor:
password: ${JASYPT_ENCRYPTOR_PASSWORD:}

View File

@@ -2,6 +2,7 @@ package com.agileboot.common.mybatis.config;
import com.agileboot.common.core.factory.YmlPropertySourceFactory;
import com.agileboot.common.mybatis.handler.InjectionMetaObjectHandler;
import com.agileboot.common.mybatis.mapper.CustomSqlInjector;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
@@ -39,4 +40,9 @@ public class MybatisPlusConfiguration {
return new InjectionMetaObjectHandler();
}
@Bean
public CustomSqlInjector customSqlInjector() {
return new CustomSqlInjector();
}
}

View File

@@ -1,6 +1,8 @@
package com.agileboot.common.mybatis.core.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@@ -50,13 +52,13 @@ public class BaseEntity implements Serializable {
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@TableField(fill = FieldFill.UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@TableField(fill = FieldFill.UPDATE)
private Date updateTime;
@TableLogic

View File

@@ -37,7 +37,7 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
Date current = ObjectUtils.defaultIfNull(baseEntity.getCreateTime(), new Date());
baseEntity.setCreateTime(current);
baseEntity.setUpdateTime(current);
// baseEntity.setUpdateTime(current);
baseEntity.setDeleted(0);
// 如果创建人为空,则填充当前登录用户的信息
@@ -47,17 +47,17 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
Long userId = loginUser.getUserId();
// 填充创建人、更新人和创建部门信息
baseEntity.setCreateBy(userId);
baseEntity.setUpdateBy(userId);
// baseEntity.setUpdateBy(userId);
} else {
// 填充创建人、更新人和创建部门信息
baseEntity.setCreateBy(DEFAULT_USER_ID);
baseEntity.setUpdateBy(DEFAULT_USER_ID);
// baseEntity.setUpdateBy(DEFAULT_USER_ID);
}
}
} else {
Date date = new Date();
this.strictInsertFill(metaObject, "createTime", Date.class, date);
this.strictInsertFill(metaObject, "updateTime", Date.class, date);
// this.strictInsertFill(metaObject, "updateTime", Date.class, date);
}
} catch (Exception e) {
throw new BizException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);

View File

@@ -0,0 +1,25 @@
package com.agileboot.common.mybatis.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import java.io.Serializable;
import java.util.Collection;
/**
* @Author cuiJiaWang
* @Create 2025-09-22 16:41
*/
public interface BaseMapperDelete<T> extends BaseMapper<T> {
/**
* 物理删除
*/
int deleteAbsoluteById(Serializable id);
/**
* 批量物理删除
*/
int deleteAbsoluteByIds(@Param(Constants.COLL) Collection<?> idList);
}

View File

@@ -0,0 +1,21 @@
package com.agileboot.common.mybatis.mapper;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import java.util.List;
/**
* @Author cuiJiaWang
* @Create 2025-09-22 16:46
*/
public class CustomSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
methodList.add(new DeleteAbsoluteById());
methodList.add(new DeleteAbsoluteByIds());
return methodList;
}
}

View File

@@ -0,0 +1,28 @@
package com.agileboot.common.mybatis.mapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
/**
* @Author cuiJiaWang
* @Create 2025-09-22 16:45
*/
public class DeleteAbsoluteById extends AbstractMethod {
private static final String method = "deleteAbsoluteById";
public DeleteAbsoluteById() {
super(method);
}
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.DELETE_BY_ID;
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty());
SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, Object.class);
return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
}
}

View File

@@ -0,0 +1,29 @@
package com.agileboot.common.mybatis.mapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
/**
* @Author cuiJiaWang
* @Create 2025-09-22 16:49
*/
public class DeleteAbsoluteByIds extends AbstractMethod {
private static final String method = "deleteAbsoluteByIds";
public DeleteAbsoluteByIds() {
super(method);
}
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.DELETE_BY_IDS;
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(), SqlScriptUtils.convertForeach(SqlScriptUtils.convertChoose("@org.apache.ibatis.type.SimpleTypeRegistry@isSimpleType(item.getClass())", "#{item}", "#{item." + tableInfo.getKeyProperty() + "}"), "coll", (String) null, "item", ","));
SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, Object.class);
return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
}
}

View File

@@ -30,3 +30,58 @@ mybatis-plus:
insertStrategy: NOT_NULL
updateStrategy: NOT_NULL
whereStrategy: NOT_NULL
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: agileboot
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic:
primary: master
strict: false
druid:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
datasource:
master:
url: jdbc:mysql://${wol.mysql.maser.url}/agileboot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: ${wol.mysql.maser.username}
password: ${wol.mysql.maser.password}

View File

@@ -0,0 +1,38 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-common</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>wol-common-nacos</artifactId>
<dependencies>
<!-- Nacos 服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Spring Boot 3 + Spring Cloud 2023 默认不启用 bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
spring:
cloud:
nacos:
discovery:
server-addr: ${wol.nacos.server.addr}
username: ${wol.nacos.server.username}
password: ${wol.nacos.server.password}
group: ${wol.nacos.server.group}
namespace: ${wol.nacos.server.namespace}
config:
server-addr: ${wol.nacos.server.addr}
group: ${wol.nacos.server.group}
namespace: ${wol.nacos.server.namespace}
file-extension: yaml
refresh-enabled: true # 是否开启动态刷新
name: ${spring.application.name}
username: ${wol.nacos.server.username}
password: ${wol.nacos.server.password}

View File

@@ -40,6 +40,19 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- JetCache -->
<!-- <dependency>-->
<!-- <groupId>com.alicp.jetcache</groupId>-->
<!-- <artifactId>jetcache-starter-redisson</artifactId>-->
<!-- <version>2.7.5</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-logging</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
</dependencies>
</project>
</project>

View File

@@ -1,5 +1,6 @@
package com.agileboot.common.redis.config;
import com.agileboot.common.core.factory.YmlPropertySourceFactory;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
@@ -12,6 +13,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
@@ -19,6 +21,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
@Slf4j
@AutoConfiguration
@PropertySource(value = "classpath:common-cache.yml", factory = YmlPropertySourceFactory.class)
public class RedisConfiguration {
@Bean

View File

@@ -0,0 +1,46 @@
spring:
data:
redis:
database: ${wol.redis.database}
host: ${wol.redis.host}
port: ${wol.redis.port}
password: ${wol.redis.password}
redis:
redisson:
config: |
singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password: ${wol.redis.password}
subscriptionsPerConnection: 5
clientName: null
address: "redis://${wol.redis.host}:${wol.redis.port}"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 24
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.client.codec.StringCodec> {}
transportMode: "NIO"
jetcache:
statIntervalMinutes: 1
areaInCacheName: false
local:
default:
type: caffeine
keyConvertor: fastjson2
remote:
default:
type: redisson
keyConvertor: fastjson2
broadcastChannel: ${spring.application.name}
keyPrefix: ${spring.application.name}
valueEncoder: java
valueDecoder: java
defaultExpireInMillis: 5000

View File

@@ -1,16 +1,13 @@
package com.agileboot.common.satoken.config;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.same.SaSameUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.agileboot.common.core.constant.HttpStatus;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -21,6 +18,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
* @author Lion Li
*/
@AutoConfiguration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class SaTokenMvcConfiguration implements WebMvcConfigurer {
/**
@@ -36,7 +34,6 @@ public class SaTokenMvcConfiguration implements WebMvcConfigurer {
* 注册 [Sa-Token全局过滤器]
*/
@Bean
@ConditionalOnMissingClass("cn.dev33.satoken.reactor.spring.SaTokenContextRegister")
public SaServletFilter getGlobleSaServletFilter() {
return new SaServletFilter()
.addInclude("/**").addExclude("/favicon.ico")
@@ -47,36 +44,4 @@ public class SaTokenMvcConfiguration implements WebMvcConfigurer {
.setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
}
/**
* 校验是否从网关转发
*/
@Bean
@ConditionalOnMissingBean(SaServletFilter.class)
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
.addInclude("/**")
.addExclude("/actuator", "/actuator/**")
.setAuth(obj -> {
if (SaManager.getConfig().getCheckSameToken()) {
SaSameUtil.checkCurrentRequestToken();
}
})
.setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
}
/**
* 对 actuator 健康检查接口 做账号密码鉴权
*/
// @Bean
// public SaServletFilter actuatorFilter() {
// String username = SpringUtil.getProperty("spring.cloud.nacos.discovery.metadata.username");
// String password = SpringUtil.getProperty("spring.cloud.nacos.discovery.metadata.userpassword");
// return new SaServletFilter()
// .addInclude("/actuator", "/actuator/**")
// .setAuth(obj -> {
// SaHttpBasicUtil.check(username + ":" + password);
// })
// .setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED));
// }
}

View File

@@ -139,7 +139,8 @@ public class LoginUser implements Serializable {
* 是否是超级管理员
*/
private Integer isAdmin;
private String clientId;
private Long clientId;
private Integer status;
/**
* 获取登录id

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>agileboot</artifactId>
<groupId>com.agileboot</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>agileboot-domain</artifactId>
<description>
领域核心代码 放在这个包
generator代码生成
</description>
<dependencies>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-infrastructure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -1,52 +0,0 @@
package com.agileboot.domain.common.cache;
import cn.hutool.extra.spring.SpringUtil;
import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate;
import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
/**
* 缓存中心 提供全局访问点
* 如果是领域类的缓存 可以自己新建一个直接放在CacheCenter 不用放在infrastructure包里的GuavaCacheService
* 或者RedisCacheService
* @author valarchie
*/
@Component
public class CacheCenter {
public static AbstractGuavaCacheTemplate<String> configCache;
public static AbstractGuavaCacheTemplate<SysDeptEntity> deptCache;
public static RedisCacheTemplate<String> captchaCache;
public static RedisCacheTemplate<SystemLoginUser> loginUserCache;
public static RedisCacheTemplate<SysUserEntity> userCache;
public static RedisCacheTemplate<SysRoleEntity> roleCache;
public static RedisCacheTemplate<SysPostEntity> postCache;
@PostConstruct
public void init() {
GuavaCacheService guavaCache = SpringUtil.getBean(GuavaCacheService.class);
RedisCacheService redisCache = SpringUtil.getBean(RedisCacheService.class);
configCache = guavaCache.configCache;
deptCache = guavaCache.deptCache;
captchaCache = redisCache.captchaCache;
loginUserCache = redisCache.loginUserCache;
userCache = redisCache.userCache;
roleCache = redisCache.roleCache;
postCache = redisCache.postCache;
}
}

View File

@@ -1,39 +0,0 @@
package com.agileboot.domain.common.cache;
import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.config.db.SysConfigService;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class GuavaCacheService {
private final SysConfigService configService;
private final SysDeptService deptService;
public final AbstractGuavaCacheTemplate<String> configCache = new AbstractGuavaCacheTemplate<String>() {
@Override
public String getObjectFromDb(Object id) {
return configService.getConfigValueByKey(id.toString());
}
};
public final AbstractGuavaCacheTemplate<SysDeptEntity> deptCache = new AbstractGuavaCacheTemplate<SysDeptEntity>() {
@Override
public SysDeptEntity getObjectFromDb(Object id) {
return deptService.getById(id.toString());
}
};
}

View File

@@ -1,83 +0,0 @@
package com.agileboot.domain.common.cache;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.common.enums.common.GenderEnum;
import com.agileboot.common.enums.common.LoginStatusEnum;
import com.agileboot.common.enums.common.NoticeStatusEnum;
import com.agileboot.common.enums.common.NoticeTypeEnum;
import com.agileboot.common.enums.common.OperationStatusEnum;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.common.UserStatusEnum;
import com.agileboot.common.enums.common.VisibleStatusEnum;
import com.agileboot.common.enums.common.YesOrNoEnum;
import com.agileboot.common.enums.dictionary.Dictionary;
import com.agileboot.common.enums.DictionaryEnum;
import com.agileboot.common.enums.dictionary.DictionaryData;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 本地一级缓存 使用Map
*
* @author valarchie
*/
public class MapCache {
private static final Map<String, List<DictionaryData>> DICTIONARY_CACHE = MapUtil.newHashMap(128);
private MapCache() {
}
static {
initDictionaryCache();
}
private static void initDictionaryCache() {
// TODO 这个可以做成自动扫描
loadInCache(BusinessTypeEnum.values());
loadInCache(YesOrNoEnum.values());
loadInCache(StatusEnum.values());
loadInCache(GenderEnum.values());
loadInCache(NoticeStatusEnum.values());
loadInCache(NoticeTypeEnum.values());
loadInCache(OperationStatusEnum.values());
loadInCache(LoginStatusEnum.values());
loadInCache(VisibleStatusEnum.values());
loadInCache(UserStatusEnum.values());
}
public static Map<String, List<DictionaryData>> dictionaryCache() {
return DICTIONARY_CACHE;
}
private static void loadInCache(DictionaryEnum[] dictionaryEnums) {
DICTIONARY_CACHE.put(getDictionaryName(dictionaryEnums[0].getClass()), arrayToList(dictionaryEnums));
}
private static String getDictionaryName(Class<?> clazz) {
Objects.requireNonNull(clazz);
Dictionary annotation = clazz.getAnnotation(Dictionary.class);
Objects.requireNonNull(annotation);
return annotation.name();
}
@SuppressWarnings("rawtypes")
private static List<DictionaryData> arrayToList(DictionaryEnum[] dictionaryEnums) {
if(ArrayUtil.isEmpty(dictionaryEnums)) {
return ListUtil.empty();
}
return Arrays.stream(dictionaryEnums).map(DictionaryData::new).collect(Collectors.toList());
}
}

View File

@@ -1,82 +0,0 @@
package com.agileboot.domain.common.cache;
import cn.hutool.extra.spring.SpringUtil;
import com.agileboot.infrastructure.cache.RedisUtil;
import com.agileboot.infrastructure.cache.redis.CacheKeyEnum;
import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import com.agileboot.domain.system.role.db.SysRoleService;
import com.agileboot.domain.system.user.db.SysUserService;
import java.io.Serializable;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class RedisCacheService {
private final RedisUtil redisUtil;
public RedisCacheTemplate<String> captchaCache;
public RedisCacheTemplate<SystemLoginUser> loginUserCache;
public RedisCacheTemplate<SysUserEntity> userCache;
public RedisCacheTemplate<SysRoleEntity> roleCache;
public RedisCacheTemplate<SysPostEntity> postCache;
// public RedisCacheTemplate<RoleInfo> roleModelInfoCache;
@PostConstruct
public void init() {
captchaCache = new RedisCacheTemplate<>(redisUtil, CacheKeyEnum.CAPTCHAT);
loginUserCache = new RedisCacheTemplate<>(redisUtil, CacheKeyEnum.LOGIN_USER_KEY);
userCache = new RedisCacheTemplate<SysUserEntity>(redisUtil, CacheKeyEnum.USER_ENTITY_KEY) {
@Override
public SysUserEntity getObjectFromDb(Object id) {
SysUserService userService = SpringUtil.getBean(SysUserService.class);
return userService.getById((Serializable) id);
}
};
roleCache = new RedisCacheTemplate<SysRoleEntity>(redisUtil, CacheKeyEnum.ROLE_ENTITY_KEY) {
@Override
public SysRoleEntity getObjectFromDb(Object id) {
SysRoleService roleService = SpringUtil.getBean(SysRoleService.class);
return roleService.getById((Serializable) id);
}
};
// roleModelInfoCache = new RedisCacheTemplate<RoleInfo>(redisUtil, CacheKeyEnum.ROLE_MODEL_INFO_KEY) {
// @Override
// public RoleInfo getObjectFromDb(Object id) {
// UserDetailsService userDetailsService = SpringUtil.getBean(UserDetailsService.class);
// return userDetailsService.getRoleInfo((Long) id);
// }
//
// };
postCache = new RedisCacheTemplate<SysPostEntity>(redisUtil, CacheKeyEnum.POST_ENTITY_KEY) {
@Override
public SysPostEntity getObjectFromDb(Object id) {
SysPostService postService = SpringUtil.getBean(SysPostService.class);
return postService.getById((Serializable) id);
}
};
}
}

View File

@@ -1,28 +0,0 @@
package com.agileboot.domain.common.command;
import cn.hutool.core.collection.CollUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class BulkOperationCommand<T> {
public BulkOperationCommand(List<T> idList) {
if (CollUtil.isEmpty(idList)) {
throw new ApiException(ErrorCode.Business.COMMON_BULK_DELETE_IDS_IS_INVALID);
}
// 移除重复元素
this.ids = new HashSet<>(idList);
}
private Set<T> ids;
}

View File

@@ -1,18 +0,0 @@
package com.agileboot.domain.common.dto;
import com.agileboot.domain.system.user.dto.UserDTO;
import java.util.Set;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class CurrentLoginUserDTO {
private UserDTO userInfo;
private String roleKey;
private Set<String> permissions;
}

View File

@@ -1,17 +0,0 @@
package com.agileboot.domain.common.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author valarchie
*/
@Data
@AllArgsConstructor
public class TokenDTO {
private String token;
private CurrentLoginUserDTO currentUser;
}

View File

@@ -1,19 +0,0 @@
package com.agileboot.domain.common.dto;
import cn.hutool.core.lang.tree.Tree;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@Data
@NoArgsConstructor
public class TreeSelectedDTO {
private List<Long> checkedKeys;
private List<Tree<Long>> menus;
private List<Tree<Long>> depts;
}

View File

@@ -1,18 +0,0 @@
package com.agileboot.domain.common.dto;
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;
}

View File

@@ -1,19 +0,0 @@
package com.agileboot.domain.common.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@Data
@NoArgsConstructor
public class UploadFileDTO {
public UploadFileDTO(String imgUrl) {
this.imgUrl = imgUrl;
}
private String imgUrl;
}

View File

@@ -1,52 +0,0 @@
package com.agileboot.domain.system.config;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.system.config.command.ConfigUpdateCommand;
import com.agileboot.domain.system.config.dto.ConfigDTO;
import com.agileboot.domain.system.config.model.ConfigModel;
import com.agileboot.domain.system.config.model.ConfigModelFactory;
import com.agileboot.domain.system.config.query.ConfigQuery;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import com.agileboot.domain.system.config.db.SysConfigService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class ConfigApplicationService {
private final ConfigModelFactory configModelFactory;
private final SysConfigService configService;
public PageDTO<ConfigDTO> getConfigList(ConfigQuery query) {
Page<SysConfigEntity> page = configService.page(query.toPage(), query.toQueryWrapper());
List<ConfigDTO> 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);
}
public void updateConfig(ConfigUpdateCommand updateCommand) {
ConfigModel configModel = configModelFactory.loadById(updateCommand.getConfigId());
configModel.loadUpdateCommand(updateCommand);
configModel.checkCanBeModify();
configModel.updateById();
CacheCenter.configCache.invalidate(configModel.getConfigKey());
}
}

View File

@@ -1,24 +0,0 @@
package com.agileboot.domain.system.config.command;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
/**
* @author valarchie
*/
@Data
@Schema
public class ConfigUpdateCommand {
@NotNull
@Positive
private Long configId;
@NotNull
@NotEmpty
private String configValue;
}

View File

@@ -1,60 +0,0 @@
package com.agileboot.domain.system.config.model;
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.domain.system.config.command.ConfigUpdateCommand;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import com.agileboot.domain.system.config.db.SysConfigService;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ConfigModel extends SysConfigEntity {
private SysConfigService configService;
private Set<String> configOptionSet;
public ConfigModel(SysConfigService configService) {
this.configService = configService;
}
public ConfigModel(SysConfigEntity entity, SysConfigService configService) {
BeanUtil.copyProperties(entity, this);
List<String> options =
JSONUtil.isTypeJSONArray(entity.getConfigOptions()) ? JSONUtil.toList(entity.getConfigOptions(),
String.class) : ListUtil.empty();
this.configOptionSet = new HashSet<>(options);
this.configService = configService;
}
public void loadUpdateCommand(ConfigUpdateCommand updateCommand) {
this.setConfigValue(updateCommand.getConfigValue());
}
public void checkCanBeModify() {
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);
}
}
}

View File

@@ -1,33 +0,0 @@
package com.agileboot.domain.system.config.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import com.agileboot.domain.system.config.db.SysConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 配置模型工厂
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class ConfigModelFactory {
private final SysConfigService configService;
public ConfigModel loadById(Long configId) {
SysConfigEntity byId = configService.getById(configId);
if (byId == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, configId, "参数配置");
}
return new ConfigModel(byId, configService);
}
public ConfigModel create() {
return new ConfigModel(configService);
}
}

View File

@@ -1,42 +0,0 @@
package com.agileboot.domain.system.config.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.config.db.SysConfigEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@Schema(name = "配置查询参数")
public class ConfigQuery extends AbstractPageQuery<SysConfigEntity> {
@Schema(description = "配置名称")
private String configName;
@Schema(description = "配置key")
private String configKey;
@Schema(description = "是否允许更改配置")
private Boolean isAllowChange;
@Override
public QueryWrapper<SysConfigEntity> addQueryCondition() {
QueryWrapper<SysConfigEntity> queryWrapper = new QueryWrapper<SysConfigEntity>()
.like(StrUtil.isNotEmpty(configName), "config_name", configName)
.eq(StrUtil.isNotEmpty(configKey), "config_key", configKey)
.eq(isAllowChange != null, "is_allow_change", isAllowChange);
this.timeRangeColumn = "create_time";
return queryWrapper;
}
}

View File

@@ -1,88 +0,0 @@
package com.agileboot.domain.system.dept;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.command.UpdateDeptCommand;
import com.agileboot.domain.system.dept.dto.DeptDTO;
import com.agileboot.domain.system.dept.model.DeptModel;
import com.agileboot.domain.system.dept.model.DeptModelFactory;
import com.agileboot.domain.system.dept.query.DeptQuery;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import com.agileboot.domain.system.role.db.SysRoleService;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 部门服务
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class DeptApplicationService {
private final SysDeptService deptService;
private final SysRoleService roleService;
private final DeptModelFactory deptModelFactory;
public List<DeptDTO> getDeptList(DeptQuery query) {
List<SysDeptEntity> 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<Tree<Long>> getDeptTree() {
List<SysDeptEntity> list = deptService.list();
return TreeUtil.build(list, 0L, (dept, tree) -> {
tree.setId(dept.getDeptId());
tree.setParentId(dept.getParentId());
tree.putExtra("label", dept.getDeptName());
});
}
public void addDept(AddDeptCommand addCommand) {
DeptModel deptModel = deptModelFactory.create();
deptModel.loadAddCommand(addCommand);
deptModel.checkDeptNameUnique();
deptModel.generateAncestors();
deptModel.insert();
}
public void updateDept(UpdateDeptCommand updateCommand) {
DeptModel deptModel = deptModelFactory.loadById(updateCommand.getDeptId());
deptModel.loadUpdateCommand(updateCommand);
deptModel.checkDeptNameUnique();
deptModel.checkParentIdConflict();
deptModel.checkStatusAllowChange();
deptModel.generateAncestors();
deptModel.updateById();
}
public void removeDept(Long deptId) {
DeptModel deptModel = deptModelFactory.loadById(deptId);
deptModel.checkHasChildDept();
deptModel.checkDeptAssignedToUsers();
deptModel.deleteById();
}
}

View File

@@ -1,58 +0,0 @@
package com.agileboot.domain.system.dept.command;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class AddDeptCommand {
/**
* 父部门ID
*/
@NotNull
@PositiveOrZero
private Long parentId;
/**
* 部门名称
*/
@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;
private Integer status;
}

View File

@@ -1,19 +0,0 @@
package com.agileboot.domain.system.dept.command;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdateDeptCommand extends AddDeptCommand {
@NotNull
@PositiveOrZero
private Long deptId;
}

View File

@@ -1,75 +0,0 @@
package com.agileboot.domain.system.dept.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 部门表
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_dept")
@ApiModel(value = "SysDeptEntity对象", description = "部门表")
public class SysDeptEntity extends BaseEntity<SysDeptEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("部门id")
@TableId(value = "dept_id", type = IdType.AUTO)
private Long deptId;
@ApiModelProperty("父部门id")
@TableField("parent_id")
private Long parentId;
@ApiModelProperty("祖级列表")
@TableField("ancestors")
private String ancestors;
@ApiModelProperty("部门名称")
@TableField("dept_name")
private String deptName;
@ApiModelProperty("显示顺序")
@TableField("order_num")
private Integer orderNum;
@TableField("leader_id")
private Long leaderId;
@ApiModelProperty("负责人")
@TableField("leader_name")
private String leaderName;
@ApiModelProperty("联系电话")
@TableField("phone")
private String phone;
@ApiModelProperty("邮箱")
@TableField("email")
private String email;
@ApiModelProperty("部门状态0正常 1停用")
@TableField("`status`")
private Integer status;
@Override
public Serializable pkVal() {
return this.deptId;
}
}

View File

@@ -1,15 +0,0 @@
package com.agileboot.domain.system.dept.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 部门表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysDeptMapper extends BaseMapper<SysDeptEntity> {
}

View File

@@ -1,50 +0,0 @@
package com.agileboot.domain.system.dept.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 部门表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysDeptService extends IService<SysDeptEntity> {
/**
* 检测部门名称是否一致
*
* @param deptName 部门名称
* @param deptId 部门id
* @param parentId 父级部门id
* @return 校验结果
*/
boolean isDeptNameDuplicated(String deptName, Long deptId, Long parentId);
/**
* 检测部门底下是否还有正在使用中的子部门
* @param deptId 部门id
* @param enabled 部门是否开启
* @return 结果
*/
boolean hasChildrenDept(Long deptId, Boolean enabled);
/**
* 是否是目标部门的子部门
* @param parentId 目标部门id
* @param childId 子部门id
* @return 校验结果
*/
boolean isChildOfTheDept(Long parentId, Long childId);
/**
* 检测该部门是否已有用户使用
* @param deptId 部门id
* @return 校验结果
*/
boolean isDeptAssignedToUsers(Long deptId);
}

View File

@@ -1,62 +0,0 @@
package com.agileboot.domain.system.dept.db;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SysUserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* <p>
* 部门表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
@Service
@RequiredArgsConstructor
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDeptEntity> implements SysDeptService {
private final SysUserMapper userMapper;
@Override
public boolean isDeptNameDuplicated(String deptName, Long deptId, Long parentId) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dept_name", deptName)
.ne(deptId != null, "dept_id", deptId)
.eq(parentId != null, "parent_id", parentId);
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean hasChildrenDept(Long deptId, Boolean enabled) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(enabled != null, "status", 1)
.and(o -> o.eq("parent_id", deptId).or()
.apply("FIND_IN_SET (" + deptId + " , ancestors)")
);
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean isChildOfTheDept(Long parentId, Long childId) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.apply("dept_id = '" + childId + "' and FIND_IN_SET ( " + parentId + " , ancestors)");
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean isDeptAssignedToUsers(Long deptId) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dept_id", deptId);
return userMapper.exists(queryWrapper);
}
}

View File

@@ -1,51 +0,0 @@
package com.agileboot.domain.system.dept.dto;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import java.util.Date;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class DeptDTO {
public DeptDTO(SysDeptEntity entity) {
if (entity != null) {
this.id = 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.createTime = entity.getCreateTime();
this.statusStr = BasicEnumUtil.getDescriptionByValue(StatusEnum.class, entity.getStatus());
}
}
private Long id;
private Long parentId;
private String deptName;
private Integer orderNum;
private String leaderName;
private String phone;
private String email;
private Integer status;
private String statusStr;
private Date createTime;
}

View File

@@ -1,106 +0,0 @@
package com.agileboot.domain.system.dept.model;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.command.UpdateDeptCommand;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects;
/**
* @author valarchie
*/
public class DeptModel extends SysDeptEntity {
private final SysDeptService deptService;
public DeptModel(SysDeptService deptService) {
this.deptService = deptService;
}
public DeptModel(SysDeptEntity entity, SysDeptService deptService) {
if (entity != null) {
// 如果大数据量的话 可以用MapStruct优化
BeanUtil.copyProperties(entity, this);
}
this.deptService = deptService;
}
public void loadAddCommand(AddDeptCommand addCommand) {
this.setParentId(addCommand.getParentId());
this.setDeptName(addCommand.getDeptName());
this.setOrderNum(addCommand.getOrderNum());
this.setLeaderName(addCommand.getLeaderName());
this.setPhone(addCommand.getPhone());
this.setEmail(addCommand.getEmail());
this.setStatus(addCommand.getStatus());
}
public void loadUpdateCommand(UpdateDeptCommand updateCommand) {
loadAddCommand(updateCommand);
setStatus(Convert.toInt(updateCommand.getStatus(), 0));
}
public void checkDeptNameUnique() {
if (deptService.isDeptNameDuplicated(getDeptName(), getDeptId(), getParentId())) {
throw new ApiException(ErrorCode.Business.DEPT_NAME_IS_NOT_UNIQUE, getDeptName());
}
}
public void checkParentIdConflict() {
if (Objects.equals(getParentId(), getDeptId())) {
throw new ApiException(ErrorCode.Business.DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF);
}
}
public void checkHasChildDept() {
if (deptService.hasChildrenDept(getDeptId(), null)) {
throw new ApiException(ErrorCode.Business.DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE);
}
}
public void checkDeptAssignedToUsers() {
if (deptService.isDeptAssignedToUsers(getDeptId())) {
throw new ApiException(ErrorCode.Business.DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE);
}
}
public void generateAncestors() {
// 处理 getParentId 可能为 null 的情况
if (getParentId() == null || getParentId() == 0) {
setAncestors(String.valueOf(getParentId() == null ? 0 : getParentId()));
return;
}
SysDeptEntity parentDept = deptService.getById(getParentId());
// 检查 parentDept 是否为 null 或者状态为禁用
if (parentDept == null || StatusEnum.DISABLE.equals(
BasicEnumUtil.fromValue(StatusEnum.class, parentDept.getStatus()))) {
throw new ApiException(ErrorCode.Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED);
}
// 处理 parentDept.getAncestors() 可能为 null 的情况
String ancestors = parentDept.getAncestors() == null ? "" : parentDept.getAncestors();
setAncestors(ancestors + "," + getParentId());
}
/**
* DDD 有些阻抗 如果为了追求性能的话 还是得通过 数据库的方式来判断
*/
public void checkStatusAllowChange() {
if (StatusEnum.DISABLE.getValue().equals(getStatus()) &&
deptService.hasChildrenDept(getDeptId(), true)) {
throw new ApiException(ErrorCode.Business.DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE);
}
}
}

View File

@@ -1,45 +0,0 @@
package com.agileboot.domain.system.dept.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 部门模型工厂
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class DeptModelFactory {
private final SysDeptService deptService;
public DeptModel loadById(Long deptId) {
SysDeptEntity byId = deptService.getById(deptId);
if (byId == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, deptId, "部门");
}
return new DeptModel(byId, deptService);
}
public DeptModel create() {
return new DeptModel(deptService);
}
public DeptModel loadFromAddCommand(AddDeptCommand addCommand, DeptModel model) {
model.setParentId(addCommand.getParentId());
model.setDeptName(addCommand.getDeptName());
model.setOrderNum(addCommand.getOrderNum());
model.setLeaderName(addCommand.getLeaderName());
model.setPhone(addCommand.getPhone());
model.setEmail(addCommand.getEmail());
return model;
}
}

View File

@@ -1,36 +0,0 @@
package com.agileboot.domain.system.dept.query;
import com.agileboot.common.core.page.AbstractQuery;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
public class DeptQuery extends AbstractQuery<SysDeptEntity> {
private Long deptId;
private Long parentId;
@Override
public QueryWrapper<SysDeptEntity> addQueryCondition() {
// TODO parentId 这个似乎没使用
return new QueryWrapper<SysDeptEntity>()
// .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)")
// );
}
}

View File

@@ -1,54 +0,0 @@
package com.agileboot.domain.system.log;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.system.log.dto.LoginLogDTO;
import com.agileboot.domain.system.log.query.LoginLogQuery;
import com.agileboot.domain.system.log.dto.OperationLogDTO;
import com.agileboot.domain.system.log.query.OperationLogQuery;
import com.agileboot.domain.system.log.db.SysLoginInfoEntity;
import com.agileboot.domain.system.log.db.SysOperationLogEntity;
import com.agileboot.domain.system.log.db.SysLoginInfoService;
import com.agileboot.domain.system.log.db.SysOperationLogService;
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 lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class LogApplicationService {
// TODO 命名到时候统一改成叫LoginLog
private final SysLoginInfoService loginInfoService;
private final SysOperationLogService operationLogService;
public PageDTO<LoginLogDTO> getLoginInfoList(LoginLogQuery query) {
Page<SysLoginInfoEntity> page = loginInfoService.page(query.toPage(), query.toQueryWrapper());
List<LoginLogDTO> records = page.getRecords().stream().map(LoginLogDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
public void deleteLoginInfo(BulkOperationCommand<Long> deleteCommand) {
QueryWrapper<SysLoginInfoEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.in("info_id", deleteCommand.getIds());
loginInfoService.remove(queryWrapper);
}
public PageDTO<OperationLogDTO> getOperationLogList(OperationLogQuery query) {
Page<SysOperationLogEntity> page = operationLogService.page(query.toPage(), query.toQueryWrapper());
List<OperationLogDTO> records = page.getRecords().stream().map(OperationLogDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
public void deleteOperationLog(BulkOperationCommand<Long> deleteCommand) {
operationLogService.removeBatchByIds(deleteCommand.getIds());
}
}

Some files were not shown because too many files have changed in this diff Show More