feat: 重构项目结构并新增微信小程序认证模块
This commit is contained in:
16
README.md
16
README.md
@@ -97,7 +97,7 @@ spring:
|
||||
|
||||
---
|
||||
|
||||
## 📁 项目结构
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
youlai-boot
|
||||
@@ -105,15 +105,13 @@ youlai-boot
|
||||
├── sql/ # 数据库脚本
|
||||
├── src/main/java/com/youlai/boot/
|
||||
│ ├── auth/ # 认证模块
|
||||
│ ├── common/ # 公共模块
|
||||
│ ├── config/ # 配置模块
|
||||
│ ├── core/ # 核心模块(AOP、异常、过滤器)
|
||||
│ ├── file/ # 文件服务
|
||||
│ ├── plugin/ # 插件扩展(Knife4j、MyBatis)
|
||||
│ ├── security/ # 安全模块(JWT、Token)
|
||||
│ ├── support/ # 支撑服务(邮件、短信、WebSocket)
|
||||
│ ├── system/ # 系统模块(用户、角色、菜单、部门)
|
||||
│ ├── tool/ # 工具模块(代码生成)
|
||||
│ ├── module/ # 业务模块(文件、代码生成)
|
||||
│ ├── framework/ # 技术中台(安全、缓存、持久化、集成)
|
||||
│ ├── interfaces/ # 对外接口(SSE)
|
||||
│ ├── shared/ # 跨模块共享(DTO、枚举、常量)
|
||||
│ ├── common/ # 基础能力(结果、异常、切面、工具)
|
||||
│ ├── config/ # 全局配置
|
||||
│ └── YouLaiBootApplication.java # 启动类
|
||||
└── pom.xml # Maven 配置
|
||||
```
|
||||
|
||||
7
pom.xml
7
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>youlai-boot</artifactId>
|
||||
<version>4.1.0</version>
|
||||
<version>4.3.0</version>
|
||||
<description>基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。</description>
|
||||
|
||||
<parent>
|
||||
@@ -133,11 +133,6 @@
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
|
||||
@@ -372,7 +372,7 @@ CREATE TABLE `sys_user` (
|
||||
-- Records of sys_user
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345678', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18888888888', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (4, 'dept_manager', '部门主管', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345680', 1, 'manager@youlaitech.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (5, 'dept_member', '部门成员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345681', 1, 'member@youlaitech.com', now(), NULL, now(), NULL, 0);
|
||||
@@ -406,26 +406,30 @@ INSERT IGNORE INTO `sys_user_role` VALUES (7, 7);
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_log`;
|
||||
CREATE TABLE `sys_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`module` varchar(50) NOT NULL COMMENT '日志模块',
|
||||
`request_method` varchar(64) NOT NULL COMMENT '请求方式',
|
||||
`request_params` text COMMENT '请求参数(批量请求参数可能会超过text)',
|
||||
`response_content` mediumtext COMMENT '返回参数',
|
||||
`content` varchar(255) NOT NULL COMMENT '日志内容',
|
||||
`request_uri` varchar(255) COMMENT '请求路径',
|
||||
`method` varchar(255) COMMENT '方法名',
|
||||
`ip` varchar(45) COMMENT 'IP地址',
|
||||
`province` varchar(100) COMMENT '省份',
|
||||
`city` varchar(100) COMMENT '城市',
|
||||
`execution_time` bigint COMMENT '执行时间(ms)',
|
||||
`browser` varchar(100) COMMENT '浏览器',
|
||||
`browser_version` varchar(100) COMMENT '浏览器版本',
|
||||
`os` varchar(100) COMMENT '终端系统',
|
||||
`create_by` bigint COMMENT '创建人ID',
|
||||
`create_time` datetime COMMENT '创建时间',
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`module` TINYINT NOT NULL COMMENT '模块,数字枚举,参考 LogModule 枚举',
|
||||
`action_type` TINYINT NOT NULL COMMENT '操作类型,数字枚举,参考 ActionType 枚举',
|
||||
`title` VARCHAR(100) NOT NULL COMMENT '前端显示标题',
|
||||
`content` TEXT COMMENT '自定义日志内容',
|
||||
`operator_id` BIGINT NOT NULL COMMENT '操作人ID',
|
||||
`operator_name` VARCHAR(50) COMMENT '操作人名称',
|
||||
`request_uri` VARCHAR(255) COMMENT '请求路径',
|
||||
`request_method` VARCHAR(10) COMMENT '请求方法',
|
||||
`ip` VARCHAR(45) COMMENT 'IP地址',
|
||||
`province` VARCHAR(100) COMMENT '省份',
|
||||
`city` VARCHAR(100) COMMENT '城市',
|
||||
`device` VARCHAR(100) COMMENT '设备',
|
||||
`os` VARCHAR(100) COMMENT '操作系统',
|
||||
`browser` VARCHAR(100) COMMENT '浏览器',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '0失败 1成功',
|
||||
`error_msg` VARCHAR(255) COMMENT '错误信息',
|
||||
`execution_time` INT COMMENT '执行时间(ms)',
|
||||
`create_time` DATETIME COMMENT '操作时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
|
||||
KEY `idx_module_action_time` (`module`, `action_type`, `create_time`),
|
||||
KEY `idx_operator_time` (`operator_id`, `create_time`),
|
||||
KEY `idx_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for gen_table
|
||||
|
||||
@@ -333,7 +333,7 @@ CREATE TABLE `sys_user` (
|
||||
-- Records of sys_user
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345678', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18888888888', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (4, 'dept_manager', '部门主管', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345680', 1, 'manager@youlaitech.com', now(), NULL, now(), NULL, 0);
|
||||
INSERT INTO `sys_user` VALUES (5, 'dept_member', '部门成员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345681', 1, 'member@youlaitech.com', now(), NULL, now(), NULL, 0);
|
||||
@@ -366,26 +366,30 @@ INSERT IGNORE INTO `sys_user_role` VALUES (7, 7);
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_log`;
|
||||
CREATE TABLE `sys_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`module` varchar(50) NOT NULL COMMENT '日志模块',
|
||||
`request_method` varchar(64) NOT NULL COMMENT '请求方式',
|
||||
`request_params` text COMMENT '请求参数(批量请求参数可能会超过text)',
|
||||
`response_content` mediumtext COMMENT '返回参数',
|
||||
`content` varchar(255) NOT NULL COMMENT '日志内容',
|
||||
`request_uri` varchar(255) COMMENT '请求路径',
|
||||
`method` varchar(255) COMMENT '方法名',
|
||||
`ip` varchar(45) COMMENT 'IP地址',
|
||||
`province` varchar(100) COMMENT '省份',
|
||||
`city` varchar(100) COMMENT '城市',
|
||||
`execution_time` bigint COMMENT '执行时间(ms)',
|
||||
`browser` varchar(100) COMMENT '浏览器',
|
||||
`browser_version` varchar(100) COMMENT '浏览器版本',
|
||||
`os` varchar(100) COMMENT '终端系统',
|
||||
`create_by` bigint COMMENT '创建人ID',
|
||||
`create_time` datetime COMMENT '创建时间',
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`module` TINYINT NOT NULL COMMENT '模块,数字枚举,参考 LogModule 枚举',
|
||||
`action_type` TINYINT NOT NULL COMMENT '操作类型,数字枚举,参考 ActionType 枚举',
|
||||
`title` VARCHAR(100) NOT NULL COMMENT '前端显示标题',
|
||||
`content` TEXT COMMENT '自定义日志内容',
|
||||
`operator_id` BIGINT NOT NULL COMMENT '操作人ID',
|
||||
`operator_name` VARCHAR(50) COMMENT '操作人名称',
|
||||
`request_uri` VARCHAR(255) COMMENT '请求路径',
|
||||
`request_method` VARCHAR(10) COMMENT '请求方法',
|
||||
`ip` VARCHAR(45) COMMENT 'IP地址',
|
||||
`province` VARCHAR(100) COMMENT '省份',
|
||||
`city` VARCHAR(100) COMMENT '城市',
|
||||
`device` VARCHAR(100) COMMENT '设备',
|
||||
`os` VARCHAR(100) COMMENT '操作系统',
|
||||
`browser` VARCHAR(100) COMMENT '浏览器',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '0失败 1成功',
|
||||
`error_msg` VARCHAR(255) COMMENT '错误信息',
|
||||
`execution_time` INT COMMENT '执行时间(ms)',
|
||||
`create_time` DATETIME COMMENT '操作时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
|
||||
KEY `idx_module_action_time` (`module`, `action_type`, `create_time`),
|
||||
KEY `idx_operator_time` (`operator_id`, `create_time`),
|
||||
KEY `idx_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for gen_table
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.youlai.boot.auth.controller;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.CaptchaVO;
|
||||
import com.youlai.boot.auth.model.dto.LoginRequest;
|
||||
import com.youlai.boot.auth.model.LoginReq;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.auth.service.AuthService;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.framework.integration.captcha.model.CaptchaInfo;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -32,24 +33,24 @@ public class AuthController {
|
||||
|
||||
@Operation(summary = "获取验证码")
|
||||
@GetMapping("/captcha")
|
||||
public Result<CaptchaVO> getCaptcha() {
|
||||
CaptchaVO captcha = authService.getCaptcha();
|
||||
public Result<CaptchaInfo> getCaptcha() {
|
||||
CaptchaInfo captcha = authService.getCaptcha();
|
||||
return Result.success(captcha);
|
||||
}
|
||||
|
||||
@Operation(summary = "账号密码登录")
|
||||
@PostMapping("/login")
|
||||
@Log(value = "登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> login(@RequestBody @Valid LoginRequest request) {
|
||||
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
|
||||
public Result<AuthenticationToken> login(@RequestBody @Valid LoginReq request) {
|
||||
AuthenticationToken authenticationToken = authService.login(request.getUsername(), request.getPassword());
|
||||
return Result.success(authenticationToken);
|
||||
}
|
||||
|
||||
@Operation(summary = "短信验证码登录")
|
||||
@PostMapping("/login/sms")
|
||||
@Log(value = "短信验证码登录", module = LogModuleEnum.LOGIN)
|
||||
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
|
||||
public Result<AuthenticationToken> loginBySms(
|
||||
@Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile,
|
||||
@Parameter(description = "手机号", example = "18888888888") @RequestParam String mobile,
|
||||
@Parameter(description = "验证码", example = "123456") @RequestParam String code
|
||||
) {
|
||||
AuthenticationToken loginResult = authService.loginBySms(mobile, code);
|
||||
@@ -59,7 +60,7 @@ public class AuthController {
|
||||
@Operation(summary = "发送登录短信验证码")
|
||||
@PostMapping("/sms/code")
|
||||
public Result<Void> sendSmsCode(
|
||||
@Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile
|
||||
@Parameter(description = "手机号", example = "18888888888") @RequestParam String mobile
|
||||
) {
|
||||
authService.sendSmsCode(mobile);
|
||||
return Result.success();
|
||||
@@ -67,7 +68,7 @@ public class AuthController {
|
||||
|
||||
@Operation(summary = "退出登录")
|
||||
@DeleteMapping("/logout")
|
||||
@Log(value = "退出登录", module = LogModuleEnum.LOGIN)
|
||||
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGOUT)
|
||||
public Result<Void> logout() {
|
||||
authService.logout();
|
||||
return Result.success();
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
package com.youlai.boot.auth.controller;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.WechatMiniappLoginResult;
|
||||
import com.youlai.boot.auth.service.WechatMiniappAuthService;
|
||||
import com.youlai.boot.auth.model.WxMaBindMobileReq;
|
||||
import com.youlai.boot.auth.model.WxMaPhoneLoginReq;
|
||||
import com.youlai.boot.auth.model.WxMaLoginResp;
|
||||
import com.youlai.boot.auth.service.WxMaAuthService;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 微信小程序认证控制层
|
||||
*
|
||||
@@ -24,12 +30,12 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
*/
|
||||
@Tag(name = "13.微信小程序认证")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/wechat/miniapp/auth")
|
||||
@RequestMapping("/api/v1/wxma/auth")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class WechatMiniappAuthController {
|
||||
public class WxMaAuthController {
|
||||
|
||||
private final WechatMiniappAuthService wechatMiniappAuthService;
|
||||
private final WxMaAuthService wxMaAuthService;
|
||||
|
||||
/**
|
||||
* 静默登录
|
||||
@@ -42,12 +48,12 @@ public class WechatMiniappAuthController {
|
||||
*/
|
||||
@Operation(summary = "静默登录", description = "通过微信 code 登录,已绑定用户直接返回 token,未绑定用户返回 openid 需绑定手机号")
|
||||
@PostMapping("/silent-login")
|
||||
@Log(value = "微信小程序静默登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<WechatMiniappLoginResult> silentLogin(
|
||||
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
|
||||
public Result<WxMaLoginResp> silentLogin(
|
||||
@Parameter(description = "微信登录凭证(wx.login 获取)", required = true, example = "0xxx")
|
||||
@RequestParam String code
|
||||
) {
|
||||
WechatMiniappLoginResult result = wechatMiniappAuthService.silentLogin(code);
|
||||
WxMaLoginResp result = wxMaAuthService.silentLogin(code);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@@ -60,14 +66,9 @@ public class WechatMiniappAuthController {
|
||||
*/
|
||||
@Operation(summary = "手机号快捷登录", description = "同时使用微信 code 和手机号授权 code 登录,适用于企业认证小程序")
|
||||
@PostMapping("/phone-login")
|
||||
@Log(value = "微信小程序手机号快捷登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> phoneLogin(
|
||||
@Parameter(description = "微信登录凭证(wx.login 获取)", required = true, example = "0xxx")
|
||||
@RequestParam String loginCode,
|
||||
@Parameter(description = "手机号授权凭证(getPhoneNumber 事件获取)", required = true, example = "0xxx")
|
||||
@RequestParam String phoneCode
|
||||
) {
|
||||
AuthenticationToken result = wechatMiniappAuthService.phoneLogin(loginCode, phoneCode);
|
||||
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
|
||||
public Result<AuthenticationToken> phoneLogin(@Valid @RequestBody WxMaPhoneLoginReq req) {
|
||||
AuthenticationToken result = wxMaAuthService.phoneLogin(req.getLoginCode(), req.getPhoneCode());
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@@ -80,16 +81,9 @@ public class WechatMiniappAuthController {
|
||||
*/
|
||||
@Operation(summary = "绑定手机号", description = "为静默登录用户绑定手机号,绑定成功后自动登录")
|
||||
@PostMapping("/bind-mobile")
|
||||
@Log(value = "微信小程序绑定手机号", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> bindMobile(
|
||||
@Parameter(description = "微信用户唯一标识(静默登录返回)", required = true)
|
||||
@RequestParam String openid,
|
||||
@Parameter(description = "手机号码", required = true, example = "18812345678")
|
||||
@RequestParam String mobile,
|
||||
@Parameter(description = "短信验证码", required = true, example = "123456")
|
||||
@RequestParam String smsCode
|
||||
) {
|
||||
AuthenticationToken result = wechatMiniappAuthService.bindMobile(openid, mobile, smsCode);
|
||||
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
|
||||
public Result<AuthenticationToken> bindMobile(@Valid @RequestBody WxMaBindMobileReq req) {
|
||||
AuthenticationToken result = wxMaAuthService.bindMobile(req.getOpenid(), req.getMobile(), req.getSmsCode());
|
||||
return Result.success(result);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.auth.model.dto;
|
||||
package com.youlai.boot.auth.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -13,7 +13,7 @@ import jakarta.validation.constraints.NotBlank;
|
||||
*/
|
||||
@Schema(description = "登录请求参数")
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
public class LoginReq {
|
||||
|
||||
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@@ -29,4 +29,3 @@ public class LoginRequest {
|
||||
@Schema(description = "验证码", example = "123456")
|
||||
private String captchaCode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.youlai.boot.auth.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信小程序绑定手机号请求
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@Schema(description = "微信小程序绑定手机号请求")
|
||||
@Data
|
||||
public class WxMaBindMobileReq {
|
||||
|
||||
@NotBlank(message = "openid 不能为空")
|
||||
@Schema(description = "微信用户唯一标识(静默登录返回)", example = "oVBkZ0aYgDMDIywRdgPW8-joxXc4")
|
||||
private String openid;
|
||||
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
@Schema(description = "手机号码", example = "18888888888")
|
||||
private String mobile;
|
||||
|
||||
@NotBlank(message = "短信验证码不能为空")
|
||||
@Schema(description = "短信验证码", example = "123456")
|
||||
private String smsCode;
|
||||
}
|
||||
42
src/main/java/com/youlai/boot/auth/model/WxMaLoginResp.java
Normal file
42
src/main/java/com/youlai/boot/auth/model/WxMaLoginResp.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.youlai.boot.auth.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 微信小程序登录响应
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "微信小程序登录响应")
|
||||
public class WxMaLoginResp {
|
||||
|
||||
@Schema(description = "是否新用户")
|
||||
private Boolean isNewUser;
|
||||
|
||||
@Schema(description = "是否需要绑定手机号")
|
||||
private Boolean needBindMobile;
|
||||
|
||||
@Schema(description = "微信openid(绑定手机号时需要)")
|
||||
private String openid;
|
||||
|
||||
@Schema(description = "访问令牌")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(description = "刷新令牌")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(description = "令牌类型")
|
||||
private String tokenType;
|
||||
|
||||
@Schema(description = "过期时间(秒)")
|
||||
private Integer expiresIn;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.youlai.boot.auth.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信小程序手机号快捷登录请求
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@Schema(description = "微信小程序手机号快捷登录请求")
|
||||
@Data
|
||||
public class WxMaPhoneLoginReq {
|
||||
|
||||
@NotBlank(message = "微信登录凭证不能为空")
|
||||
@Schema(description = "微信登录凭证(wx.login 获取)", example = "0xxx")
|
||||
private String loginCode;
|
||||
|
||||
@NotBlank(message = "手机号授权凭证不能为空")
|
||||
@Schema(description = "手机号授权凭证(getPhoneNumber 事件获取)", example = "0xxx")
|
||||
private String phoneCode;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.youlai.boot.auth.model.vo;
|
||||
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信小程序登录结果
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "微信小程序登录结果")
|
||||
public class WechatMiniappLoginResult {
|
||||
|
||||
@Schema(description = "是否新用户")
|
||||
private Boolean isNewUser;
|
||||
|
||||
@Schema(description = "是否需要绑定手机号")
|
||||
private Boolean needBindMobile;
|
||||
|
||||
@Schema(description = "微信openid(绑定手机号时需要)")
|
||||
private String openid;
|
||||
|
||||
@Schema(description = "访问令牌")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(description = "刷新令牌")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(description = "令牌类型")
|
||||
private String tokenType;
|
||||
|
||||
@Schema(description = "过期时间(秒)")
|
||||
private Integer expiresIn;
|
||||
|
||||
/**
|
||||
* 创建需要绑定手机号的结果
|
||||
*/
|
||||
public static WechatMiniappLoginResult needBindMobile(String openid) {
|
||||
WechatMiniappLoginResult result = new WechatMiniappLoginResult();
|
||||
result.setIsNewUser(true);
|
||||
result.setNeedBindMobile(true);
|
||||
result.setOpenid(openid);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建登录成功的结果
|
||||
*/
|
||||
public static WechatMiniappLoginResult success(AuthenticationToken token) {
|
||||
WechatMiniappLoginResult result = new WechatMiniappLoginResult();
|
||||
result.setIsNewUser(false);
|
||||
result.setNeedBindMobile(false);
|
||||
result.setAccessToken(token.getAccessToken());
|
||||
result.setRefreshToken(token.getRefreshToken());
|
||||
result.setTokenType(token.getTokenType());
|
||||
result.setExpiresIn(token.getExpiresIn());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.boot.auth.service;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.CaptchaVO;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.framework.integration.captcha.model.CaptchaInfo;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
|
||||
/**
|
||||
* 认证服务接口
|
||||
@@ -43,10 +43,8 @@ public interface AuthService {
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*
|
||||
* @return 验证码
|
||||
*/
|
||||
CaptchaVO getCaptcha();
|
||||
CaptchaInfo getCaptcha();
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.boot.auth.service;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.WechatMiniappLoginResult;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.auth.model.WxMaLoginResp;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
|
||||
/**
|
||||
* 微信小程序认证服务接口
|
||||
@@ -9,7 +9,7 @@ import com.youlai.boot.security.model.AuthenticationToken;
|
||||
* @author Ray.Hao
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public interface WechatMiniappAuthService {
|
||||
public interface WxMaAuthService {
|
||||
|
||||
/**
|
||||
* 静默登录
|
||||
@@ -21,7 +21,7 @@ public interface WechatMiniappAuthService {
|
||||
* @param code 微信登录凭证(wx.login 获取)
|
||||
* @return 登录结果(成功返回 token,需绑定返回 openid)
|
||||
*/
|
||||
WechatMiniappLoginResult silentLogin(String code);
|
||||
WxMaLoginResp silentLogin(String code);
|
||||
|
||||
/**
|
||||
* 手机号快捷登录
|
||||
@@ -1,24 +1,21 @@
|
||||
package com.youlai.boot.auth.service.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.auth.model.vo.CaptchaVO;
|
||||
import com.youlai.boot.auth.service.AuthService;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.enums.CaptchaTypeEnum;
|
||||
import com.youlai.boot.config.property.CaptchaProperties;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.security.model.SmsAuthenticationToken;
|
||||
import com.youlai.boot.security.token.TokenManager;
|
||||
import com.youlai.boot.security.util.SecurityUtils;
|
||||
import com.youlai.boot.support.sms.enums.SmsTypeEnum;
|
||||
import com.youlai.boot.support.sms.service.SmsService;
|
||||
import com.youlai.boot.system.service.UserSocialService;
|
||||
import com.youlai.boot.system.service.UserService;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import com.youlai.boot.framework.integration.captcha.model.CaptchaInfo;
|
||||
import com.youlai.boot.framework.integration.captcha.service.CaptchaService;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.framework.security.model.SmsAuthenticationToken;
|
||||
import com.youlai.boot.framework.security.token.TokenManager;
|
||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||
import com.youlai.boot.framework.integration.sms.enums.SmsTypeEnum;
|
||||
import com.youlai.boot.framework.integration.sms.service.SmsService;
|
||||
import com.youlai.boot.system.model.entity.SysLog;
|
||||
import com.youlai.boot.system.service.LogService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@@ -27,8 +24,10 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.awt.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -47,12 +46,10 @@ public class AuthServiceImpl implements AuthService {
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final TokenManager tokenManager;
|
||||
|
||||
private final Font captchaFont;
|
||||
private final CaptchaProperties captchaProperties;
|
||||
private final CodeGenerator codeGenerator;
|
||||
|
||||
private final SmsService smsService;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final CaptchaService captchaService;
|
||||
private final LogService logService;
|
||||
/**
|
||||
* 用户名密码登录
|
||||
*
|
||||
@@ -124,7 +121,6 @@ public class AuthServiceImpl implements AuthService {
|
||||
// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)
|
||||
AuthenticationToken authenticationToken = tokenManager.generateToken(authentication);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
return authenticationToken;
|
||||
}
|
||||
|
||||
@@ -143,50 +139,10 @@ public class AuthServiceImpl implements AuthService {
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*
|
||||
* @return 验证码
|
||||
*/
|
||||
@Override
|
||||
public CaptchaVO getCaptcha() {
|
||||
|
||||
String captchaType = captchaProperties.getType();
|
||||
int width = captchaProperties.getWidth();
|
||||
int height = captchaProperties.getHeight();
|
||||
int interfereCount = captchaProperties.getInterfereCount();
|
||||
int codeLength = captchaProperties.getCode().getLength();
|
||||
|
||||
AbstractCaptcha captcha;
|
||||
if (CaptchaTypeEnum.CIRCLE.name().equalsIgnoreCase(captchaType)) {
|
||||
captcha = CaptchaUtil.createCircleCaptcha(width, height, codeLength, interfereCount);
|
||||
} else if (CaptchaTypeEnum.GIF.name().equalsIgnoreCase(captchaType)) {
|
||||
captcha = CaptchaUtil.createGifCaptcha(width, height, codeLength);
|
||||
} else if (CaptchaTypeEnum.LINE.name().equalsIgnoreCase(captchaType)) {
|
||||
captcha = CaptchaUtil.createLineCaptcha(width, height, codeLength, interfereCount);
|
||||
} else if (CaptchaTypeEnum.SHEAR.name().equalsIgnoreCase(captchaType)) {
|
||||
captcha = CaptchaUtil.createShearCaptcha(width, height, codeLength, interfereCount);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid captcha type: " + captchaType);
|
||||
}
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.setTextAlpha(captchaProperties.getTextAlpha());
|
||||
captcha.setFont(captchaFont);
|
||||
|
||||
String captchaCode = captcha.getCode();
|
||||
String imageBase64Data = captcha.getImageBase64Data();
|
||||
|
||||
// 验证码文本缓存至Redis,用于登录校验
|
||||
String captchaId = IdUtil.fastSimpleUUID();
|
||||
redisTemplate.opsForValue().set(
|
||||
StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaId),
|
||||
captchaCode,
|
||||
captchaProperties.getExpireSeconds(),
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
return CaptchaVO.builder()
|
||||
.captchaId(captchaId)
|
||||
.captchaBase64(imageBase64Data)
|
||||
.build();
|
||||
public CaptchaInfo getCaptcha() {
|
||||
return captchaService.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,16 +5,16 @@ import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.auth.model.vo.WechatMiniappLoginResult;
|
||||
import com.youlai.boot.auth.service.WechatMiniappAuthService;
|
||||
import com.youlai.boot.auth.model.WxMaLoginResp;
|
||||
import com.youlai.boot.auth.service.WxMaAuthService;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.security.exception.NeedBindMobileException;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.security.model.WechatMiniAuthenticationToken;
|
||||
import com.youlai.boot.security.token.TokenManager;
|
||||
import com.youlai.boot.framework.security.exception.NeedBindMobileException;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import com.youlai.boot.framework.security.model.WechatMiniAuthenticationToken;
|
||||
import com.youlai.boot.framework.security.token.TokenManager;
|
||||
import com.youlai.boot.system.enums.SocialPlatformEnum;
|
||||
import com.youlai.boot.system.model.entity.User;
|
||||
import com.youlai.boot.system.model.entity.SysUser;
|
||||
import com.youlai.boot.system.service.UserSocialService;
|
||||
import com.youlai.boot.system.service.UserService;
|
||||
import com.youlai.boot.system.service.UserRoleService;
|
||||
@@ -35,12 +35,12 @@ import java.util.Collections;
|
||||
* 微信小程序认证服务实现
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.4.0
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class WechatMiniappAuthServiceImpl implements WechatMiniappAuthService {
|
||||
public class WxMaAuthServiceImpl implements WxMaAuthService {
|
||||
|
||||
private final WxMaService wxMaService;
|
||||
private final AuthenticationManager authenticationManager;
|
||||
@@ -54,16 +54,27 @@ public class WechatMiniappAuthServiceImpl implements WechatMiniappAuthService {
|
||||
* 静默登录
|
||||
*/
|
||||
@Override
|
||||
public WechatMiniappLoginResult silentLogin(String code) {
|
||||
public WxMaLoginResp silentLogin(String code) {
|
||||
WechatMiniAuthenticationToken token = new WechatMiniAuthenticationToken(code);
|
||||
|
||||
try {
|
||||
Authentication authentication = authenticationManager.authenticate(token);
|
||||
AuthenticationToken authToken = tokenManager.generateToken(authentication);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
return WechatMiniappLoginResult.success(authToken);
|
||||
return WxMaLoginResp.builder()
|
||||
.isNewUser(false)
|
||||
.needBindMobile(false)
|
||||
.accessToken(authToken.getAccessToken())
|
||||
.refreshToken(authToken.getRefreshToken())
|
||||
.tokenType(authToken.getTokenType())
|
||||
.expiresIn(authToken.getExpiresIn())
|
||||
.build();
|
||||
} catch (NeedBindMobileException e) {
|
||||
return WechatMiniappLoginResult.needBindMobile(e.getOpenid());
|
||||
return WxMaLoginResp.builder()
|
||||
.isNewUser(true)
|
||||
.needBindMobile(true)
|
||||
.openid(e.getOpenid())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +94,7 @@ public class WechatMiniappAuthServiceImpl implements WechatMiniappAuthService {
|
||||
log.info("微信小程序手机号快捷登录:openid={}, mobile={}", openid, mobile);
|
||||
|
||||
// 3. 查询或创建用户
|
||||
User user = findOrCreateUser(mobile);
|
||||
SysUser user = findOrCreateUser(mobile);
|
||||
|
||||
// 4. 绑定微信 openid
|
||||
bindWechatOpenid(user, session);
|
||||
@@ -102,7 +113,7 @@ public class WechatMiniappAuthServiceImpl implements WechatMiniappAuthService {
|
||||
validateSmsCode(mobile, smsCode);
|
||||
|
||||
// 2. 查询或创建用户
|
||||
User user = findOrCreateUser(mobile);
|
||||
SysUser user = findOrCreateUser(mobile);
|
||||
|
||||
// 3. 绑定微信 openid
|
||||
userSocialService.bindOrUpdate(
|
||||
@@ -148,9 +159,9 @@ public class WechatMiniappAuthServiceImpl implements WechatMiniappAuthService {
|
||||
/**
|
||||
* 查询或创建用户
|
||||
*/
|
||||
private User findOrCreateUser(String mobile) {
|
||||
User user = userService.lambdaQuery()
|
||||
.eq(User::getMobile, mobile)
|
||||
private SysUser findOrCreateUser(String mobile) {
|
||||
SysUser user = userService.lambdaQuery()
|
||||
.eq(SysUser::getMobile, mobile)
|
||||
.one();
|
||||
|
||||
if (user == null) {
|
||||
@@ -167,8 +178,8 @@ public class WechatMiniappAuthServiceImpl implements WechatMiniappAuthService {
|
||||
* 新用户默认分配 GUEST(访问游客)角色
|
||||
* </p>
|
||||
*/
|
||||
private User createNewUser(String mobile) {
|
||||
User user = new User();
|
||||
private SysUser createNewUser(String mobile) {
|
||||
SysUser user = new SysUser();
|
||||
user.setMobile(mobile);
|
||||
user.setUsername("wx_" + IdUtil.fastSimpleUUID().substring(0, 8));
|
||||
user.setNickname("微信用户");
|
||||
@@ -187,7 +198,7 @@ public class WechatMiniappAuthServiceImpl implements WechatMiniappAuthService {
|
||||
/**
|
||||
* 绑定微信 openid
|
||||
*/
|
||||
private void bindWechatOpenid(User user, WxMaJscode2SessionResult session) {
|
||||
private void bindWechatOpenid(SysUser user, WxMaJscode2SessionResult session) {
|
||||
try {
|
||||
userSocialService.bindOrUpdate(
|
||||
user.getId(),
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.youlai.boot.common.annotation;
|
||||
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
@@ -16,34 +17,31 @@ import java.lang.annotation.*;
|
||||
public @interface Log {
|
||||
|
||||
/**
|
||||
* 日志描述
|
||||
* 模块
|
||||
*
|
||||
* @return 日志描述
|
||||
* @return 模块
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 日志模块
|
||||
*
|
||||
* @return 日志模块
|
||||
*/
|
||||
|
||||
LogModuleEnum module();
|
||||
|
||||
/**
|
||||
* 是否记录请求参数
|
||||
* 操作类型
|
||||
*
|
||||
* @return 是否记录请求参数
|
||||
* @return 操作类型
|
||||
*/
|
||||
boolean params() default true;
|
||||
ActionTypeEnum value();
|
||||
|
||||
/**
|
||||
* 是否记录响应结果
|
||||
* <br/>
|
||||
* 响应结果默认不记录,避免日志过大
|
||||
* @return 是否记录响应结果
|
||||
* 操作标题(可选,默认使用枚举描述)
|
||||
*
|
||||
* @return 标题
|
||||
*/
|
||||
boolean result() default false;
|
||||
String title() default "";
|
||||
|
||||
/**
|
||||
* 自定义日志内容(可选,用于记录操作细节)
|
||||
*
|
||||
* @return 日志内容
|
||||
*/
|
||||
String content() default "";
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.youlai.boot.common.annotation;
|
||||
|
||||
import com.youlai.boot.core.validator.FieldValidator;
|
||||
import com.youlai.boot.common.validator.FieldValidator;
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
|
||||
|
||||
136
src/main/java/com/youlai/boot/common/aspect/LogAspect.java
Normal file
136
src/main/java/com/youlai/boot/common/aspect/LogAspect.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package com.youlai.boot.common.aspect;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.model.entity.SysLog;
|
||||
import com.youlai.boot.system.service.LogService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日志切面
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LogAspect {
|
||||
|
||||
private final LogService logService;
|
||||
|
||||
/**
|
||||
* 日志注解切点
|
||||
*/
|
||||
@Pointcut("@annotation(logAnnotation)")
|
||||
public void logPointCut(Log logAnnotation) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 环绕通知:记录操作日志
|
||||
*/
|
||||
@Around(value = "logPointCut(logAnnotation)", argNames = "pjp,logAnnotation")
|
||||
public Object around(ProceedingJoinPoint pjp, Log logAnnotation) throws Throwable {
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
Object result = null;
|
||||
Exception exception = null;
|
||||
|
||||
try {
|
||||
result = pjp.proceed();
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
} finally {
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
saveLogAsync(logAnnotation, executionTime, exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步保存日志
|
||||
*/
|
||||
@Async
|
||||
public void saveLogAsync(Log logAnnotation, long executionTime, Exception exception) {
|
||||
try {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
|
||||
// 解析 User-Agent
|
||||
String userAgentStr = request.getHeader("User-Agent");
|
||||
UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
|
||||
|
||||
// 解析 IP 地区
|
||||
String ip = IPUtils.getIpAddr(request);
|
||||
String region = IPUtils.getRegion(ip);
|
||||
String province = null;
|
||||
String city = null;
|
||||
if (StrUtil.isNotBlank(region)) {
|
||||
String[] parts = region.split("\\|");
|
||||
if (parts.length >= 3) {
|
||||
province = StrUtil.blankToDefault(parts[2], null);
|
||||
city = StrUtil.blankToDefault(parts[3], null);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前用户信息
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
String username = SecurityUtils.getUsername();
|
||||
|
||||
// 构建日志实体
|
||||
LogModuleEnum module = logAnnotation.module();
|
||||
ActionTypeEnum actionType = logAnnotation.value();
|
||||
String title = StrUtil.blankToDefault(logAnnotation.title(),
|
||||
module.getLabel() + "-" + actionType.getLabel());
|
||||
String content = logAnnotation.content();
|
||||
|
||||
SysLog logEntity = new SysLog();
|
||||
logEntity.setModule(module);
|
||||
logEntity.setActionType(actionType);
|
||||
logEntity.setTitle(title);
|
||||
logEntity.setContent(content);
|
||||
logEntity.setOperatorId(userId);
|
||||
logEntity.setOperatorName(username);
|
||||
logEntity.setRequestUri(request.getRequestURI());
|
||||
logEntity.setRequestMethod(request.getMethod());
|
||||
logEntity.setIp(ip);
|
||||
logEntity.setProvince(province);
|
||||
logEntity.setCity(city);
|
||||
logEntity.setDevice(userAgent.getOs().getName());
|
||||
logEntity.setOs(userAgent.getOs().getName());
|
||||
logEntity.setBrowser(userAgent.getBrowser().getName());
|
||||
logEntity.setStatus(exception == null ? 1 : 0);
|
||||
logEntity.setErrorMsg(exception != null ? exception.getMessage() : null);
|
||||
logEntity.setExecutionTime((int) executionTime);
|
||||
logEntity.setCreateTime(LocalDateTime.now());
|
||||
|
||||
logService.save(logEntity);
|
||||
} catch (Exception e) {
|
||||
log.error("保存操作日志异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.youlai.boot.core.aspect;
|
||||
package com.youlai.boot.common.aspect;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.core.exception.BusinessException;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.annotation.RepeatSubmit;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 操作类型枚举
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Schema(enumAsRef = true)
|
||||
@Getter
|
||||
public enum ActionTypeEnum implements IBaseEnum<Integer> {
|
||||
|
||||
LOGIN(1, "登录"),
|
||||
LOGOUT(2, "登出"),
|
||||
INSERT(3, "新增"),
|
||||
UPDATE(4, "修改"),
|
||||
DELETE(5, "删除"),
|
||||
GRANT(6, "授权"),
|
||||
EXPORT(7, "导出"),
|
||||
IMPORT(8, "导入"),
|
||||
UPLOAD(9, "上传"),
|
||||
DOWNLOAD(10, "下载"),
|
||||
CHANGE_PASSWORD(11, "修改密码"),
|
||||
RESET_PASSWORD(12, "重置密码"),
|
||||
ENABLE(13, "启用"),
|
||||
DISABLE(14, "禁用"),
|
||||
LIST(15, "查询列表"),
|
||||
OTHER(99, "其他");
|
||||
|
||||
@EnumValue
|
||||
private final Integer value;
|
||||
|
||||
@JsonValue
|
||||
private final String label;
|
||||
|
||||
ActionTypeEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -12,22 +14,39 @@ import lombok.Getter;
|
||||
*/
|
||||
@Schema(enumAsRef = true)
|
||||
@Getter
|
||||
public enum LogModuleEnum {
|
||||
public enum LogModuleEnum implements IBaseEnum<Integer> {
|
||||
|
||||
EXCEPTION("异常"),
|
||||
LOGIN("登录"),
|
||||
USER("用户"),
|
||||
DEPT("部门"),
|
||||
ROLE("角色"),
|
||||
MENU("菜单"),
|
||||
DICT("字典"),
|
||||
SETTING("系统配置"),
|
||||
OTHER("其他");
|
||||
LOGIN(1, "登录"),
|
||||
USER(2, "用户管理"),
|
||||
ROLE(3, "角色管理"),
|
||||
DEPT(4, "部门管理"),
|
||||
MENU(5, "菜单管理"),
|
||||
DICT(6, "字典管理"),
|
||||
CONFIG(7, "系统配置"),
|
||||
FILE(8, "文件管理"),
|
||||
NOTICE(9, "通知公告"),
|
||||
LOG(10, "日志管理"),
|
||||
CODEGEN(11, "代码生成"),
|
||||
OTHER(99, "其他");
|
||||
|
||||
@EnumValue
|
||||
private final Integer value;
|
||||
|
||||
@JsonValue
|
||||
private final String moduleName;
|
||||
private final String label;
|
||||
|
||||
LogModuleEnum(String moduleName) {
|
||||
this.moduleName = moduleName;
|
||||
LogModuleEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RequestMethodEnum {
|
||||
/**
|
||||
* 搜寻 @AnonymousGetMapping
|
||||
*/
|
||||
GET("GET"),
|
||||
|
||||
/**
|
||||
* 搜寻 @AnonymousPostMapping
|
||||
*/
|
||||
POST("POST"),
|
||||
|
||||
/**
|
||||
* 搜寻 @AnonymousPutMapping
|
||||
*/
|
||||
PUT("PUT"),
|
||||
|
||||
/**
|
||||
* 搜寻 @AnonymousPatchMapping
|
||||
*/
|
||||
PATCH("PATCH"),
|
||||
|
||||
/**
|
||||
* 搜寻 @AnonymousDeleteMapping
|
||||
*/
|
||||
DELETE("DELETE"),
|
||||
|
||||
/**
|
||||
* 否则就是所有 Request 接口都放行
|
||||
*/
|
||||
ALL("All");
|
||||
|
||||
/**
|
||||
* Request 类型
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
public static RequestMethodEnum find(String type) {
|
||||
for (RequestMethodEnum value : RequestMethodEnum.values()) {
|
||||
if (value.getType().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return ALL;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.youlai.boot.core.exception;
|
||||
package com.youlai.boot.common.exception;
|
||||
|
||||
import com.youlai.boot.core.web.IResultCode;
|
||||
import com.youlai.boot.common.result.IResultCode;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.helpers.MessageFormatter;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.core.web;
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.core.web;
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
/**
|
||||
* 响应码接口
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.core.web;
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.core.web;
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
@@ -10,7 +10,7 @@ import org.springframework.http.MediaType;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Web响应写入器
|
||||
* 响应写入器
|
||||
* <p>
|
||||
* 用于在过滤器、Security处理器等无法使用 @RestControllerAdvice 的场景中统一写入HTTP响应。
|
||||
* 支持写入成功响应和错误响应。
|
||||
@@ -20,12 +20,12 @@ import java.nio.charset.StandardCharsets;
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
public final class WebResponseWriter {
|
||||
public final class ResponseWriter {
|
||||
|
||||
/**
|
||||
* 私有构造函数,防止实例化
|
||||
*/
|
||||
private WebResponseWriter() {
|
||||
private ResponseWriter() {
|
||||
throw new UnsupportedOperationException("工具类不允许实例化");
|
||||
}
|
||||
|
||||
@@ -116,7 +116,3 @@ public final class WebResponseWriter {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.core.web;
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.core.web;
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
package com.youlai.boot.common.util;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 日期工具类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.4.2
|
||||
*/
|
||||
public class DateUtils {
|
||||
|
||||
/**
|
||||
* 区间日期格式化为数据库日期格式
|
||||
* <p>
|
||||
* eg:2021-01-01 → 2021-01-01 00:00:00
|
||||
*
|
||||
* @param obj 要处理的对象
|
||||
* @param startTimeFieldName 起始时间字段名
|
||||
* @param endTimeFieldName 结束时间字段名
|
||||
*/
|
||||
public static void toDatabaseFormat(Object obj, String startTimeFieldName, String endTimeFieldName) {
|
||||
Field startTimeField = ReflectUtil.getField(obj.getClass(), startTimeFieldName);
|
||||
Field endTimeField = ReflectUtil.getField(obj.getClass(), endTimeFieldName);
|
||||
|
||||
if (startTimeField != null) {
|
||||
processDateTimeField(obj, startTimeField, startTimeFieldName, "yyyy-MM-dd 00:00:00");
|
||||
}
|
||||
|
||||
if (endTimeField != null) {
|
||||
processDateTimeField(obj, endTimeField, endTimeFieldName, "yyyy-MM-dd 23:59:59");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日期字段
|
||||
*
|
||||
* @param obj 要处理的对象
|
||||
* @param field 字段
|
||||
* @param fieldName 字段名
|
||||
* @param targetPattern 目标数据库日期格式
|
||||
*/
|
||||
private static void processDateTimeField(Object obj, Field field, String fieldName, String targetPattern) {
|
||||
Object fieldValue = ReflectUtil.getFieldValue(obj, fieldName);
|
||||
if (fieldValue != null) {
|
||||
// 得到原始的日期格式
|
||||
String pattern = field.isAnnotationPresent(DateTimeFormat.class) ? field.getAnnotation(DateTimeFormat.class).pattern() : "yyyy-MM-dd";
|
||||
// 转换为日期对象
|
||||
DateTime dateTime = DateUtil.parse(StrUtil.toString(fieldValue), pattern);
|
||||
// 转换为目标数据库日期格式
|
||||
ReflectUtil.setFieldValue(obj, fieldName, dateTime.toString(targetPattern));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.core.validator;
|
||||
package com.youlai.boot.common.validator;
|
||||
|
||||
import com.youlai.boot.common.annotation.ValidField;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
@@ -18,16 +18,14 @@ public class FieldValidator implements ConstraintValidator<ValidField, String> {
|
||||
|
||||
@Override
|
||||
public void initialize(ValidField constraintAnnotation) {
|
||||
// 初始化允许的值列表
|
||||
this.allowedValues = constraintAnnotation.allowedValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
if (value == null) {
|
||||
return true; // 如果字段允许为空,可以返回 true
|
||||
return true;
|
||||
}
|
||||
// 检查值是否在允许列表中
|
||||
return Arrays.asList(allowedValues).contains(value);
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
package com.youlai.boot.core.aspect;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.TimeInterval;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.aliyun.oss.HttpMethod;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import com.youlai.boot.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.model.entity.Log;
|
||||
import com.youlai.boot.system.service.LogService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.*;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 日志切面
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2024/6/25
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class LogAspect {
|
||||
private final LogService logService;
|
||||
private final HttpServletRequest request;
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
/**
|
||||
* 切点
|
||||
*/
|
||||
@Pointcut("@annotation(com.youlai.boot.common.annotation.Log)")
|
||||
public void logPointcut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@Around("logPointcut() && @annotation(logAnnotation)")
|
||||
public Object doAround(ProceedingJoinPoint joinPoint, com.youlai.boot.common.annotation.Log logAnnotation) throws Throwable {
|
||||
// 在方法执行前获取用户ID,避免在方法执行过程中清除上下文导致获取不到用户ID
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
|
||||
TimeInterval timer = DateUtil.timer();
|
||||
Object result = null;
|
||||
Exception exception = null;
|
||||
|
||||
try {
|
||||
result = joinPoint.proceed();
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
} finally {
|
||||
long executionTime = timer.interval(); // 执行时长
|
||||
this.saveLog(joinPoint, exception, result, logAnnotation, executionTime, userId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存日志
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param e 异常
|
||||
* @param jsonResult 响应结果
|
||||
* @param logAnnotation 日志注解
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
private void saveLog(final JoinPoint joinPoint, final Exception e, Object jsonResult, com.youlai.boot.common.annotation.Log logAnnotation, long executionTime, Long userId) {
|
||||
String requestURI = request.getRequestURI();
|
||||
// 创建日志记录
|
||||
Log log = new Log();
|
||||
log.setExecutionTime(executionTime);
|
||||
|
||||
// 设置日志模块和内容
|
||||
log.setModule(logAnnotation.module());
|
||||
|
||||
// 异常情况:追加异常信息到日志内容
|
||||
if (e != null) {
|
||||
log.setContent(logAnnotation.value() + "(失败:" + e.getMessage() + ")");
|
||||
// 请求参数(异常时也记录,便于排查问题)
|
||||
this.setRequestParameters(joinPoint, log);
|
||||
// 异常堆栈(截取前 2000 字符,避免过长)
|
||||
String stackTrace = JSONUtil.toJsonStr(e.getStackTrace());
|
||||
log.setResponseContent(StrUtil.sub(stackTrace, 0, 2000));
|
||||
} else {
|
||||
// 正常情况
|
||||
log.setContent(logAnnotation.value());
|
||||
// 请求参数
|
||||
if (logAnnotation.params()) {
|
||||
this.setRequestParameters(joinPoint, log);
|
||||
}
|
||||
// 响应结果
|
||||
if (logAnnotation.result() && jsonResult != null) {
|
||||
log.setResponseContent(JSONUtil.toJsonStr(jsonResult));
|
||||
}
|
||||
}
|
||||
log.setRequestUri(requestURI);
|
||||
log.setCreateBy(userId);
|
||||
String ipAddr = IPUtils.getIpAddr(request);
|
||||
if (StrUtil.isNotBlank(ipAddr)) {
|
||||
log.setIp(ipAddr);
|
||||
String region = IPUtils.getRegion(ipAddr);
|
||||
// 中国|0|四川省|成都市|电信 解析省和市
|
||||
if (StrUtil.isNotBlank(region)) {
|
||||
String[] regionArray = region.split("\\|");
|
||||
if (regionArray.length > 2) {
|
||||
log.setProvince(regionArray[2]);
|
||||
log.setCity(regionArray[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 获取浏览器和终端系统信息
|
||||
String userAgentString = request.getHeader("User-Agent");
|
||||
UserAgent userAgent = resolveUserAgent(userAgentString);
|
||||
if (Objects.nonNull(userAgent)) {
|
||||
// 系统信息
|
||||
log.setOs(userAgent.getOs().getName());
|
||||
// 浏览器信息
|
||||
log.setBrowser(userAgent.getBrowser().getName());
|
||||
log.setBrowserVersion(userAgent.getBrowser().getVersion(userAgentString));
|
||||
}
|
||||
//获取方法名
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
log.setMethod(methodName);
|
||||
// 保存日志到数据库
|
||||
logService.save(log);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求参数到日志对象中
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param log 操作日志
|
||||
*/
|
||||
private void setRequestParameters(JoinPoint joinPoint, Log log) {
|
||||
String requestMethod = request.getMethod();
|
||||
log.setRequestMethod(requestMethod);
|
||||
if (HttpMethod.GET.name().equalsIgnoreCase(requestMethod) || HttpMethod.PUT.name().equalsIgnoreCase(requestMethod) || HttpMethod.POST.name().equalsIgnoreCase(requestMethod)) {
|
||||
String params = convertArgumentsToString(joinPoint.getArgs());
|
||||
log.setRequestParams(StrUtil.sub(params, 0, 65535));
|
||||
} else {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes != null) {
|
||||
Map<?, ?> paramsMap = (Map<?, ?>) attributes.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
log.setRequestParams(StrUtil.sub(paramsMap.toString(), 0, 65535));
|
||||
} else {
|
||||
log.setRequestParams("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将参数数组转换为字符串
|
||||
*
|
||||
* @param paramsArray 参数数组
|
||||
* @return 参数字符串
|
||||
*/
|
||||
private String convertArgumentsToString(Object[] paramsArray) {
|
||||
StringBuilder params = new StringBuilder();
|
||||
if (paramsArray != null) {
|
||||
for (Object param : paramsArray) {
|
||||
if (!shouldFilterObject(param)) {
|
||||
// 如果是基本类型或者枚举类型,直接添加到参数字符串中
|
||||
if(param.getClass().isPrimitive() || param.getClass().isEnum()) {
|
||||
params.append(param).append(" ");
|
||||
} else {
|
||||
params.append(JSONUtil.toJsonStr(param)).append(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要过滤的对象。
|
||||
*
|
||||
* @param obj 对象信息。
|
||||
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||
*/
|
||||
private boolean shouldFilterObject(Object obj) {
|
||||
Class<?> clazz = obj.getClass();
|
||||
if (clazz.isArray()) {
|
||||
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
|
||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||
Collection<?> collection = (Collection<?>) obj;
|
||||
return collection.stream().anyMatch(item -> item instanceof MultipartFile);
|
||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||
Map<?, ?> map = (Map<?, ?>) obj;
|
||||
return map.values().stream().anyMatch(value -> value instanceof MultipartFile);
|
||||
}
|
||||
return obj instanceof MultipartFile || obj instanceof HttpServletRequest || obj instanceof HttpServletResponse;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析UserAgent
|
||||
*
|
||||
* @param userAgentString UserAgent字符串
|
||||
* @return UserAgent
|
||||
*/
|
||||
public UserAgent resolveUserAgent(String userAgentString) {
|
||||
if (StrUtil.isBlank(userAgentString)) {
|
||||
return null;
|
||||
}
|
||||
// 给userAgentStringMD5加密一次防止过长
|
||||
String userAgentStringMD5 = DigestUtil.md5Hex(userAgentString);
|
||||
//判断是否命中缓存
|
||||
UserAgent userAgent = Objects.requireNonNull(cacheManager.getCache("userAgent")).get(userAgentStringMD5, UserAgent.class);
|
||||
if (userAgent != null) {
|
||||
return userAgent;
|
||||
}
|
||||
userAgent = UserAgentUtil.parse(userAgentString);
|
||||
Objects.requireNonNull(cacheManager.getCache("userAgent")).put(userAgentStringMD5, userAgent);
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.cache.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.cache.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.cache.autoconfigure.CacheProperties;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.cache.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.integration.captcha.config;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.youlai.boot.framework.integration.captcha.exception;
|
||||
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 验证码异常
|
||||
*/
|
||||
@Getter
|
||||
public class CaptchaException extends RuntimeException {
|
||||
|
||||
private final ResultCode resultCode;
|
||||
|
||||
public CaptchaException(ResultCode resultCode) {
|
||||
super(resultCode.getMsg());
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
package com.youlai.boot.auth.model.vo;
|
||||
package com.youlai.boot.framework.integration.captcha.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 验证码信息
|
||||
*
|
||||
* @author Ray。Hao
|
||||
* @since 2023/03/24
|
||||
*/
|
||||
@Schema(description = "验证码信息")
|
||||
@Data
|
||||
@Builder
|
||||
public class CaptchaVO {
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "验证码信息")
|
||||
public class CaptchaInfo {
|
||||
|
||||
@Schema(description = "验证码缓存 ID")
|
||||
@Schema(description = "验证码缓存ID")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "验证码图片Base64字符串")
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.youlai.boot.framework.integration.captcha.service;
|
||||
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.enums.CaptchaTypeEnum;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.config.property.CaptchaProperties;
|
||||
import com.youlai.boot.framework.integration.captcha.exception.CaptchaException;
|
||||
import com.youlai.boot.framework.integration.captcha.model.CaptchaInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 验证码服务
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CaptchaService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final CaptchaProperties captchaProperties;
|
||||
private final CodeGenerator codeGenerator;
|
||||
private final Font captchaFont;
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
public CaptchaInfo generate() {
|
||||
String captchaType = captchaProperties.getType();
|
||||
int width = captchaProperties.getWidth();
|
||||
int height = captchaProperties.getHeight();
|
||||
int interfereCount = captchaProperties.getInterfereCount();
|
||||
int codeLength = captchaProperties.getCode().getLength();
|
||||
|
||||
AbstractCaptcha captcha;
|
||||
if (CaptchaTypeEnum.CIRCLE.name().equalsIgnoreCase(captchaType)) {
|
||||
captcha = CaptchaUtil.createCircleCaptcha(width, height, codeLength, interfereCount);
|
||||
} else if (CaptchaTypeEnum.GIF.name().equalsIgnoreCase(captchaType)) {
|
||||
captcha = CaptchaUtil.createGifCaptcha(width, height, codeLength);
|
||||
} else if (CaptchaTypeEnum.LINE.name().equalsIgnoreCase(captchaType)) {
|
||||
captcha = CaptchaUtil.createLineCaptcha(width, height, codeLength, interfereCount);
|
||||
} else if (CaptchaTypeEnum.SHEAR.name().equalsIgnoreCase(captchaType)) {
|
||||
captcha = CaptchaUtil.createShearCaptcha(width, height, codeLength, interfereCount);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid captcha type: " + captchaType);
|
||||
}
|
||||
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.setTextAlpha(captchaProperties.getTextAlpha());
|
||||
captcha.setFont(captchaFont);
|
||||
|
||||
String captchaCode = captcha.getCode();
|
||||
String imageBase64Data = captcha.getImageBase64Data();
|
||||
|
||||
String captchaId = IdUtil.fastSimpleUUID();
|
||||
redisTemplate.opsForValue().set(
|
||||
StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaId),
|
||||
captchaCode,
|
||||
captchaProperties.getExpireSeconds(),
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
return CaptchaInfo.builder()
|
||||
.captchaId(captchaId)
|
||||
.captchaBase64(imageBase64Data)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码,失败抛异常
|
||||
*
|
||||
* @param captchaId 验证码ID
|
||||
* @param captchaCode 用户输入的验证码
|
||||
* @throws CaptchaException 验证码错误或过期
|
||||
*/
|
||||
public void validate(String captchaId, String captchaCode) {
|
||||
if (StrUtil.isBlank(captchaId) || StrUtil.isBlank(captchaCode)) {
|
||||
throw new CaptchaException(ResultCode.USER_VERIFICATION_CODE_ERROR);
|
||||
}
|
||||
|
||||
String cacheKey = StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaId);
|
||||
String cachedCode = (String) redisTemplate.opsForValue().get(cacheKey);
|
||||
if (cachedCode == null) {
|
||||
throw new CaptchaException(ResultCode.USER_VERIFICATION_CODE_EXPIRED);
|
||||
}
|
||||
|
||||
if (!codeGenerator.verify(cachedCode, captchaCode)) {
|
||||
throw new CaptchaException(ResultCode.USER_VERIFICATION_CODE_ERROR);
|
||||
}
|
||||
|
||||
redisTemplate.delete(cacheKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.integration.mail.config;
|
||||
|
||||
import com.youlai.boot.config.property.MailProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.support.mail.service;
|
||||
package com.youlai.boot.framework.integration.mail.service;
|
||||
|
||||
import com.youlai.boot.config.property.MailProperties;
|
||||
import jakarta.mail.MessagingException;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.support.sms.enums;
|
||||
package com.youlai.boot.framework.integration.sms.enums;
|
||||
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.youlai.boot.support.sms.service;
|
||||
package com.youlai.boot.framework.integration.sms.service;
|
||||
|
||||
import com.youlai.boot.support.sms.enums.SmsTypeEnum;
|
||||
import com.youlai.boot.framework.integration.sms.enums.SmsTypeEnum;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.support.sms.service.impl;
|
||||
package com.youlai.boot.framework.integration.sms.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.aliyuncs.CommonRequest;
|
||||
@@ -9,8 +9,8 @@ import com.aliyuncs.exceptions.ClientException;
|
||||
import com.aliyuncs.http.MethodType;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
import com.youlai.boot.config.property.AliyunSmsProperties;
|
||||
import com.youlai.boot.support.sms.enums.SmsTypeEnum;
|
||||
import com.youlai.boot.support.sms.service.SmsService;
|
||||
import com.youlai.boot.framework.integration.sms.enums.SmsTypeEnum;
|
||||
import com.youlai.boot.framework.integration.sms.service.SmsService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.integration.wxma.config;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.job.config;
|
||||
|
||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.persistence.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.youlai.boot.config.MyDataPermissionHandler;
|
||||
import com.youlai.boot.config.MyMetaObjectHandler;
|
||||
import com.youlai.boot.framework.persistence.interceptor.MyDataPermissionHandler;
|
||||
import com.youlai.boot.framework.persistence.handler.MyMetaObjectHandler;
|
||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.persistence.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.persistence.interceptor;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
|
||||
import com.youlai.boot.common.annotation.DataPermission;
|
||||
import com.youlai.boot.common.enums.DataScopeEnum;
|
||||
import com.youlai.boot.security.model.RoleDataScope;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.security.util.SecurityUtils;
|
||||
import com.youlai.boot.shared.enums.DataScopeEnum;
|
||||
import com.youlai.boot.framework.security.model.RoleDataScope;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.security.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -1,18 +1,18 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.security.config;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import com.youlai.boot.framework.integration.captcha.service.CaptchaService;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.youlai.boot.config.property.SecurityProperties;
|
||||
import com.youlai.boot.core.filter.RateLimiterFilter;
|
||||
import com.youlai.boot.security.filter.CaptchaValidationFilter;
|
||||
import com.youlai.boot.security.filter.TokenAuthenticationFilter;
|
||||
import com.youlai.boot.security.handler.MyAccessDeniedHandler;
|
||||
import com.youlai.boot.security.handler.MyAuthenticationEntryPoint;
|
||||
import com.youlai.boot.security.provider.SmsAuthenticationProvider;
|
||||
import com.youlai.boot.security.provider.WechatMiniAuthenticationProvider;
|
||||
import com.youlai.boot.security.token.TokenManager;
|
||||
import com.youlai.boot.security.service.SysUserDetailsService;
|
||||
import com.youlai.boot.framework.web.filter.RateLimiterFilter;
|
||||
import com.youlai.boot.framework.security.filter.CaptchaValidationFilter;
|
||||
import com.youlai.boot.framework.security.filter.TokenAuthenticationFilter;
|
||||
import com.youlai.boot.framework.security.handler.MyAccessDeniedHandler;
|
||||
import com.youlai.boot.framework.security.handler.MyAuthenticationEntryPoint;
|
||||
import com.youlai.boot.framework.security.provider.SmsAuthenticationProvider;
|
||||
import com.youlai.boot.framework.security.provider.WechatMiniAuthenticationProvider;
|
||||
import com.youlai.boot.framework.security.token.TokenManager;
|
||||
import com.youlai.boot.framework.security.service.SysUserDetailsService;
|
||||
import com.youlai.boot.system.service.ConfigService;
|
||||
import com.youlai.boot.system.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -55,7 +55,7 @@ public class SecurityConfig {
|
||||
private final UserService userService;
|
||||
private final SysUserDetailsService userDetailsService;
|
||||
|
||||
private final CodeGenerator codeGenerator;
|
||||
private final CaptchaService captchaService;
|
||||
private final ConfigService configService;
|
||||
private final SecurityProperties securityProperties;
|
||||
|
||||
@@ -97,7 +97,7 @@ public class SecurityConfig {
|
||||
// 限流过滤器
|
||||
.addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class)
|
||||
// 验证码校验过滤器
|
||||
.addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class)
|
||||
.addFilterBefore(new CaptchaValidationFilter(captchaService), UsernamePasswordAuthenticationFilter.class)
|
||||
// 验证和解析过滤器
|
||||
.addFilterBefore(new TokenAuthenticationFilter(tokenManager), UsernamePasswordAuthenticationFilter.class)
|
||||
.build();
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.exception;
|
||||
package com.youlai.boot.framework.security.exception;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.exception;
|
||||
package com.youlai.boot.framework.security.exception;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
package com.youlai.boot.security.filter;
|
||||
package com.youlai.boot.framework.security.filter;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.core.web.WebResponseWriter;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.result.ResponseWriter;
|
||||
import com.youlai.boot.framework.integration.captcha.exception.CaptchaException;
|
||||
import com.youlai.boot.framework.integration.captcha.service.CaptchaService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
||||
@@ -40,12 +39,10 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
|
||||
public static final String CAPTCHA_CODE_PARAM_NAME = "captchaCode";
|
||||
public static final String CAPTCHA_ID_PARAM_NAME = "captchaId";
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final CodeGenerator codeGenerator;
|
||||
private final CaptchaService captchaService;
|
||||
|
||||
public CaptchaValidationFilter(RedisTemplate<String, Object> redisTemplate, CodeGenerator codeGenerator) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.codeGenerator = codeGenerator;
|
||||
public CaptchaValidationFilter(CaptchaService captchaService) {
|
||||
this.captchaService = captchaService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,7 +58,7 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
|
||||
// 仅支持 JSON 登录
|
||||
String contentType = request.getContentType();
|
||||
if (contentType == null || !contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
|
||||
WebResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
|
||||
ResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,24 +76,12 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
|
||||
captchaId = jsonObject.getStr(CAPTCHA_ID_PARAM_NAME);
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(captchaCode) || StrUtil.isBlank(captchaId)) {
|
||||
WebResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
String cacheVerifyCode = (String) redisTemplate.opsForValue().get(
|
||||
StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaId)
|
||||
);
|
||||
if (cacheVerifyCode == null) {
|
||||
WebResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_EXPIRED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (codeGenerator.verify(cacheVerifyCode, captchaCode)) {
|
||||
try {
|
||||
captchaService.validate(captchaId, captchaCode);
|
||||
HttpServletRequest repeatableRequest = new RepeatableReadRequestWrapper(requestWrapper, bodyBytes);
|
||||
chain.doFilter(repeatableRequest, response);
|
||||
} else {
|
||||
WebResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
|
||||
} catch (CaptchaException e) {
|
||||
ResponseWriter.writeError(response, e.getResultCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.youlai.boot.security.filter;
|
||||
package com.youlai.boot.framework.security.filter;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.core.web.WebResponseWriter;
|
||||
import com.youlai.boot.security.token.TokenManager;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.result.ResponseWriter;
|
||||
import com.youlai.boot.framework.security.token.TokenManager;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -47,7 +47,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
// 执行令牌有效性检查(包含密码学验签和过期时间验证)
|
||||
boolean isValidToken = tokenManager.validateToken(rawToken);
|
||||
if (!isValidToken) {
|
||||
WebResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
ResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
} catch (Exception ex) {
|
||||
// 安全上下文清除保障(防止上下文残留)
|
||||
SecurityContextHolder.clearContext();
|
||||
WebResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
ResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.boot.security.handler;
|
||||
package com.youlai.boot.framework.security.handler;
|
||||
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.core.web.WebResponseWriter;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.result.ResponseWriter;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
|
||||
@@ -18,7 +18,7 @@ public class MyAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
|
||||
WebResponseWriter.writeError(response, ResultCode.ACCESS_UNAUTHORIZED);
|
||||
ResponseWriter.writeError(response, ResultCode.ACCESS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.boot.security.handler;
|
||||
package com.youlai.boot.framework.security.handler;
|
||||
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.core.web.WebResponseWriter;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.result.ResponseWriter;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -32,13 +32,13 @@ public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||
if (authException instanceof BadCredentialsException) {
|
||||
// 用户名或密码错误
|
||||
WebResponseWriter.writeError(response, ResultCode.USER_PASSWORD_ERROR);
|
||||
ResponseWriter.writeError(response, ResultCode.USER_PASSWORD_ERROR);
|
||||
} else if(authException instanceof InsufficientAuthenticationException){
|
||||
// 请求头缺失Authorization、Token格式错误、Token过期、签名验证失败
|
||||
WebResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
ResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
} else {
|
||||
// 其他未明确处理的认证异常(如账户被锁定、账户禁用等)
|
||||
WebResponseWriter.writeError(response, ResultCode.USER_LOGIN_EXCEPTION, authException.getMessage());
|
||||
ResponseWriter.writeError(response, ResultCode.USER_LOGIN_EXCEPTION, authException.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.model;
|
||||
package com.youlai.boot.framework.security.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.model;
|
||||
package com.youlai.boot.framework.security.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.model;
|
||||
package com.youlai.boot.framework.security.model;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.model;
|
||||
package com.youlai.boot.framework.security.model;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.model;
|
||||
package com.youlai.boot.framework.security.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.model;
|
||||
package com.youlai.boot.framework.security.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.model;
|
||||
package com.youlai.boot.framework.security.model;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.youlai.boot.security.provider;
|
||||
package com.youlai.boot.framework.security.provider;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.security.exception.CaptchaValidationException;
|
||||
import com.youlai.boot.security.model.SmsAuthenticationToken;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.security.model.UserAuthInfo;
|
||||
import com.youlai.boot.framework.security.exception.CaptchaValidationException;
|
||||
import com.youlai.boot.framework.security.model.SmsAuthenticationToken;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import com.youlai.boot.framework.security.model.UserAuthInfo;
|
||||
import com.youlai.boot.system.service.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.youlai.boot.security.provider;
|
||||
package com.youlai.boot.framework.security.provider;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.youlai.boot.security.exception.NeedBindMobileException;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.security.model.UserAuthInfo;
|
||||
import com.youlai.boot.security.model.WechatMiniAuthenticationToken;
|
||||
import com.youlai.boot.security.service.SysUserDetailsService;
|
||||
import com.youlai.boot.framework.security.exception.NeedBindMobileException;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import com.youlai.boot.framework.security.model.UserAuthInfo;
|
||||
import com.youlai.boot.framework.security.model.WechatMiniAuthenticationToken;
|
||||
import com.youlai.boot.framework.security.service.SysUserDetailsService;
|
||||
import com.youlai.boot.system.model.entity.UserSocial;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.youlai.boot.security.service;
|
||||
package com.youlai.boot.framework.security.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.security.util.SecurityUtils;
|
||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.service.RoleMenuService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.boot.security.service;
|
||||
package com.youlai.boot.framework.security.service;
|
||||
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.security.model.UserAuthInfo;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import com.youlai.boot.framework.security.model.UserAuthInfo;
|
||||
import com.youlai.boot.system.enums.SocialPlatformEnum;
|
||||
import com.youlai.boot.system.model.entity.UserSocial;
|
||||
import com.youlai.boot.system.service.UserSocialService;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.security.token;
|
||||
package com.youlai.boot.framework.security.token;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
@@ -12,13 +12,13 @@ import cn.hutool.jwt.JWTUtil;
|
||||
import com.youlai.boot.common.constant.JwtClaimConstants;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.core.exception.BusinessException;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.config.property.SecurityProperties;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.security.model.RoleDataScope;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.framework.security.model.RoleDataScope;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@@ -1,16 +1,16 @@
|
||||
package com.youlai.boot.security.token;
|
||||
package com.youlai.boot.framework.security.token;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.core.exception.BusinessException;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.config.property.SecurityProperties;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.security.model.UserSession;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.framework.security.model.UserSession;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.boot.security.token;
|
||||
package com.youlai.boot.framework.security.token;
|
||||
|
||||
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.framework.security.model.AuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.youlai.boot.security.util;
|
||||
package com.youlai.boot.framework.security.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.security.model.RoleDataScope;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.shared.constant.SystemConstants;
|
||||
import com.youlai.boot.framework.security.model.RoleDataScope;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.youlai.boot.core.exception;
|
||||
package com.youlai.boot.framework.web.advice;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.web.config;
|
||||
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.framework.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.youlai.boot.core.filter;
|
||||
package com.youlai.boot.framework.web.filter;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.shared.constant.SystemConstants;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import com.youlai.boot.core.web.WebResponseWriter;
|
||||
import com.youlai.boot.common.result.ResponseWriter;
|
||||
import com.youlai.boot.system.service.ConfigService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
@@ -88,7 +88,7 @@ public class RateLimiterFilter extends OncePerRequestFilter {
|
||||
// 判断是否限流
|
||||
if (rateLimit(ip)) {
|
||||
// 返回限流错误信息
|
||||
WebResponseWriter.writeError(response, ResultCode.REQUEST_CONCURRENCY_LIMIT_EXCEEDED);
|
||||
ResponseWriter.writeError(response, ResultCode.REQUEST_CONCURRENCY_LIMIT_EXCEEDED);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.core.filter;
|
||||
package com.youlai.boot.framework.web.filter;
|
||||
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.youlai.boot.interfaces.openapi;
|
||||
|
||||
/**
|
||||
* 开放 API 控制器
|
||||
* <p>
|
||||
* 面向第三方系统的开放接口,使用 API Key 认证
|
||||
* </p>
|
||||
*/
|
||||
// @RestController
|
||||
// @RequestMapping("/api/v1/open")
|
||||
// @Api(tags = "开放API")
|
||||
public class OpenApiController {
|
||||
|
||||
// TODO: 实现 API Key 认证拦截器
|
||||
// TODO: 定义开放接口规范
|
||||
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.youlai.boot.support.sse;
|
||||
package com.youlai.boot.interfaces.sse.controller;
|
||||
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.security.util.SecurityUtils;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.framework.security.model.SysUserDetails;
|
||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||
import com.youlai.boot.interfaces.sse.service.SseService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.support.sse.dto;
|
||||
package com.youlai.boot.interfaces.sse.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.support.sse.dto;
|
||||
package com.youlai.boot.interfaces.sse.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.youlai.boot.support.sse;
|
||||
package com.youlai.boot.interfaces.sse.job;
|
||||
|
||||
import com.youlai.boot.interfaces.sse.registry.SseSessionRegistry;
|
||||
import com.youlai.boot.interfaces.sse.service.SseService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.youlai.boot.support.sse;
|
||||
package com.youlai.boot.interfaces.sse.registry;
|
||||
|
||||
import com.youlai.boot.support.sse.dto.OnlineUserDTO;
|
||||
import com.youlai.boot.interfaces.sse.dto.OnlineUserDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.youlai.boot.support.sse;
|
||||
package com.youlai.boot.interfaces.sse.service;
|
||||
|
||||
import com.youlai.boot.support.sse.dto.DictChangeEvent;
|
||||
import com.youlai.boot.support.sse.dto.OnlineUserDTO;
|
||||
import com.youlai.boot.interfaces.sse.dto.DictChangeEvent;
|
||||
import com.youlai.boot.interfaces.sse.dto.OnlineUserDTO;
|
||||
import com.youlai.boot.interfaces.sse.registry.SseSessionRegistry;
|
||||
import com.youlai.boot.interfaces.sse.topic.SseTopics;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.support.sse;
|
||||
package com.youlai.boot.interfaces.sse.topic;
|
||||
|
||||
/**
|
||||
* SSE 主题常量
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.youlai.boot.module.codegen.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.boot.core.web.PageResult;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.common.result.PageResult;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.config.property.CodegenProperties;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.module.codegen.service.CodegenService;
|
||||
import com.youlai.boot.module.codegen.model.form.GenConfigForm;
|
||||
@@ -45,7 +46,6 @@ public class CodegenController {
|
||||
|
||||
@Operation(summary = "获取数据表分页列表")
|
||||
@GetMapping("/table")
|
||||
@Log(value = "代码生成分页列表", module = LogModuleEnum.OTHER)
|
||||
public PageResult<TablePageVO> getTablePage(
|
||||
TableQuery queryParams
|
||||
) {
|
||||
@@ -64,7 +64,7 @@ public class CodegenController {
|
||||
|
||||
@Operation(summary = "保存代码生成配置")
|
||||
@PostMapping("/{tableName}/config")
|
||||
@Log(value = "生成代码", module = LogModuleEnum.OTHER)
|
||||
@Log(module = LogModuleEnum.CODEGEN, value = ActionTypeEnum.UPDATE)
|
||||
public Result<?> saveGenConfig(@RequestBody GenConfigForm formData) {
|
||||
genTableService.saveGenConfig(formData);
|
||||
return Result.success();
|
||||
@@ -81,7 +81,6 @@ public class CodegenController {
|
||||
|
||||
@Operation(summary = "获取预览生成代码")
|
||||
@GetMapping("/{tableName}/preview")
|
||||
@Log(value = "预览生成代码", module = LogModuleEnum.OTHER)
|
||||
public Result<List<CodegenPreviewVO>> getTablePreviewData(@PathVariable String tableName,
|
||||
@RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType,
|
||||
@RequestParam(value = "type", required = false, defaultValue = "ts") String type) {
|
||||
@@ -91,7 +90,7 @@ public class CodegenController {
|
||||
|
||||
@Operation(summary = "下载代码")
|
||||
@GetMapping("/{tableName}/download")
|
||||
@Log(value = "下载代码", module = LogModuleEnum.OTHER)
|
||||
@Log(module = LogModuleEnum.CODEGEN, value = ActionTypeEnum.DOWNLOAD)
|
||||
public void downloadZip(HttpServletResponse response, @PathVariable String tableName,
|
||||
@RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType,
|
||||
@RequestParam(value = "type", required = false, defaultValue = "ts") String type) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.youlai.boot.config.property.CodegenProperties;
|
||||
import com.youlai.boot.module.codegen.service.GenTableService;
|
||||
import com.youlai.boot.module.codegen.service.GenTableColumnService;
|
||||
import com.youlai.boot.module.codegen.service.CodegenService;
|
||||
import com.youlai.boot.core.exception.BusinessException;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.module.codegen.mapper.DatabaseMapper;
|
||||
import com.youlai.boot.module.codegen.model.entity.GenTable;
|
||||
import com.youlai.boot.module.codegen.model.entity.GenTableColumn;
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.youlai.boot.common.enums.EnvEnum;
|
||||
import com.youlai.boot.module.codegen.enums.FormTypeEnum;
|
||||
import com.youlai.boot.module.codegen.enums.JavaTypeEnum;
|
||||
import com.youlai.boot.module.codegen.enums.QueryTypeEnum;
|
||||
import com.youlai.boot.core.exception.BusinessException;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.config.property.CodegenProperties;
|
||||
import com.youlai.boot.module.codegen.converter.CodegenConverter;
|
||||
import com.youlai.boot.module.codegen.mapper.DatabaseMapper;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.youlai.boot.module.file.controller;
|
||||
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.module.file.service.FileService;
|
||||
import com.youlai.boot.module.file.model.FileInfo;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
||||
@@ -5,8 +5,8 @@ import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.core.exception.BusinessException;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.module.file.model.FileInfo;
|
||||
import com.youlai.boot.module.file.service.FileService;
|
||||
import io.minio.*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.common.constant;
|
||||
package com.youlai.boot.shared.constant;
|
||||
|
||||
/**
|
||||
* 系统常量
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.common.model;
|
||||
package com.youlai.boot.shared.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.common.model;
|
||||
package com.youlai.boot.shared.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
package com.youlai.boot.shared.enums;
|
||||
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.youlai.boot.shared.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 日志模块枚举
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Schema(enumAsRef = true)
|
||||
@Getter
|
||||
public enum LogModuleEnum {
|
||||
|
||||
EXCEPTION("异常"),
|
||||
LOGIN("登录"),
|
||||
USER("用户"),
|
||||
DEPT("部门"),
|
||||
ROLE("角色"),
|
||||
MENU("菜单"),
|
||||
DICT("字典"),
|
||||
SETTING("系统配置"),
|
||||
OTHER("其他");
|
||||
|
||||
@JsonValue
|
||||
private final String moduleName;
|
||||
|
||||
LogModuleEnum(String moduleName) {
|
||||
this.moduleName = moduleName;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
package com.youlai.boot.shared.enums;
|
||||
|
||||
import com.youlai.boot.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.youlai.boot.system.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.core.web.PageResult;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.common.result.PageResult;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.system.model.form.ConfigForm;
|
||||
import com.youlai.boot.system.model.query.ConfigQuery;
|
||||
@@ -37,7 +38,7 @@ public class ConfigController {
|
||||
@Operation(summary = "系统配置分页列表")
|
||||
@GetMapping
|
||||
@PreAuthorize("@ss.hasPerm('sys:config:list')")
|
||||
@Log( value = "系统配置分页列表",module = LogModuleEnum.SETTING)
|
||||
@Log(module = LogModuleEnum.CONFIG, value = ActionTypeEnum.LIST)
|
||||
public PageResult<ConfigVO> page(@ParameterObject ConfigQuery queryParams) {
|
||||
IPage<ConfigVO> result = configService.page(queryParams);
|
||||
return PageResult.success(result);
|
||||
@@ -46,7 +47,7 @@ public class ConfigController {
|
||||
@Operation(summary = "新增系统配置")
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPerm('sys:config:create')")
|
||||
@Log( value = "新增系统配置",module = LogModuleEnum.SETTING)
|
||||
@Log(module = LogModuleEnum.CONFIG, value = ActionTypeEnum.INSERT)
|
||||
public Result<?> save(@RequestBody @Valid ConfigForm configForm) {
|
||||
return Result.judge(configService.save(configForm));
|
||||
}
|
||||
@@ -63,7 +64,7 @@ public class ConfigController {
|
||||
@Operation(summary = "刷新系统配置缓存")
|
||||
@PutMapping("/refresh")
|
||||
@PreAuthorize("@ss.hasPerm('sys:config:refresh')")
|
||||
@Log( value = "刷新系统配置缓存",module = LogModuleEnum.SETTING)
|
||||
@Log(module = LogModuleEnum.CONFIG, value = ActionTypeEnum.UPDATE)
|
||||
public Result<ConfigForm> refreshCache() {
|
||||
return Result.judge(configService.refreshCache());
|
||||
}
|
||||
@@ -71,7 +72,7 @@ public class ConfigController {
|
||||
@Operation(summary = "修改系统配置")
|
||||
@PutMapping(value = "/{id}")
|
||||
@PreAuthorize("@ss.hasPerm('sys:config:update')")
|
||||
@Log( value = "修改系统配置",module = LogModuleEnum.SETTING)
|
||||
@Log(module = LogModuleEnum.CONFIG, value = ActionTypeEnum.UPDATE)
|
||||
public Result<?> update(@Valid @PathVariable Long id, @RequestBody ConfigForm configForm) {
|
||||
return Result.judge(configService.edit(id, configForm));
|
||||
}
|
||||
@@ -79,7 +80,7 @@ public class ConfigController {
|
||||
@Operation(summary = "删除系统配置")
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPerm('sys:config:delete')")
|
||||
@Log( value = "删除系统配置",module = LogModuleEnum.SETTING)
|
||||
@Log(module = LogModuleEnum.CONFIG, value = ActionTypeEnum.DELETE)
|
||||
public Result<?> delete(@PathVariable Long id) {
|
||||
return Result.judge(configService.delete(id));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.youlai.boot.system.controller;
|
||||
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.common.annotation.RepeatSubmit;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.shared.dto.Option;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.system.model.form.DeptForm;
|
||||
import com.youlai.boot.system.model.query.DeptQuery;
|
||||
import com.youlai.boot.system.model.vo.DeptVO;
|
||||
@@ -35,7 +36,7 @@ public class DeptController {
|
||||
|
||||
@Operation(summary = "部门列表")
|
||||
@GetMapping
|
||||
@Log( value = "部门列表",module = LogModuleEnum.DEPT)
|
||||
@Log(module = LogModuleEnum.DEPT, value = ActionTypeEnum.LIST)
|
||||
public Result<List<DeptVO>> getDeptList(
|
||||
DeptQuery queryParams
|
||||
) {
|
||||
@@ -54,6 +55,7 @@ public class DeptController {
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPerm('sys:dept:create')")
|
||||
@RepeatSubmit
|
||||
@Log(module = LogModuleEnum.DEPT, value = ActionTypeEnum.INSERT)
|
||||
public Result<?> saveDept(
|
||||
@Valid @RequestBody DeptForm formData
|
||||
) {
|
||||
@@ -73,6 +75,7 @@ public class DeptController {
|
||||
@Operation(summary = "修改部门")
|
||||
@PutMapping(value = "/{deptId}")
|
||||
@PreAuthorize("@ss.hasPerm('sys:dept:update')")
|
||||
@Log(module = LogModuleEnum.DEPT, value = ActionTypeEnum.UPDATE)
|
||||
public Result<?> updateDept(
|
||||
@PathVariable Long deptId,
|
||||
@Valid @RequestBody DeptForm formData
|
||||
@@ -84,6 +87,7 @@ public class DeptController {
|
||||
@Operation(summary = "删除部门")
|
||||
@DeleteMapping("/{ids}")
|
||||
@PreAuthorize("@ss.hasPerm('sys:dept:delete')")
|
||||
@Log(module = LogModuleEnum.DEPT, value = ActionTypeEnum.DELETE)
|
||||
public Result<?> deleteDepartments(
|
||||
@Parameter(description ="部门ID,多个以英文逗号(,)分割") @PathVariable("ids") String ids
|
||||
) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.youlai.boot.system.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.core.web.PageResult;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.shared.dto.Option;
|
||||
import com.youlai.boot.common.result.PageResult;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.system.model.form.DictItemForm;
|
||||
import com.youlai.boot.system.model.query.DictItemQuery;
|
||||
@@ -16,7 +17,7 @@ import com.youlai.boot.system.model.form.DictForm;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.system.service.DictItemService;
|
||||
import com.youlai.boot.system.service.DictService;
|
||||
import com.youlai.boot.support.sse.SseService;
|
||||
import com.youlai.boot.interfaces.sse.service.SseService;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -49,7 +50,7 @@ public class DictController {
|
||||
//---------------------------------------------------
|
||||
@Operation(summary = "字典分页列表")
|
||||
@GetMapping
|
||||
@Log( value = "字典分页列表",module = LogModuleEnum.DICT)
|
||||
@Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.LIST)
|
||||
public PageResult<DictPageVO> getDictPage(
|
||||
DictQuery queryParams
|
||||
) {
|
||||
@@ -78,6 +79,7 @@ public class DictController {
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPerm('sys:dict:create')")
|
||||
@RepeatSubmit
|
||||
@Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.INSERT)
|
||||
public Result<?> saveDict(@Valid @RequestBody DictForm formData) {
|
||||
boolean result = dictService.saveDict(formData);
|
||||
// 发送字典更新通知
|
||||
@@ -90,6 +92,7 @@ public class DictController {
|
||||
@Operation(summary = "修改字典")
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPerm('sys:dict:update')")
|
||||
@Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.UPDATE)
|
||||
public Result<?> updateDict(
|
||||
@PathVariable Long id,
|
||||
@RequestBody DictForm dictForm
|
||||
@@ -105,6 +108,7 @@ public class DictController {
|
||||
@Operation(summary = "删除字典")
|
||||
@DeleteMapping("/{ids}")
|
||||
@PreAuthorize("@ss.hasPerm('sys:dict:delete')")
|
||||
@Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.DELETE)
|
||||
public Result<?> deleteDictionaries(
|
||||
@Parameter(description = "字典ID,多个以英文逗号(,)拼接") @PathVariable String ids
|
||||
) {
|
||||
@@ -149,6 +153,7 @@ public class DictController {
|
||||
@PostMapping("/{dictCode}/items")
|
||||
@PreAuthorize("@ss.hasPerm('sys:dict-item:create')")
|
||||
@RepeatSubmit
|
||||
@Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.INSERT)
|
||||
public Result<Void> saveDictItem(
|
||||
@PathVariable String dictCode,
|
||||
@Valid @RequestBody DictItemForm formData
|
||||
@@ -178,6 +183,7 @@ public class DictController {
|
||||
@PutMapping("/{dictCode}/items/{itemId}")
|
||||
@PreAuthorize("@ss.hasPerm('sys:dict-item:update')")
|
||||
@RepeatSubmit
|
||||
@Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.UPDATE)
|
||||
public Result<?> updateDictItem(
|
||||
@PathVariable String dictCode,
|
||||
@PathVariable Long itemId,
|
||||
@@ -198,6 +204,7 @@ public class DictController {
|
||||
@Operation(summary = "删除字典项")
|
||||
@DeleteMapping("/{dictCode}/items/{itemIds}")
|
||||
@PreAuthorize("@ss.hasPerm('sys:dict-item:delete')")
|
||||
@Log(module = LogModuleEnum.DICT, value = ActionTypeEnum.DELETE)
|
||||
public Result<Void> deleteDictItems(
|
||||
@PathVariable String dictCode,
|
||||
@Parameter(description = "字典ID,多个以英文逗号(,)拼接") @PathVariable String itemIds
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
package com.youlai.boot.system.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.boot.core.web.PageResult;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.common.result.PageResult;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.system.model.query.LogQuery;
|
||||
import com.youlai.boot.system.model.vo.LogPageVO;
|
||||
import com.youlai.boot.system.model.vo.VisitOverviewVO;
|
||||
import com.youlai.boot.system.model.vo.VisitTrendVO;
|
||||
import com.youlai.boot.system.service.LogService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 日志控制层
|
||||
*
|
||||
@@ -26,6 +35,7 @@ public class LogController {
|
||||
|
||||
@Operation(summary = "日志分页列表")
|
||||
@GetMapping
|
||||
@Log(module = LogModuleEnum.LOG, value = ActionTypeEnum.LIST)
|
||||
public PageResult<LogPageVO> getLogPage(
|
||||
LogQuery queryParams
|
||||
) {
|
||||
@@ -33,4 +43,23 @@ public class LogController {
|
||||
return PageResult.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "访问趋势统计")
|
||||
@GetMapping("/views/trend")
|
||||
public Result<VisitTrendVO> getVisitTrend(
|
||||
@Parameter(description = "开始时间", example = "2024-01-01") @RequestParam String startDate,
|
||||
@Parameter(description = "结束时间", example = "2024-12-31") @RequestParam String endDate
|
||||
) {
|
||||
LocalDate start = LocalDate.parse(startDate);
|
||||
LocalDate end = LocalDate.parse(endDate);
|
||||
VisitTrendVO data = logService.getVisitTrend(start, end);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@Operation(summary = "访问统计概览")
|
||||
@GetMapping("/views")
|
||||
public Result<VisitOverviewVO> getVisitOverview() {
|
||||
VisitOverviewVO result = logService.getVisitStats();
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user