├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── top
│ │ └── qtcc
│ │ └── qiutuanallpowerfulspringboot
│ │ ├── QiutuanAllPowerfulSpringbootApplication.java
│ │ ├── annotation
│ │ ├── AuthCheck.java
│ │ ├── RateLimit.java
│ │ └── RepeatSubmit.java
│ │ ├── aop
│ │ ├── AuthInterceptor.java
│ │ └── LogInterceptor.java
│ │ ├── aspect
│ │ ├── RateLimitAspect.java
│ │ ├── RepeatSubmitAspect.java
│ │ └── RequestLogAspect.java
│ │ ├── common
│ │ ├── BaseResponse.java
│ │ ├── DeleteRequest.java
│ │ ├── PageRequest.java
│ │ ├── ResultUtils.java
│ │ └── websocket
│ │ │ ├── MessageWrapper.java
│ │ │ └── WebSocketServer.java
│ │ ├── config
│ │ ├── CorsConfig.java
│ │ ├── CosClientConfig.java
│ │ ├── EncryptConfig.java
│ │ ├── JsonConfig.java
│ │ ├── MinioClientConfig.java
│ │ ├── MyBatisPlusConfig.java
│ │ ├── RedisConfig.java
│ │ ├── ScheduleConfig.java
│ │ ├── ThreadPoolConfig.java
│ │ └── WebSocketConfig.java
│ │ ├── constant
│ │ ├── CommonConstant.java
│ │ ├── FileConstant.java
│ │ ├── HttpStatus.java
│ │ └── UserConstant.java
│ │ ├── controller
│ │ ├── FileController.java
│ │ └── UserController.java
│ │ ├── domain
│ │ ├── dto
│ │ │ ├── file
│ │ │ │ └── UploadFileRequest.java
│ │ │ └── user
│ │ │ │ ├── UserAddRequest.java
│ │ │ │ ├── UserLoginRequest.java
│ │ │ │ ├── UserQueryRequest.java
│ │ │ │ ├── UserRegisterRequest.java
│ │ │ │ ├── UserUpdateMyRequest.java
│ │ │ │ └── UserUpdateRequest.java
│ │ ├── entity
│ │ │ ├── RequestLog.java
│ │ │ └── User.java
│ │ ├── enums
│ │ │ ├── ErrorCode.java
│ │ │ ├── FileUploadBizEnum.java
│ │ │ └── UserRoleEnum.java
│ │ └── vo
│ │ │ └── user
│ │ │ ├── LoginUserVO.java
│ │ │ └── UserVO.java
│ │ ├── exception
│ │ ├── BusinessException.java
│ │ ├── GlobalExceptionHandler.java
│ │ └── ThrowUtils.java
│ │ ├── filter
│ │ ├── TraceIdFilter.java
│ │ ├── XssFilter.java
│ │ └── XssHttpServletRequestWrapper.java
│ │ ├── interceptor
│ │ └── EncryptInterceptor.java
│ │ ├── manager
│ │ └── file
│ │ │ ├── CosManager.java
│ │ │ ├── FileManager.java
│ │ │ ├── MinioManager.java
│ │ │ └── proxy
│ │ │ └── FileManagerProxy.java
│ │ ├── mapper
│ │ ├── RequestLogMapper.java
│ │ └── UserMapper.java
│ │ ├── service
│ │ ├── RequestLogService.java
│ │ └── impl
│ │ │ └── RequestLogServiceImpl.java
│ │ ├── servicec
│ │ ├── UserService.java
│ │ └── impl
│ │ │ └── UserServiceImpl.java
│ │ └── utils
│ │ ├── DateUtils.java
│ │ ├── EncryptUtils.java
│ │ ├── FileUtils.java
│ │ ├── NetUtils.java
│ │ ├── SpringContextUtils.java
│ │ ├── SqlUtils.java
│ │ └── StringUtils.java
└── resources
│ ├── application.yml
│ ├── banner.txt
│ ├── logback-spring.xml
│ └── sql
│ └── springboot_init.sql
└── test
└── java
└── top
└── qtcc
└── qiutuanallpowerfulspringboot
└── QiutuanAllPowerfulSpringbootApplicationTests.java
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 球团
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # SpringBoot脚手架
4 |
5 | ## 项目介绍
6 |
7 | 这是一个功能齐全的SpringBoot脚手架项目,集成了常用的开发组件和功能模块,帮助开发者快速搭建企业级Java应用。
8 |
9 | ## 主要特性
10 |
11 | ### 1. 权限管理
12 |
13 | - 基于注解的权限控制 (`@AuthCheck`)
14 | - 灵活的角色权限校验机制
15 | - 用户登录状态管理
16 |
17 | ### 2. 接口保护
18 |
19 | - 接口限流控制 (`@RateLimit`)
20 | - 防重复提交保护 (`@RepeatSubmit`)
21 | - 请求响应日志记录
22 |
23 | ### 3. 文件存储
24 |
25 | - 支持腾讯云COS对象存储
26 | - 支持MinIO对象存储
27 | - 灵活的存储配置
28 |
29 | ### 4. 实时通信
30 |
31 | - WebSocket服务支持
32 | - 心跳检测机制
33 | - 消息确认机制
34 |
35 | ### 5. 缓存支持
36 |
37 | - Redis缓存集成
38 | - 统一的缓存管理配置
39 | - 分布式锁支持
40 |
41 | ### 6. 数据库支持
42 |
43 | - MyBatis-Plus集成
44 | - 分页插件
45 | - 数据库连接池配置
46 |
47 | ### 7. 通用功能
48 |
49 | - 统一响应处理
50 | - 全局跨域配置
51 | - 线程池管理
52 | - 统一异常处理
53 |
54 | ## 技术栈
55 |
56 | - SpringBoot
57 | - MyBatis-Plus
58 | - Redis
59 | - WebSocket
60 | - MinIO
61 | - 腾讯云COS
62 | - AOP
63 |
64 | ## 快速开始
65 |
66 | ### 环境要求
67 |
68 | - JDK 17+
69 | - Maven 3.6+
70 | - Redis
71 | - MySQL
72 |
73 | ### 配置说明
74 |
75 | 1. 数据库配置
76 | 2. Redis配置
77 | 3. 文件存储配置(COS/MinIO)
78 | 4. 线程池配置
79 |
80 | ### 常用注解
81 |
82 | ```java
83 | @AuthCheck // 权限校验
84 |
85 | @RateLimit // 接口限流
86 |
87 | @RepeatSubmit // 防重复提交
88 | ```
89 |
90 | ## 项目结构
91 |
92 | ```
93 | ├── annotation // 自定义注解
94 | ├── aop // AOP切面
95 | ├── aspect // 切面实现
96 | ├── common // 通用类
97 | ├── config // 配置类
98 | ├── constant // 常量定义
99 | ├── controller // 控制器
100 | ├── service // 服务层
101 | └── util // 工具类
102 | ```
103 |
104 | ## 特别说明
105 |
106 | 本项目提供了一套完整的企业级应用开发框架,开发者可以基于此快速构建自己的应用。项目集成了主流的技术组件,并提供了丰富的功能特性。
107 |
108 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | top.qtcc
6 | qiutuan-all-powerful-springboot
7 | 0.0.1-SNAPSHOT
8 | qiutuan-all-powerful-springboot
9 | qiutuan-all-powerful-springboot
10 |
11 | 17
12 | UTF-8
13 | UTF-8
14 | 2.7.6
15 |
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-jdbc
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-web
24 |
25 |
26 | org.springframework.boot
27 | spring-boot-starter-aop
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-websocket
32 |
33 |
34 | com.baomidou
35 | mybatis-plus-boot-starter
36 | 3.5.9
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-starter-validation
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-data-redis
45 |
46 |
47 |
48 | com.baomidou
49 | mybatis-plus-jsqlparser
50 |
51 |
52 |
53 | mysql
54 | mysql-connector-java
55 |
56 |
57 |
58 |
59 | com.qcloud
60 | cos_api
61 | 5.6.89
62 |
63 |
64 |
65 |
66 | org.jetbrains
67 | annotations
68 | 26.0.1
69 |
70 |
71 |
72 |
73 | io.minio
74 | minio
75 | 8.5.13
76 |
77 |
78 |
79 | org.apache.commons
80 | commons-lang3
81 |
82 |
83 |
84 | io.jsonwebtoken
85 | jjwt
86 | 0.9.1
87 |
88 |
89 | com.github.xiaoymin
90 | knife4j-openapi2-spring-boot-starter
91 | 4.4.0
92 |
93 |
94 |
95 | org.projectlombok
96 | lombok
97 | true
98 |
99 |
100 | org.springframework.boot
101 | spring-boot-starter-test
102 | test
103 |
104 |
105 |
106 | junit
107 | junit
108 | test
109 |
110 |
111 |
112 | cn.hutool
113 | hutool-all
114 | 5.8.32
115 |
116 |
117 |
118 |
119 | redis.clients
120 | jedis
121 | 3.7.0
122 |
123 |
124 |
125 |
126 | org.ansj
127 | ansj_seg
128 | 5.1.6
129 |
130 |
131 |
132 | org.apache.commons
133 | commons-text
134 | 1.1
135 |
136 |
137 |
138 |
139 |
140 | org.springframework.boot
141 | spring-boot-dependencies
142 | ${spring-boot.version}
143 | pom
144 | import
145 |
146 |
147 | com.baomidou
148 | mybatis-plus-bom
149 | 3.5.9
150 | pom
151 | import
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | org.apache.maven.plugins
160 | maven-compiler-plugin
161 | 3.8.1
162 |
163 | 17
164 | 17
165 | UTF-8
166 |
167 |
168 |
169 | org.springframework.boot
170 | spring-boot-maven-plugin
171 | ${spring-boot.version}
172 |
173 |
174 | repackage
175 |
176 | repackage
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/QiutuanAllPowerfulSpringbootApplication.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot;
2 |
3 | import org.mybatis.spring.annotation.MapperScan;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 |
7 | /**
8 | * 启动类
9 | *
10 | * @author qiutuan
11 | * @date 2024/12/10
12 | */
13 | @SpringBootApplication
14 | @MapperScan("top.qtcc.qiutuanallpowerfulspringboot.mapper")
15 | public class QiutuanAllPowerfulSpringbootApplication {
16 |
17 | public static void main(String[] args) {
18 | SpringApplication.run(QiutuanAllPowerfulSpringbootApplication.class, args);
19 | System.out.println("""
20 | .-----------------TTTT_-----_______ \s
21 | /''''''''''(______O] ----------____ \\______/]_
22 | __...---'""\"\\_ --'' Q ___________@
23 | |''' ._ _______________=---------""\"""\"" \s
24 | | ..--''| l L |_l | \s
25 | | ..--'' . /-___j ' ' \s
26 | | ..--'' / , ' ' \s
27 | |--'' / ` \\ \s
28 | L__' \\ - \s
29 | - '-. \s
30 | '. / \s
31 | '-./ \s
32 | """);
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/annotation/AuthCheck.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * 权限校验
10 | *
11 | * @author qiutuan
12 | * @date 2024/11/02
13 | */
14 | @Target(ElementType.METHOD)
15 | @Retention(RetentionPolicy.RUNTIME)
16 | public @interface AuthCheck {
17 |
18 | /**
19 | * 必须有某个角色
20 | *
21 | * @return
22 | */
23 | String mustRole() default "";
24 |
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/annotation/RateLimit.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.annotation;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 限流注解
7 | *
8 | * @author qiutuan
9 | * @date 2024/12/06
10 | */
11 | @Target(ElementType.METHOD)
12 | @Retention(RetentionPolicy.RUNTIME)
13 | @Documented
14 | public @interface RateLimit {
15 | /**
16 | * 限流时间窗口(秒)
17 | */
18 | int time() default 60;
19 |
20 | /**
21 | * 限流次数
22 | */
23 | int count() default 100;
24 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/annotation/RepeatSubmit.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.annotation;
2 |
3 | import java.lang.annotation.*;
4 |
5 | /**
6 | * 防重复提交注解
7 | *
8 | * @author qiutuan
9 | * @date 2024/12/10
10 | */
11 | @Target(ElementType.METHOD)
12 | @Retention(RetentionPolicy.RUNTIME)
13 | @Documented
14 | public @interface RepeatSubmit {
15 | /**
16 | * 间隔时间(ms),小于此时间视为重复提交
17 | */
18 | int interval() default 5000;
19 |
20 | /**
21 | * 提示消息
22 | */
23 | String message() default "请勿重复提交";
24 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/aop/AuthInterceptor.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.aop;
2 |
3 | import org.aspectj.lang.ProceedingJoinPoint;
4 | import org.aspectj.lang.annotation.Around;
5 | import org.aspectj.lang.annotation.Aspect;
6 | import org.springframework.stereotype.Component;
7 | import org.springframework.web.context.request.RequestAttributes;
8 | import org.springframework.web.context.request.RequestContextHolder;
9 | import org.springframework.web.context.request.ServletRequestAttributes;
10 | import top.qtcc.qiutuanallpowerfulspringboot.annotation.AuthCheck;
11 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.User;
12 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
13 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.UserRoleEnum;
14 | import top.qtcc.qiutuanallpowerfulspringboot.exception.BusinessException;
15 | import top.qtcc.qiutuanallpowerfulspringboot.servicec.UserService;
16 |
17 | import javax.annotation.Resource;
18 | import javax.servlet.http.HttpServletRequest;
19 |
20 | /**
21 | * 权限校验 AOP
22 | *
23 | * @author qiutuan
24 | * @date 2024/11/02
25 | */
26 | @Aspect
27 | @Component
28 | public class AuthInterceptor {
29 |
30 | @Resource
31 | private UserService userService;
32 |
33 | /**
34 | * 执行拦截
35 | *
36 | * @param joinPoint 切点
37 | * @param authCheck 权限校验注解
38 | * @return 执行结果
39 | */
40 | @Around("@annotation(authCheck)")
41 | public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
42 | String mustRole = authCheck.mustRole();
43 | RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
44 | HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
45 | // 当前登录用户
46 | User loginUser = userService.getLoginUser(request);
47 | UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
48 | // 不需要权限,放行
49 | if (mustRoleEnum == null) {
50 | return joinPoint.proceed();
51 | }
52 | // 必须有该权限才通过
53 | UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());
54 | if (userRoleEnum == null) {
55 | throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
56 | }
57 | // 如果被封号,直接拒绝
58 | if (UserRoleEnum.BAN.equals(userRoleEnum)) {
59 | throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
60 | }
61 | // 必须有管理员权限
62 | if (UserRoleEnum.ADMIN.equals(mustRoleEnum)) {
63 | // 用户没有管理员权限,拒绝
64 | if (!UserRoleEnum.ADMIN.equals(userRoleEnum)) {
65 | throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
66 | }
67 | }
68 | // 通过权限校验,放行
69 | return joinPoint.proceed();
70 | }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/aop/LogInterceptor.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.aop;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.aspectj.lang.ProceedingJoinPoint;
6 | import org.aspectj.lang.annotation.Around;
7 | import org.aspectj.lang.annotation.Aspect;
8 | import org.springframework.stereotype.Component;
9 | import org.springframework.util.StopWatch;
10 | import org.springframework.web.context.request.RequestAttributes;
11 | import org.springframework.web.context.request.RequestContextHolder;
12 | import org.springframework.web.context.request.ServletRequestAttributes;
13 |
14 | import javax.servlet.http.HttpServletRequest;
15 | import java.util.UUID;
16 |
17 | /**
18 | * 请求响应日志 AOP
19 | *
20 | * @author qiutuan
21 | * @date 2024/11/02
22 | **/
23 | @Aspect
24 | @Component
25 | @Slf4j
26 | public class LogInterceptor {
27 |
28 | /**
29 | * 执行拦截
30 | */
31 | @Around("execution(* top.qtcc.qiutuanallpowerfulspringboot.controller*.*(..))")
32 | public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
33 | // 计时
34 | StopWatch stopWatch = new StopWatch();
35 | stopWatch.start();
36 | // 获取请求路径
37 | RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
38 | HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
39 | // 生成请求唯一 id
40 | String requestId = UUID.randomUUID().toString();
41 | String url = httpServletRequest.getRequestURI();
42 | // 获取请求参数
43 | Object[] args = point.getArgs();
44 | String reqParam = "[" + StringUtils.join(args, ", ") + "]";
45 | // 输出请求日志
46 | log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
47 | httpServletRequest.getRemoteHost(), reqParam);
48 | // 执行原方法
49 | Object result = point.proceed();
50 | // 输出响应日志
51 | stopWatch.stop();
52 | long totalTimeMillis = stopWatch.getTotalTimeMillis();
53 | log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
54 | return result;
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/aspect/RateLimitAspect.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.aspect;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.aspectj.lang.ProceedingJoinPoint;
5 | import org.aspectj.lang.annotation.Around;
6 | import org.aspectj.lang.annotation.Aspect;
7 | import org.aspectj.lang.reflect.MethodSignature;
8 | import org.springframework.data.redis.core.RedisTemplate;
9 | import org.springframework.data.redis.core.StringRedisTemplate;
10 | import org.springframework.data.redis.core.script.DefaultRedisScript;
11 | import org.springframework.stereotype.Component;
12 | import top.qtcc.qiutuanallpowerfulspringboot.annotation.RateLimit;
13 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
14 | import top.qtcc.qiutuanallpowerfulspringboot.exception.BusinessException;
15 |
16 | import javax.annotation.Resource;
17 | import java.lang.reflect.Method;
18 | import java.util.Collections;
19 |
20 | /**
21 | * 接口限流
22 | *
23 | * @author qiutuan
24 | * @date 2024/12/06
25 | */
26 | @Aspect
27 | @Component
28 | @Slf4j
29 | public class RateLimitAspect {
30 |
31 | @Resource
32 | private StringRedisTemplate stringRedisTemplate;
33 |
34 | @Around("@annotation(rateLimit)")
35 | public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
36 | MethodSignature signature = (MethodSignature) point.getSignature();
37 | Method method = signature.getMethod();
38 |
39 | // 限流的key为类名+方法名
40 | String key = method.getDeclaringClass().getName() + ":" + method.getName();
41 |
42 | // 获取限流时间
43 | int time = rateLimit.time();
44 | // 获取限流次数
45 | int count = rateLimit.count();
46 |
47 | if (!tryAcquire(key, count, time)) {
48 | throw new BusinessException(ErrorCode.OPERATION_ERROR, "访问过于频繁,请稍后再试");
49 | }
50 |
51 | return point.proceed();
52 | }
53 |
54 | private boolean tryAcquire(String key, int count, int time) {
55 | String script = "local current = redis.call('incr', KEYS[1]) " +
56 | "if current == 1 then " +
57 | " redis.call('expire', KEYS[1], ARGV[1]) " +
58 | "end " +
59 | "if current <= tonumber(ARGV[2]) then " +
60 | " return 1 " +
61 | "else " +
62 | " return 0 " +
63 | "end";
64 |
65 | return Boolean.TRUE.equals(stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
66 | Collections.singletonList(key), String.valueOf(time), String.valueOf(count)));
67 | }
68 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/aspect/RepeatSubmitAspect.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.aspect;
2 |
3 | import org.aspectj.lang.ProceedingJoinPoint;
4 | import org.aspectj.lang.annotation.Around;
5 | import org.aspectj.lang.annotation.Aspect;
6 | import org.springframework.data.redis.core.RedisTemplate;
7 | import org.springframework.stereotype.Component;
8 | import org.springframework.web.context.request.RequestContextHolder;
9 | import org.springframework.web.context.request.ServletRequestAttributes;
10 | import top.qtcc.qiutuanallpowerfulspringboot.annotation.RepeatSubmit;
11 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
12 | import top.qtcc.qiutuanallpowerfulspringboot.exception.BusinessException;
13 |
14 | import javax.annotation.Resource;
15 | import javax.servlet.http.HttpServletRequest;
16 | import java.util.Objects;
17 | import java.util.concurrent.TimeUnit;
18 |
19 | /**
20 | * 防重复提交切面
21 | *
22 | * @author qiutuan
23 | * @date 2024/12/10
24 | */
25 | @Aspect
26 | @Component
27 | public class RepeatSubmitAspect {
28 |
29 | @Resource
30 | private RedisTemplate redisTemplate;
31 |
32 | @Around("@annotation(repeatSubmit)")
33 | public Object around(ProceedingJoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
34 | HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
35 |
36 | // 获取请求token,如果没有token,使用请求地址
37 | String token = request.getHeader("token");
38 | String key = token == null ? request.getRequestURI() : token;
39 |
40 | // 构建Redis键
41 | String repeatKey = "repeat_submit:" + key + ":" + request.getRequestURI();
42 |
43 | // 判断是否重复提交
44 | if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(repeatKey, 1,
45 | repeatSubmit.interval(), TimeUnit.MILLISECONDS))) {
46 | // 不是重复提交,执行方法
47 | return point.proceed();
48 | } else {
49 | // 重复提交
50 | throw new BusinessException(ErrorCode.OPERATION_ERROR, repeatSubmit.message());
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/aspect/RequestLogAspect.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.aspect;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.aspectj.lang.ProceedingJoinPoint;
5 | import org.aspectj.lang.annotation.Around;
6 | import org.aspectj.lang.annotation.Aspect;
7 | import org.springframework.stereotype.Component;
8 | import org.springframework.web.context.request.RequestAttributes;
9 | import org.springframework.web.context.request.RequestContextHolder;
10 | import org.springframework.web.context.request.ServletRequestAttributes;
11 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.RequestLog;
12 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.User;
13 | import top.qtcc.qiutuanallpowerfulspringboot.service.RequestLogService;
14 |
15 | import javax.annotation.Resource;
16 | import javax.servlet.http.HttpServletRequest;
17 | import java.util.UUID;
18 |
19 | import static top.qtcc.qiutuanallpowerfulspringboot.constant.UserConstant.USER_LOGIN_STATE;
20 |
21 | /**
22 | * 请求日志切面
23 | *
24 | * @author qiutuan
25 | * @date 2024/12/07
26 | */
27 | @Aspect
28 | @Component
29 | @Slf4j
30 | public class RequestLogAspect {
31 |
32 | @Resource
33 | private RequestLogService requestLogService;
34 |
35 | //TODO 未保存请求参数
36 | @Around("execution(* top.qtcc.qiutuanallpowerfulspringboot.controller.*.*(..))")
37 | public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
38 | long startTime = System.currentTimeMillis();
39 | RequestLog requestLog = new RequestLog();
40 | requestLog.setRequestId(UUID.randomUUID().toString());
41 |
42 | // 获取请求信息
43 | RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
44 | assert requestAttributes != null;
45 | HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
46 |
47 | requestLog.setUrl(request.getRequestURI());
48 | requestLog.setMethod(request.getMethod());
49 | requestLog.setIp(request.getRemoteAddr());
50 |
51 | Object attribute = request.getSession().getAttribute(USER_LOGIN_STATE);
52 | if (attribute != null) {
53 | requestLog.setUserId(((User) attribute).getId());
54 | }
55 |
56 | Object result;
57 | try {
58 | result = joinPoint.proceed();
59 | requestLog.setStatus(200);
60 | } catch (Exception e) {
61 | requestLog.setStatus(500);
62 | requestLog.setErrorMsg(e.getMessage());
63 | throw e;
64 | } finally {
65 | requestLog.setCostTime(System.currentTimeMillis() - startTime);
66 | requestLogService.save(requestLog);
67 | }
68 |
69 | return result;
70 | }
71 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/common/BaseResponse.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.common;
2 |
3 | import lombok.Data;
4 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
5 |
6 | import java.io.Serializable;
7 |
8 | /**
9 | * 通用返回类
10 | *
11 | * @author qiutuan
12 | * @date 2024/11/02
13 | */
14 | @Data
15 | public class BaseResponse implements Serializable {
16 |
17 | private int code;
18 |
19 | private T data;
20 |
21 | private String message;
22 |
23 | public BaseResponse(int code, T data, String message) {
24 | this.code = code;
25 | this.data = data;
26 | this.message = message;
27 | }
28 |
29 | public BaseResponse(int code, T data) {
30 | this(code, data, "");
31 | }
32 |
33 | public BaseResponse(ErrorCode errorCode) {
34 | this(errorCode.getCode(), null, errorCode.getMessage());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/common/DeleteRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.common;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 删除请求
9 | *
10 | * @author qiutuan
11 | * @date 2024/11/02
12 | */
13 | @Data
14 | public class DeleteRequest implements Serializable {
15 |
16 | /**
17 | * id
18 | */
19 | private Long id;
20 |
21 | private static final long serialVersionUID = 1L;
22 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/common/PageRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.common;
2 |
3 | import lombok.Data;
4 | import top.qtcc.qiutuanallpowerfulspringboot.constant.CommonConstant;
5 |
6 | /**
7 | * 分页请求
8 | *
9 | * @author qiutuan
10 | * @date 2024/11/02
11 | */
12 | @Data
13 | public class PageRequest {
14 |
15 | /**
16 | * 当前页号
17 | */
18 | private int current = 1;
19 |
20 | /**
21 | * 页面大小
22 | */
23 | private int pageSize = 10;
24 |
25 | /**
26 | * 排序字段
27 | */
28 | private String sortField;
29 |
30 | /**
31 | * 排序顺序(默认升序)
32 | */
33 | private String sortOrder = CommonConstant.SORT_ORDER_ASC;
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/common/ResultUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.common;
2 |
3 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
4 |
5 | /**
6 | * 返回工具类
7 | *
8 | * @author qiutuan
9 | * @date 2024/11/02
10 | */
11 | public class ResultUtils {
12 |
13 | /**
14 | * 成功
15 | *
16 | * @param data 返回数据
17 | * @param 数据类型
18 | * @return BaseResponse
19 | */
20 | public static BaseResponse success(T data) {
21 | return new BaseResponse<>(0, data, "ok");
22 | }
23 |
24 | /**
25 | * 失败
26 | *
27 | * @param errorCode 错误码
28 | * @return BaseResponse
29 | */
30 | public static BaseResponse error(ErrorCode errorCode) {
31 | return new BaseResponse<>(errorCode);
32 | }
33 |
34 | /**
35 | * 失败
36 | *
37 | * @param code 错误码
38 | * @param message 错误信息
39 | * @return BaseResponse
40 | */
41 | public static BaseResponse error(int code, String message) {
42 | return new BaseResponse(code, null, message);
43 | }
44 |
45 | /**
46 | * 失败
47 | *
48 | * @param errorCode 错误码
49 | * @return BaseResponse
50 | */
51 | public static BaseResponse error(ErrorCode errorCode, String message) {
52 | return new BaseResponse(errorCode.getCode(), null, message);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/common/websocket/MessageWrapper.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.common.websocket;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 消息包装类
7 | *
8 | * @author qiutuan
9 | * @date 2024/12/09
10 | */
11 | @Data
12 | public class MessageWrapper {
13 | private String messageId;
14 | private String message;
15 |
16 | public MessageWrapper(String messageId, String message) {
17 | this.messageId = messageId;
18 | this.message = message;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/common/websocket/WebSocketServer.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.common.websocket;
2 |
3 | import cn.hutool.json.JSONUtil;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.data.redis.core.RedisTemplate;
8 | import org.springframework.scheduling.annotation.Scheduled;
9 | import org.springframework.stereotype.Component;
10 |
11 | import javax.websocket.*;
12 | import javax.websocket.server.PathParam;
13 | import javax.websocket.server.ServerEndpoint;
14 | import java.util.Objects;
15 | import java.util.UUID;
16 | import java.util.concurrent.ConcurrentHashMap;
17 | import java.util.concurrent.CopyOnWriteArraySet;
18 | import java.util.concurrent.TimeUnit;
19 |
20 |
21 | /**
22 | * WebSocket服务
23 | *
24 | * @author qiutuan
25 | * @date 2024/11/22
26 | */
27 | @Slf4j
28 | @Component
29 | @ServerEndpoint("/websocket/{key}")
30 | public class WebSocketServer {
31 | /**
32 | * 日志工具
33 | */
34 | private final Logger logger = LoggerFactory.getLogger(this.getClass());
35 | /**
36 | * 与某个客户端的连接会话,需要通过它来给客户端发送数据
37 | */
38 | private Session session;
39 | /**
40 | * 连接标识
41 | */
42 | private String key;
43 | /**
44 | * 用来存放每个客户端对应的MyWebSocket对象
45 | */
46 | private static final CopyOnWriteArraySet WEB_SOCKETS = new CopyOnWriteArraySet<>();
47 | /**
48 | * 用来存在线连接用户信息
49 | */
50 | private static final ConcurrentHashMap SESSION_POOL = new ConcurrentHashMap();
51 |
52 | private RedisTemplate redisTemplate;
53 |
54 | /**
55 | * 链接成功调用的方法
56 | *
57 | * @param session 会话
58 | * @param key 连接标识
59 | */
60 | @OnOpen
61 | public void onOpen(Session session, @PathParam("key") String key) {
62 | try {
63 | this.session = session;
64 | this.key = key;
65 | WEB_SOCKETS.add(this);
66 | SESSION_POOL.put(key, session);
67 | logger.info("【websocket消息】有新的连接,总数为:{}", WEB_SOCKETS.size());
68 | } catch (Exception e) {
69 | }
70 | }
71 |
72 | /**
73 | * 链接关闭调用的方法
74 | */
75 | @OnClose
76 | public void onClose() {
77 | try {
78 | WEB_SOCKETS.remove(this);
79 | SESSION_POOL.remove(this.key);
80 | logger.info("【websocket消息】连接断开,总数为:{}", WEB_SOCKETS.size());
81 | } catch (Exception e) {
82 | }
83 | }
84 |
85 | /**
86 | * 收到客户端消息后调用的方法
87 | *
88 | * @param message 消息
89 | */
90 | @OnMessage
91 | public void onMessage(String message) {
92 | logger.info("【websocket消息】收到客户端消息:{}", message);
93 | }
94 |
95 | /**
96 | * 发送错误时的处理
97 | *
98 | * @param session 会话
99 | * @param error 错误
100 | */
101 | @OnError
102 | public void onError(Session session, Throwable error) {
103 | logger.error("用户错误,原因:{}", error.getMessage());
104 | error.printStackTrace();
105 | }
106 |
107 | /**
108 | * 此为广播消息
109 | *
110 | * @param message 消息
111 | */
112 | public void sendAllMessage(String message) {
113 | logger.info("【websocket消息】广播消息:{}", message);
114 | for (WebSocketServer webSocket : WEB_SOCKETS) {
115 | try {
116 | if (webSocket.session.isOpen()) {
117 | webSocket.session.getAsyncRemote().sendText(message);
118 | }
119 | } catch (Exception e) {
120 | e.printStackTrace();
121 | }
122 | }
123 | }
124 |
125 | /**
126 | * 此为单点消息
127 | *
128 | * @param key 连接标识
129 | * @param message 消息
130 | */
131 | public void sendOneMessage(String key, String message) {
132 | Session session = SESSION_POOL.get(key);
133 | if (session != null && session.isOpen()) {
134 | try {
135 | logger.info("【websocket消息】 单点消息:{}", message);
136 | session.getAsyncRemote().sendText(message);
137 | } catch (Exception e) {
138 | e.printStackTrace();
139 | }
140 | }
141 | }
142 |
143 | /**
144 | * 此为单点消息
145 | *
146 | * @param key 连接标识
147 | * @param message 消息
148 | */
149 | public void sendOneMessage(String key, Object message) {
150 | Session session = SESSION_POOL.get(key);
151 | if (session != null && session.isOpen()) {
152 | try {
153 | logger.info("【websocket消息】 单点消息:{}", message);
154 | session.getAsyncRemote().sendText(JSONUtil.toJsonStr(message));
155 | } catch (Exception e) {
156 | e.printStackTrace();
157 | }
158 | }
159 | }
160 |
161 | /**
162 | * 此为单点消息(多人)
163 | *
164 | * @param keys 连接标识
165 | * @param message 消息
166 | */
167 | public void sendMoreMessage(String[] keys, String message) {
168 | for (String key : keys) {
169 | Session session = SESSION_POOL.get(key);
170 | if (session != null && session.isOpen()) {
171 | try {
172 | logger.info("【websocket消息】 单点消息:{}", message);
173 | session.getAsyncRemote().sendText(message);
174 | } catch (Exception e) {
175 | e.printStackTrace();
176 | }
177 | }
178 | }
179 |
180 | }
181 |
182 | /**
183 | * 心跳检测
184 | */
185 | @Scheduled(fixedRate = 30000)
186 | public void heartbeat() {
187 | for (WebSocketServer webSocket : WEB_SOCKETS) {
188 | try {
189 | if (webSocket.session.isOpen()) {
190 | webSocket.session.getAsyncRemote().sendText("heartbeat");
191 | }
192 | } catch (Exception e) {
193 | log.error("心跳检测异常", e);
194 | }
195 | }
196 | }
197 |
198 | /**
199 | * 发送消息确认机制
200 | *
201 | * @param key 连接标识
202 | * @param message 消息
203 | */
204 | public void sendOneMessageWithAck(String key, String message) {
205 | Session session = SESSION_POOL.get(key);
206 | if (session != null && session.isOpen()) {
207 | try {
208 | String messageId = UUID.randomUUID().toString();
209 | MessageWrapper wrapper = new MessageWrapper(messageId, message);
210 | session.getAsyncRemote().sendText(JSONUtil.toJsonStr(wrapper));
211 | // 存储消息到Redis,等待确认
212 | redisTemplate.opsForValue().set("msg:" + messageId, message, 5, TimeUnit.MINUTES);
213 | } catch (Exception e) {
214 | log.error("发送消息异常", e);
215 | }
216 | }
217 | }
218 |
219 |
220 | @Override
221 | public boolean equals(Object o) {
222 | if (this == o) {
223 | return true;
224 | }
225 | if (o == null || getClass() != o.getClass()) {
226 | return false;
227 | }
228 | WebSocketServer that = (WebSocketServer) o;
229 | return Objects.equals(logger, that.logger) && Objects.equals(session, that.session) && Objects.equals(key, that.key) && Objects.equals(redisTemplate, that.redisTemplate);
230 | }
231 |
232 | @Override
233 | public int hashCode() {
234 | return Objects.hash(logger, session, key, redisTemplate);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/CorsConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.web.servlet.config.annotation.CorsRegistry;
5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6 |
7 | /**
8 | * 全局跨域配置
9 | *
10 | * @author qiutuan
11 | * @date 2024/11/02
12 | */
13 | @Configuration
14 | public class CorsConfig implements WebMvcConfigurer {
15 |
16 | /**
17 | * 跨域配置
18 | *
19 | * @param registry 跨域注册器
20 | */
21 | @Override
22 | public void addCorsMappings(CorsRegistry registry) {
23 | // 覆盖所有请求
24 | registry.addMapping("/**")
25 | // 允许发送 Cookie
26 | .allowCredentials(true)
27 | // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
28 | .allowedOriginPatterns("*")
29 | .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
30 | .allowedHeaders("*")
31 | .exposedHeaders("*");
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/CosClientConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import com.qcloud.cos.COSClient;
4 | import com.qcloud.cos.ClientConfig;
5 | import com.qcloud.cos.auth.BasicCOSCredentials;
6 | import com.qcloud.cos.auth.COSCredentials;
7 | import com.qcloud.cos.region.Region;
8 | import lombok.Data;
9 | import org.springframework.boot.context.properties.ConfigurationProperties;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Configuration;
12 |
13 | /**
14 | * 腾讯云对象存储客户端
15 | *
16 | * @author qiutuan
17 | * @date 2024/11/02
18 | */
19 | @Configuration
20 | @ConfigurationProperties(prefix = "cos.client")
21 | @Data
22 | public class CosClientConfig {
23 |
24 | /**
25 | * accessKey
26 | */
27 | private String accessKey;
28 |
29 | /**
30 | * secretKey
31 | */
32 | private String secretKey;
33 |
34 | /**
35 | * 区域
36 | */
37 | private String region;
38 |
39 | /**
40 | * 桶名
41 | */
42 | private String bucket;
43 |
44 | @Bean
45 | public COSClient cosClient() {
46 | // 初始化用户身份信息(secretId, secretKey)
47 | COSCredentials cred = new BasicCOSCredentials(accessKey, secretKey);
48 | // 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224
49 | ClientConfig clientConfig = new ClientConfig(new Region(region));
50 | // 生成cos客户端
51 | return new COSClient(cred, clientConfig);
52 | }
53 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/EncryptConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6 | import top.qtcc.qiutuanallpowerfulspringboot.interceptor.EncryptInterceptor;
7 |
8 | /**
9 | * 加密配置类
10 | *
11 | * @author qiutuan
12 | * @date 2024/12/10
13 | * 如需使用拦截器,必须加上该注解Configuration
14 | */
15 | public class EncryptConfig implements WebMvcConfigurer {
16 |
17 | @Bean
18 | public EncryptInterceptor encryptInterceptor() {
19 | return new EncryptInterceptor();
20 | }
21 |
22 | @Override
23 | public void addInterceptors(InterceptorRegistry registry) {
24 | registry.addInterceptor(encryptInterceptor())
25 | .addPathPatterns("/**")
26 | .excludePathPatterns("/public/**");
27 | }
28 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/JsonConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.fasterxml.jackson.databind.module.SimpleModule;
5 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
6 | import org.springframework.boot.jackson.JsonComponent;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
9 |
10 | /**
11 | * Spring MVC Json 配置
12 | *
13 | * @author qiutuan
14 | * @date 2024/11/02
15 | */
16 | @JsonComponent
17 | public class JsonConfig {
18 |
19 | /**
20 | * 添加 Long 转 json 精度丢失的配置
21 | *
22 | * @param builder {@link Jackson2ObjectMapperBuilder }
23 | * @return {@link ObjectMapper }
24 | */
25 | @Bean
26 | public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
27 | ObjectMapper objectMapper = builder.createXmlMapper(false).build();
28 | SimpleModule module = new SimpleModule();
29 | module.addSerializer(Long.class, ToStringSerializer.instance);
30 | module.addSerializer(Long.TYPE, ToStringSerializer.instance);
31 | objectMapper.registerModule(module);
32 | return objectMapper;
33 | }
34 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/MinioClientConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import io.minio.MinioClient;
4 | import lombok.Data;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 |
9 | /**
10 | * Minio 客户端配置
11 | *
12 | * @author qiutuan
13 | * @date 2024/11/19
14 | */
15 | @Configuration
16 | @ConfigurationProperties(prefix = "minio")
17 | @Data
18 | public class MinioClientConfig {
19 |
20 | private String endpoint;
21 |
22 | private String accessKey;
23 |
24 | private String secretKey;
25 |
26 | private String bucket;
27 |
28 |
29 | @Bean
30 | public MinioClient minioClient() {
31 | return MinioClient.builder()
32 | .endpoint(endpoint)
33 | .credentials(accessKey, secretKey)
34 | .build();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/MyBatisPlusConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import com.baomidou.mybatisplus.annotation.DbType;
4 | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
5 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
6 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
7 | import org.apache.ibatis.reflection.MetaObject;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 |
11 | import java.time.LocalDateTime;
12 |
13 | /**
14 | * @author qiutuan
15 | * @date 2024/12/10
16 | */
17 | @Configuration
18 | public class MyBatisPlusConfig {
19 |
20 | /**
21 | * 添加分页插件
22 | */
23 | @Bean
24 | public MybatisPlusInterceptor mybatisPlusInterceptor() {
25 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
26 | // 如果配置多个插件, 切记分页最后添加
27 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
28 | // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
29 | return interceptor;
30 | }
31 |
32 |
33 | /**
34 | * 自动填充功能(自动填充创建时间和更新时间)
35 | *
36 | * @return {@link MetaObjectHandler }
37 | */
38 | @Bean
39 | public MetaObjectHandler metaObjectHandler() {
40 | return new MetaObjectHandler() {
41 | @Override
42 | public void insertFill(MetaObject metaObject) {
43 | this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
44 | this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
45 | }
46 |
47 | @Override
48 | public void updateFill(MetaObject metaObject) {
49 | this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
50 | }
51 | };
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/RedisConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import org.springframework.cache.CacheManager;
4 | import org.springframework.cache.annotation.EnableCaching;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.data.redis.cache.RedisCacheConfiguration;
8 | import org.springframework.data.redis.cache.RedisCacheManager;
9 | import org.springframework.data.redis.connection.RedisConnectionFactory;
10 | import org.springframework.data.redis.core.RedisTemplate;
11 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
12 | import org.springframework.data.redis.serializer.RedisSerializationContext;
13 | import org.springframework.data.redis.serializer.StringRedisSerializer;
14 |
15 | import java.time.Duration;
16 |
17 | /**
18 | * Redis 配置类
19 | *
20 | * @author qiutuan
21 | * @date 2024/12/07
22 | */
23 | @EnableCaching
24 | @Configuration
25 | public class RedisConfig {
26 |
27 | /**
28 | * 配置 RedisTemplate Bean
29 | *
30 | * @param connectionFactory RedisConnectionFactory
31 | * @return {@link RedisTemplate }<{@link String }, {@link Object }>
32 | */
33 | @Bean
34 | public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
35 | RedisTemplate template = new RedisTemplate<>();
36 | template.setConnectionFactory(connectionFactory);
37 |
38 | // 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
39 | template.setKeySerializer(new StringRedisSerializer());
40 | // 使用 GenericJackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
41 | template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
42 |
43 | template.setHashKeySerializer(new StringRedisSerializer());
44 | template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
45 |
46 | template.afterPropertiesSet();
47 | return template;
48 | }
49 |
50 | /**
51 | * 配置 CacheManager Bean
52 | *
53 | * @param factory RedisConnectionFactory
54 | * @return {@link CacheManager }
55 | */
56 | @Bean
57 | public CacheManager cacheManager(RedisConnectionFactory factory) {
58 | RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
59 | // 缓存过期时间 1 小时
60 | .entryTtl(Duration.ofHours(1))
61 | .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
62 | .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
63 | // 禁用缓存空值
64 | .disableCachingNullValues();
65 |
66 | return RedisCacheManager.builder(factory)
67 | .cacheDefaults(config)
68 | .build();
69 | }
70 |
71 |
72 | /**
73 | * 配置 RedisCacheConfiguration Bean
74 | *
75 | * @return {@link RedisCacheConfiguration }
76 | */
77 | @Bean
78 | public RedisCacheConfiguration redisCacheConfiguration() {
79 | return RedisCacheConfiguration.defaultCacheConfig()
80 | // 缓存过期时间 1 小时
81 | .entryTtl(Duration.ofHours(1))
82 | .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
83 | .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
84 | // 禁用缓存空值
85 | .disableCachingNullValues()
86 | // 缓存名称前缀
87 | .prefixCacheNameWith("cache:");
88 | }
89 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/ScheduleConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.scheduling.annotation.EnableScheduling;
6 | import org.springframework.scheduling.annotation.SchedulingConfigurer;
7 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
8 | import org.springframework.scheduling.config.ScheduledTaskRegistrar;
9 |
10 | /**
11 | * 定时任务配置
12 | *
13 | * @author qiutuan
14 | * @date 2024/12/08
15 | */
16 | @Configuration
17 | @EnableScheduling
18 | public class ScheduleConfig implements SchedulingConfigurer {
19 |
20 | @Bean(name = "taskScheduler")
21 | public ThreadPoolTaskScheduler taskScheduler() {
22 | ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
23 | // 设置线程池大小
24 | scheduler.setPoolSize(10);
25 | scheduler.setThreadNamePrefix("scheduled-task-");
26 | scheduler.initialize();
27 | return scheduler;
28 | }
29 |
30 | @Override
31 | public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
32 | // 使用自定义的ThreadPoolTaskScheduler作为调度器
33 | taskRegistrar.setTaskScheduler(taskScheduler());
34 | }
35 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/ThreadPoolConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.scheduling.annotation.EnableAsync;
6 | import org.springframework.scheduling.annotation.EnableScheduling;
7 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
8 |
9 | import java.util.concurrent.Executor;
10 | import java.util.concurrent.ThreadPoolExecutor;
11 |
12 | /**
13 | * 线程池配置
14 | *
15 | * @author qiutuan
16 | * @date 2024/12/07
17 | * EnableAsync // 开启异步支持
18 | * EnableScheduling // 开启定时任务支持
19 | */
20 | @Configuration
21 | @EnableAsync
22 | @EnableScheduling
23 | public class ThreadPoolConfig {
24 |
25 | /**
26 | * 配置业务线程池
27 | */
28 | @Bean("businessExecutor")
29 | public Executor businessExecutor() {
30 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
31 | // 核心线程数
32 | executor.setCorePoolSize(10);
33 | // 最大线程数
34 | executor.setMaxPoolSize(20);
35 | // 队列容量
36 | executor.setQueueCapacity(200);
37 | // 线程名前缀
38 | executor.setThreadNamePrefix("business-");
39 | // 线程存活时间
40 | executor.setKeepAliveSeconds(60);
41 | // 拒绝策略
42 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
43 | // 等待所有任务完成后再关闭线程池
44 | executor.setWaitForTasksToCompleteOnShutdown(true);
45 | executor.initialize();
46 | return executor;
47 | }
48 |
49 | /**
50 | * 配置异步任务线程池
51 | */
52 | @Bean("asyncExecutor")
53 | public Executor asyncExecutor() {
54 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
55 | // 核心线程数
56 | executor.setCorePoolSize(5);
57 | // 最大线程数
58 | executor.setMaxPoolSize(10);
59 | // 队列容量
60 | executor.setQueueCapacity(100);
61 | // 线程名前缀
62 | executor.setThreadNamePrefix("async-");
63 | // 线程存活时间
64 | executor.setKeepAliveSeconds(60);
65 | // 拒绝策略:由调用线程处理
66 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
67 | // 等待所有任务完成后再关闭线程池
68 | executor.setWaitForTasksToCompleteOnShutdown(true);
69 | executor.initialize();
70 | return executor;
71 | }
72 |
73 | /**
74 | * 配置定时任务线程池
75 | */
76 | @Bean("scheduledExecutor")
77 | public Executor scheduledExecutor() {
78 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
79 | // 核心线程数
80 | executor.setCorePoolSize(3);
81 | // 最大线程数
82 | executor.setMaxPoolSize(5);
83 | // 队列容量
84 | executor.setQueueCapacity(50);
85 | // 线程名前缀
86 | executor.setThreadNamePrefix("scheduled-");
87 | // 线程存活时间
88 | executor.setKeepAliveSeconds(60);
89 | // 拒绝策略:由调用线程处理
90 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
91 | // 等待所有任务完成后再关闭线程池
92 | executor.setWaitForTasksToCompleteOnShutdown(true);
93 | executor.initialize();
94 | return executor;
95 | }
96 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/config/WebSocketConfig.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.web.socket.server.standard.ServerEndpointExporter;
6 |
7 |
8 | /**
9 | * @author qiutuan
10 | * @date 2024/11/22
11 | */
12 | @Configuration
13 | public class WebSocketConfig {
14 | @Bean
15 | public ServerEndpointExporter serverEndpointExporter() {
16 | return new ServerEndpointExporter();
17 | }
18 | }
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/constant/CommonConstant.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.constant;
2 |
3 | /**
4 | * 通用常量
5 | *
6 | * @author qiutuan
7 | * @date 2024/11/02
8 | */
9 | public interface CommonConstant {
10 |
11 | /**
12 | * 升序
13 | */
14 | String SORT_ORDER_ASC = "ascend";
15 |
16 | /**
17 | * 降序
18 | */
19 | String SORT_ORDER_DESC = " descend";
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/constant/FileConstant.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.constant;
2 |
3 | /**
4 | * 文件常量
5 | *
6 | * @author qiutuan
7 | * @date 2024/11/02
8 | */
9 | public interface FileConstant {
10 |
11 | /**
12 | * COS 访问地址
13 | * todo 需替换配置
14 | */
15 | String COS_HOST = "http://120.79.57.149:9000/qiutuan-all-powerful/";
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/constant/HttpStatus.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.constant;
2 |
3 | /**
4 | * 返回状态码
5 | *
6 | * @author qiutuan
7 | */
8 | public class HttpStatus
9 | {
10 | /**
11 | * 操作成功
12 | */
13 | public static final int SUCCESS = 200;
14 |
15 | /**
16 | * 对象创建成功
17 | */
18 | public static final int CREATED = 201;
19 |
20 | /**
21 | * 请求已经被接受
22 | */
23 | public static final int ACCEPTED = 202;
24 |
25 | /**
26 | * 操作已经执行成功,但是没有返回数据
27 | */
28 | public static final int NO_CONTENT = 204;
29 |
30 | /**
31 | * 资源已被移除
32 | */
33 | public static final int MOVED_PERM = 301;
34 |
35 | /**
36 | * 重定向
37 | */
38 | public static final int SEE_OTHER = 303;
39 |
40 | /**
41 | * 资源没有被修改
42 | */
43 | public static final int NOT_MODIFIED = 304;
44 |
45 | /**
46 | * 参数列表错误(缺少,格式不匹配)
47 | */
48 | public static final int BAD_REQUEST = 400;
49 |
50 | /**
51 | * 未授权
52 | */
53 | public static final int UNAUTHORIZED = 401;
54 |
55 | /**
56 | * 访问受限,授权过期
57 | */
58 | public static final int FORBIDDEN = 403;
59 |
60 | /**
61 | * 资源,服务未找到
62 | */
63 | public static final int NOT_FOUND = 404;
64 |
65 | /**
66 | * 不允许的http方法
67 | */
68 | public static final int BAD_METHOD = 405;
69 |
70 | /**
71 | * 资源冲突,或者资源被锁
72 | */
73 | public static final int CONFLICT = 409;
74 |
75 | /**
76 | * 不支持的数据,媒体类型
77 | */
78 | public static final int UNSUPPORTED_TYPE = 415;
79 |
80 | /**
81 | * 系统内部错误
82 | */
83 | public static final int ERROR = 500;
84 |
85 | /**
86 | * 接口未实现
87 | */
88 | public static final int NOT_IMPLEMENTED = 501;
89 |
90 | /**
91 | * 系统警告消息
92 | */
93 | public static final int WARN = 601;
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/constant/UserConstant.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.constant;
2 |
3 | /**
4 | * 用户常量
5 | *
6 | * @author qiutuan
7 | * @date 2024/11/02
8 | */
9 | public interface UserConstant {
10 |
11 | /**
12 | * 用户登录态键
13 | */
14 | String USER_LOGIN_STATE = "user_login";
15 |
16 | // region 权限
17 |
18 | /**
19 | * 默认角色
20 | */
21 | String DEFAULT_ROLE = "user";
22 |
23 | /**
24 | * 管理员角色
25 | */
26 | String ADMIN_ROLE = "admin";
27 |
28 | /**
29 | * 被封号
30 | */
31 | String BAN_ROLE = "ban";
32 |
33 | // endregion
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/controller/FileController.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.controller;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.apache.commons.lang3.RandomStringUtils;
6 | import org.springframework.web.bind.annotation.PostMapping;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestPart;
9 | import org.springframework.web.bind.annotation.RestController;
10 | import org.springframework.web.multipart.MultipartFile;
11 | import top.qtcc.qiutuanallpowerfulspringboot.common.BaseResponse;
12 | import top.qtcc.qiutuanallpowerfulspringboot.common.ResultUtils;
13 | import top.qtcc.qiutuanallpowerfulspringboot.constant.FileConstant;
14 | import top.qtcc.qiutuanallpowerfulspringboot.domain.dto.file.UploadFileRequest;
15 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.User;
16 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
17 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.FileUploadBizEnum;
18 | import top.qtcc.qiutuanallpowerfulspringboot.exception.BusinessException;
19 | import top.qtcc.qiutuanallpowerfulspringboot.manager.file.FileManager;
20 | import top.qtcc.qiutuanallpowerfulspringboot.servicec.UserService;
21 |
22 | import javax.annotation.Resource;
23 | import javax.servlet.http.HttpServletRequest;
24 | import java.io.File;
25 | import java.util.Arrays;
26 |
27 | /**
28 | * 文件接口
29 | *
30 | * @author qiutuan
31 | * @date 2024/11/02
32 | */
33 | @RestController
34 | @RequestMapping("/file")
35 | @Slf4j
36 | public class FileController {
37 |
38 | @Resource
39 | private UserService userService;
40 |
41 | @Resource(name = "fileManager")
42 | private FileManager fileManager;
43 |
44 | /**
45 | * 文件上传
46 | *
47 | * @param multipartFile 文件
48 | * @param uploadFileRequest 上传文件请求
49 | * @param request 请求
50 | * @return 文件地址
51 | */
52 | @PostMapping("/upload")
53 | public BaseResponse uploadFile(@RequestPart("file") MultipartFile multipartFile,
54 | UploadFileRequest uploadFileRequest, HttpServletRequest request) {
55 | String biz = uploadFileRequest.getBiz();
56 | FileUploadBizEnum fileUploadBizEnum = FileUploadBizEnum.getEnumByValue(biz);
57 | if (fileUploadBizEnum == null) {
58 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
59 | }
60 | validFile(multipartFile, fileUploadBizEnum);
61 | User loginUser = userService.getLoginUser(request);
62 | // 文件目录:根据业务、用户来划分
63 | String uuid = RandomStringUtils.randomAlphanumeric(8);
64 | String filename = uuid + "-" + multipartFile.getOriginalFilename();
65 | String filepath = String.format("/%s/%s/%s", fileUploadBizEnum.getValue(), loginUser.getId(), filename);
66 | File file = null;
67 | try {
68 | // 上传文件
69 | file = File.createTempFile(filepath, null);
70 | multipartFile.transferTo(file);
71 | fileManager.putObject(filepath, file);
72 | // 返回可访问地址
73 | return ResultUtils.success(FileConstant.COS_HOST + filepath);
74 | } catch (Exception e) {
75 | log.error("file upload error, filepath = " + filepath, e);
76 | throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
77 | } finally {
78 | if (file != null) {
79 | // 删除临时文件
80 | boolean delete = file.delete();
81 | if (!delete) {
82 | log.error("file delete error, filepath = {}", filepath);
83 | }
84 | }
85 | }
86 | }
87 |
88 | /**
89 | * 校验文件
90 | *
91 | * @param multipartFile 文件
92 | * @param fileUploadBizEnum 业务类型
93 | */
94 | private void validFile(MultipartFile multipartFile, FileUploadBizEnum fileUploadBizEnum) {
95 | // 文件大小
96 | long fileSize = multipartFile.getSize();
97 | // 文件后缀
98 | String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
99 | final long ONE_M = 1024 * 1024L;
100 | // 用户头像
101 | if (FileUploadBizEnum.USER_AVATAR.equals(fileUploadBizEnum)) {
102 | if (fileSize > ONE_M) {
103 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小不能超过 1M");
104 | }
105 | if (!Arrays.asList("jpeg", "jpg", "svg", "png", "webp").contains(fileSuffix)) {
106 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型错误");
107 | }
108 | }
109 | // 用户文件
110 | if (FileUploadBizEnum.USER_FILE.equals(fileUploadBizEnum)) {
111 | if (fileSize > 10 * ONE_M) {
112 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小不能超过 10M");
113 | }
114 | if (!Arrays.asList("pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt").contains(fileSuffix)) {
115 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型错误");
116 | }
117 | }
118 | // 用户图片
119 | if (FileUploadBizEnum.USER_IMAGE.equals(fileUploadBizEnum)) {
120 | if (fileSize > 5 * ONE_M) {
121 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小不能超过 5M");
122 | }
123 | if (!Arrays.asList("jpeg", "jpg", "svg", "png", "webp").contains(fileSuffix)) {
124 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型错误");
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/controller/UserController.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.controller;
2 |
3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.apache.commons.lang3.StringUtils;
6 | import org.springframework.beans.BeanUtils;
7 | import org.springframework.util.DigestUtils;
8 | import org.springframework.web.bind.annotation.*;
9 | import top.qtcc.qiutuanallpowerfulspringboot.annotation.AuthCheck;
10 | import top.qtcc.qiutuanallpowerfulspringboot.annotation.RateLimit;
11 | import top.qtcc.qiutuanallpowerfulspringboot.annotation.RepeatSubmit;
12 | import top.qtcc.qiutuanallpowerfulspringboot.common.BaseResponse;
13 | import top.qtcc.qiutuanallpowerfulspringboot.common.DeleteRequest;
14 | import top.qtcc.qiutuanallpowerfulspringboot.common.ResultUtils;
15 | import top.qtcc.qiutuanallpowerfulspringboot.constant.UserConstant;
16 | import top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user.*;
17 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.User;
18 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
19 | import top.qtcc.qiutuanallpowerfulspringboot.domain.vo.user.LoginUserVO;
20 | import top.qtcc.qiutuanallpowerfulspringboot.domain.vo.user.UserVO;
21 | import top.qtcc.qiutuanallpowerfulspringboot.exception.BusinessException;
22 | import top.qtcc.qiutuanallpowerfulspringboot.exception.ThrowUtils;
23 | import top.qtcc.qiutuanallpowerfulspringboot.servicec.UserService;
24 |
25 | import javax.annotation.Resource;
26 | import javax.servlet.http.HttpServletRequest;
27 | import java.util.List;
28 |
29 | import static top.qtcc.qiutuanallpowerfulspringboot.servicec.impl.UserServiceImpl.SALT;
30 |
31 |
32 | /**
33 | * 用户接口
34 | *
35 | * @author qiutuan
36 | * @date 2024/11/02
37 | */
38 | @RestController
39 | @RequestMapping("/user")
40 | @Slf4j
41 | public class UserController {
42 |
43 | @Resource
44 | private UserService userService;
45 |
46 |
47 | /**
48 | * 用户注册
49 | *
50 | * @param userRegisterRequest 用户注册请求
51 | * @return 用户 id
52 | */
53 | @PostMapping("/register")
54 | public BaseResponse userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
55 | if (userRegisterRequest == null) {
56 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
57 | }
58 | String userAccount = userRegisterRequest.getUserAccount();
59 | String userPassword = userRegisterRequest.getUserPassword();
60 | String checkPassword = userRegisterRequest.getCheckPassword();
61 | if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
62 | return null;
63 | }
64 | long result = userService.userRegister(userAccount, userPassword, checkPassword);
65 | return ResultUtils.success(result);
66 | }
67 |
68 | /**
69 | * 用户登录
70 | *
71 | * @param userLoginRequest 用户登录请求
72 | * @param request 请求
73 | * @return 登录用户信息
74 | */
75 | @RateLimit(count = 30)
76 | @RepeatSubmit(interval = 3000)
77 | @PostMapping("/login")
78 | public BaseResponse userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
79 | if (userLoginRequest == null) {
80 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
81 | }
82 | String userAccount = userLoginRequest.getUserAccount();
83 | String userPassword = userLoginRequest.getUserPassword();
84 | if (StringUtils.isAnyBlank(userAccount, userPassword)) {
85 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
86 | }
87 | LoginUserVO loginUserVO = userService.userLogin(userAccount, userPassword, request);
88 | return ResultUtils.success(loginUserVO);
89 | }
90 |
91 | /**
92 | * 用户注销
93 | *
94 | * @param request 请求
95 | * @return 是否成功
96 | */
97 | @RateLimit(count = 10)
98 | @RepeatSubmit(interval = 3000)
99 | @PostMapping("/logout")
100 | public BaseResponse userLogout(HttpServletRequest request) {
101 | if (request == null) {
102 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
103 | }
104 | boolean result = userService.userLogout(request);
105 | return ResultUtils.success(result);
106 | }
107 |
108 | /**
109 | * 获取当前登录用户
110 | *
111 | * @param request 请求
112 | * @return 登录用户信息
113 | */
114 | @GetMapping("/get/login")
115 | public BaseResponse getLoginUser(HttpServletRequest request) {
116 | User user = userService.getLoginUser(request);
117 | return ResultUtils.success(userService.getLoginUserVO(user));
118 | }
119 |
120 | // endregion
121 |
122 | // region 增删改查
123 |
124 | /**
125 | * 创建用户
126 | *
127 | * @param userAddRequest 用户添加请求
128 | * @param request 请求
129 | * @return 用户 id
130 | */
131 | @PostMapping("/add")
132 | @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
133 | public BaseResponse addUser(@RequestBody UserAddRequest userAddRequest, HttpServletRequest request) {
134 | if (userAddRequest == null) {
135 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
136 | }
137 | User user = new User();
138 | BeanUtils.copyProperties(userAddRequest, user);
139 | // 默认密码 12345678
140 | String defaultPassword = "12345678";
141 | String encryptPassword = DigestUtils.md5DigestAsHex((SALT + defaultPassword).getBytes());
142 | user.setUserPassword(encryptPassword);
143 | boolean result = userService.save(user);
144 | ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
145 | return ResultUtils.success(user.getId());
146 | }
147 |
148 | /**
149 | * 删除用户
150 | *
151 | * @param deleteRequest 用户删除请求
152 | * @param request 请求
153 | * @return 是否成功
154 | */
155 | @PostMapping("/delete")
156 | @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
157 | public BaseResponse deleteUser(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
158 | if (deleteRequest == null || deleteRequest.getId() <= 0) {
159 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
160 | }
161 | boolean b = userService.removeById(deleteRequest.getId());
162 | return ResultUtils.success(b);
163 | }
164 |
165 | /**
166 | * 更新用户
167 | *
168 | * @param userUpdateRequest 用户更新请求
169 | * @param request 请求
170 | * @return 是否成功
171 | */
172 | @PostMapping("/update")
173 | @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
174 | public BaseResponse updateUser(@RequestBody UserUpdateRequest userUpdateRequest,
175 | HttpServletRequest request) {
176 | if (userUpdateRequest == null || userUpdateRequest.getId() == null) {
177 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
178 | }
179 | User user = new User();
180 | BeanUtils.copyProperties(userUpdateRequest, user);
181 | boolean result = userService.updateById(user);
182 | ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
183 | return ResultUtils.success(true);
184 | }
185 |
186 | /**
187 | * 根据 id 获取用户(仅管理员)
188 | *
189 | * @param id 用户 id
190 | * @param request 请求
191 | * @return 用户信息
192 | */
193 | @GetMapping("/get")
194 | @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
195 | public BaseResponse getUserById(long id, HttpServletRequest request) {
196 | if (id <= 0) {
197 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
198 | }
199 | User user = userService.getById(id);
200 | ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR);
201 | return ResultUtils.success(user);
202 | }
203 |
204 | /**
205 | * 根据 id 获取包装类
206 | *
207 | * @param id 用户 id
208 | * @param request 请求
209 | * @return 用户信息
210 | */
211 | @GetMapping("/get/vo")
212 | public BaseResponse getUserVOById(long id, HttpServletRequest request) {
213 | BaseResponse response = getUserById(id, request);
214 | User user = response.getData();
215 | return ResultUtils.success(userService.getUserVO(user));
216 | }
217 |
218 | /**
219 | * 分页获取用户列表(仅管理员)
220 | *
221 | * @param userQueryRequest 用户查询请求
222 | * @param request 请求
223 | * @return 用户列表
224 | */
225 | @RateLimit(count = 30)
226 | @RepeatSubmit(interval = 3000)
227 | @PostMapping("/list/page")
228 | @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
229 | public BaseResponse> listUserByPage(@RequestBody UserQueryRequest userQueryRequest,
230 | HttpServletRequest request) {
231 | long current = userQueryRequest.getCurrent();
232 | long size = userQueryRequest.getPageSize();
233 | Page userPage = userService.page(new Page<>(current, size),
234 | userService.getQueryWrapper(userQueryRequest));
235 | return ResultUtils.success(userPage);
236 | }
237 |
238 | /**
239 | * 分页获取用户封装列表
240 | *
241 | * @param userQueryRequest 用户查询请求
242 | * @param request 请求
243 | * @return 用户列表
244 | */
245 | @PostMapping("/list/page/vo")
246 | public BaseResponse> listUserVOByPage(@RequestBody UserQueryRequest userQueryRequest,
247 | HttpServletRequest request) {
248 | if (userQueryRequest == null) {
249 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
250 | }
251 | long current = userQueryRequest.getCurrent();
252 | long size = userQueryRequest.getPageSize();
253 | // 限制爬虫
254 | ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
255 | Page userPage = userService.page(new Page<>(current, size),
256 | userService.getQueryWrapper(userQueryRequest));
257 | Page userVOPage = new Page<>(current, size, userPage.getTotal());
258 | List userVO = userService.getUserVO(userPage.getRecords());
259 | userVOPage.setRecords(userVO);
260 | return ResultUtils.success(userVOPage);
261 | }
262 |
263 | // endregion
264 |
265 | /**
266 | * 更新个人信息
267 | *
268 | * @param userUpdateMyRequest 个人信息更新请求
269 | * @param request 请求
270 | * @return 是否成功
271 | */
272 | @PostMapping("/update/my")
273 | public BaseResponse updateMyUser(@RequestBody UserUpdateMyRequest userUpdateMyRequest,
274 | HttpServletRequest request) {
275 | if (userUpdateMyRequest == null) {
276 | throw new BusinessException(ErrorCode.PARAMS_ERROR);
277 | }
278 | User loginUser = userService.getLoginUser(request);
279 | User user = new User();
280 | BeanUtils.copyProperties(userUpdateMyRequest, user);
281 | user.setId(loginUser.getId());
282 | boolean result = userService.updateById(user);
283 | ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
284 | return ResultUtils.success(true);
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/dto/file/UploadFileRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.dto.file;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serial;
6 | import java.io.Serializable;
7 |
8 | /**
9 | * 文件上传请求
10 | *
11 | * @author qiutuan
12 | * @date 2024/11/02
13 | */
14 | @Data
15 | public class UploadFileRequest implements Serializable {
16 |
17 | /**
18 | * 业务
19 | */
20 | private String biz;
21 |
22 | @Serial
23 | private static final long serialVersionUID = 1L;
24 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/dto/user/UserAddRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 用户创建请求
9 | *
10 | * @author qiutuan
11 | * @date 2024/11/02
12 | */
13 | @Data
14 | public class UserAddRequest implements Serializable {
15 |
16 | /**
17 | * 用户昵称
18 | */
19 | private String userName;
20 |
21 | /**
22 | * 账号
23 | */
24 | private String userAccount;
25 |
26 | /**
27 | * 用户头像
28 | */
29 | private String userAvatar;
30 |
31 | /**
32 | * 用户角色: user, admin
33 | */
34 | private String userRole;
35 |
36 | private static final long serialVersionUID = 1L;
37 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/dto/user/UserLoginRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 用户登录请求
9 | *
10 | * @author qiutuan
11 | * @date 2024/11/02
12 | */
13 | @Data
14 | public class UserLoginRequest implements Serializable {
15 |
16 | private static final long serialVersionUID = 3191241716373120793L;
17 |
18 | private String userAccount;
19 |
20 | private String userPassword;
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/dto/user/UserQueryRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 | import top.qtcc.qiutuanallpowerfulspringboot.common.PageRequest;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * 用户查询请求
11 | *
12 | * @author qiutuan
13 | * @date 2024/11/02
14 | */
15 | @EqualsAndHashCode(callSuper = true)
16 | @Data
17 | public class UserQueryRequest extends PageRequest implements Serializable {
18 | /**
19 | * id
20 | */
21 | private Long id;
22 |
23 | /**
24 | * 开放平台id
25 | */
26 | private String unionId;
27 |
28 | /**
29 | * 公众号openId
30 | */
31 | private String mpOpenId;
32 |
33 | /**
34 | * 用户昵称
35 | */
36 | private String userName;
37 |
38 | /**
39 | * 简介
40 | */
41 | private String userProfile;
42 |
43 | /**
44 | * 用户角色
45 | */
46 | private String userRole;
47 |
48 | private static final long serialVersionUID = 1L;
49 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/dto/user/UserRegisterRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 用户注册请求体
9 | *
10 | * @author qiutuan
11 | * @date 2024/11/02
12 | */
13 | @Data
14 | public class UserRegisterRequest implements Serializable {
15 |
16 | private static final long serialVersionUID = 3191241716373120793L;
17 |
18 | private String userAccount;
19 |
20 | private String userPassword;
21 |
22 | private String checkPassword;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/dto/user/UserUpdateMyRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 用户更新个人信息请求
9 | *
10 | * @author qiutuan
11 | * @date 2024/11/02
12 | */
13 | @Data
14 | public class UserUpdateMyRequest implements Serializable {
15 |
16 | /**
17 | * 用户昵称
18 | */
19 | private String userName;
20 |
21 | /**
22 | * 用户头像
23 | */
24 | private String userAvatar;
25 |
26 | /**
27 | * 简介
28 | */
29 | private String userProfile;
30 |
31 | private static final long serialVersionUID = 1L;
32 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/dto/user/UserUpdateRequest.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 用户更新请求
9 | *
10 | * @author qiutuan
11 | * @date 2024/11/02
12 | */
13 | @Data
14 | public class UserUpdateRequest implements Serializable {
15 | /**
16 | * id
17 | */
18 | private Long id;
19 |
20 | /**
21 | * 用户昵称
22 | */
23 | private String userName;
24 |
25 | /**
26 | * 用户头像
27 | */
28 | private String userAvatar;
29 |
30 | /**
31 | * 简介
32 | */
33 | private String userProfile;
34 |
35 | /**
36 | * 用户角色
37 | */
38 | private String userRole;
39 |
40 | private static final long serialVersionUID = 1L;
41 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/entity/RequestLog.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.TableName;
4 | import lombok.Data;
5 |
6 | import java.time.LocalDateTime;
7 |
8 | /**
9 | * 请求日志
10 | *
11 | * @author qiutuan
12 | * @date 2024/12/10
13 | */
14 | @Data
15 | @TableName("request_log")
16 | public class RequestLog {
17 | private Long id;
18 | private String requestId;
19 | private String url;
20 | private String method;
21 | private String params;
22 | private String ip;
23 | private Long userId;
24 | private Integer status;
25 | private String errorMsg;
26 | private Long costTime;
27 | private LocalDateTime createTime;
28 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/entity/User.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.entity;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.io.Serializable;
8 | import java.sql.Timestamp;
9 |
10 | /**
11 | * 用户实体类
12 | *
13 | * @author qiutuan
14 | * @date 2024/12/10
15 | */
16 | @Data
17 | @AllArgsConstructor
18 | @NoArgsConstructor
19 | public class User implements Serializable {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | /**
24 | * 用户ID,主键,自增
25 | */
26 | private Long id;
27 |
28 | /**
29 | * 用户账号
30 | */
31 | private String userAccount;
32 |
33 | /**
34 | * 用户密码
35 | */
36 | private String userPassword;
37 |
38 |
39 | /**
40 | * 用户昵称
41 | */
42 | private String userName;
43 |
44 |
45 | /**
46 | * 用户头像
47 | */
48 | private String userAvatar;
49 |
50 |
51 | /**
52 | * 用户简介
53 | */
54 | private String userProfile;
55 |
56 | /**
57 | * 用户角色,默认为 'user'
58 | * 默认值为 'user',表示普通用户
59 | */
60 | private String userRole;
61 |
62 |
63 | /**
64 | * 创建时间,默认值为当前时间戳
65 | * 记录用户创建的时间
66 | */
67 | private Timestamp createTime;
68 |
69 |
70 | /**
71 | * 更新时间,每次更新记录时自动更新为当前时间戳
72 | * 记录用户最后更新的时间
73 | */
74 | private Timestamp updateTime;
75 |
76 |
77 | /**
78 | * 是否删除,0表示未删除,非0表示已删除
79 | * 用于逻辑删除,0表示未删除,1或其他值表示已删除
80 | */
81 | private Byte isDelete;
82 |
83 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/enums/ErrorCode.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.enums;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * 自定义错误码
7 | *
8 | * @author qiutuan
9 | * @date 2024/11/02
10 | */
11 | @Getter
12 | public enum ErrorCode {
13 | // 通用错误码
14 |
15 | SUCCESS(0, "ok"),
16 | PARAMS_ERROR(40000, "请求参数错误"),
17 | NOT_LOGIN_ERROR(40100, "未登录"),
18 | NO_AUTH_ERROR(40101, "无权限"),
19 | NOT_FOUND_ERROR(40400, "请求数据不存在"),
20 | FORBIDDEN_ERROR(40300, "禁止访问"),
21 | SYSTEM_ERROR(50000, "系统内部异常"),
22 | OPERATION_ERROR(50001, "操作失败");
23 |
24 | /**
25 | * 状态码
26 | */
27 | private final int code;
28 |
29 | /**
30 | * 信息
31 | */
32 | private final String message;
33 |
34 | ErrorCode(int code, String message) {
35 | this.code = code;
36 | this.message = message;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/enums/FileUploadBizEnum.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.enums;
2 |
3 | import lombok.Getter;
4 | import org.apache.commons.lang3.ObjectUtils;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.stream.Collectors;
9 |
10 | /**
11 | * 文件上传业务类型枚举
12 | *
13 | * @author qiutuan
14 | * @date 2024/11/02
15 | */
16 | @Getter
17 | public enum FileUploadBizEnum {
18 | // 文件上传业务类型枚举
19 |
20 | USER_AVATAR("用户头像", "user_avatar"),
21 | USER_FILE("用户文件", "user_file"),
22 | USER_IMAGE("用户图片", "user_image");
23 |
24 | private final String text;
25 |
26 | private final String value;
27 |
28 | FileUploadBizEnum(String text, String value) {
29 | this.text = text;
30 | this.value = value;
31 | }
32 |
33 | /**
34 | * 获取值列表
35 | *
36 | * @return 值列表
37 | */
38 | public static List getValues() {
39 | return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
40 | }
41 |
42 | /**
43 | * 根据 value 获取枚举
44 | *
45 | * @param value value
46 | * @return 枚举
47 | */
48 | public static FileUploadBizEnum getEnumByValue(String value) {
49 | if (ObjectUtils.isEmpty(value)) {
50 | return null;
51 | }
52 | for (FileUploadBizEnum anEnum : FileUploadBizEnum.values()) {
53 | if (anEnum.value.equals(value)) {
54 | return anEnum;
55 | }
56 | }
57 | return null;
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/enums/UserRoleEnum.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.enums;
2 |
3 | import lombok.Getter;
4 | import org.apache.commons.lang3.ObjectUtils;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.stream.Collectors;
9 |
10 | /**
11 | * 用户角色枚举
12 | *
13 | * @author qiutuan
14 | * @date 2024/11/02
15 | */
16 | @Getter
17 | public enum UserRoleEnum {
18 | // 用户角色枚举
19 |
20 | USER("用户", "user"),
21 | ADMIN("管理员", "admin"),
22 | BAN("被封号", "ban");
23 |
24 | private final String text;
25 |
26 | private final String value;
27 |
28 | UserRoleEnum(String text, String value) {
29 | this.text = text;
30 | this.value = value;
31 | }
32 |
33 | /**
34 | * 获取值列表
35 | *
36 | * @return 值列表
37 | */
38 | public static List getValues() {
39 | return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
40 | }
41 |
42 | /**
43 | * 根据 value 获取枚举
44 | *
45 | * @param value value
46 | * @return 枚举
47 | */
48 | public static UserRoleEnum getEnumByValue(String value) {
49 | if (ObjectUtils.isEmpty(value)) {
50 | return null;
51 | }
52 | for (UserRoleEnum anEnum : UserRoleEnum.values()) {
53 | if (anEnum.value.equals(value)) {
54 | return anEnum;
55 | }
56 | }
57 | return null;
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/vo/user/LoginUserVO.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.vo.user;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 | import java.util.Date;
7 |
8 | /**
9 | * 已登录用户视图(脱敏)
10 | *
11 | * @author qiutuan
12 | * @date 2024/11/02
13 | **/
14 | @Data
15 | public class LoginUserVO implements Serializable {
16 |
17 | private static final long serialVersionUID = 1L;
18 |
19 | /**
20 | * 用户 id
21 | */
22 | private Long id;
23 |
24 | /**
25 | * 用户昵称
26 | */
27 | private String userName;
28 |
29 | /**
30 | * 用户头像
31 | */
32 | private String userAvatar;
33 |
34 | /**
35 | * 用户简介
36 | */
37 | private String userProfile;
38 |
39 | /**
40 | * 用户角色
41 | */
42 | private String userRole;
43 |
44 | /**
45 | * 创建时间
46 | */
47 | private Date createTime;
48 |
49 | /**
50 | * 更新时间
51 | */
52 | private Date updateTime;
53 |
54 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/domain/vo/user/UserVO.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.domain.vo.user;
2 |
3 | import lombok.Data;
4 |
5 | import java.io.Serializable;
6 | import java.util.Date;
7 |
8 | /**
9 | * 用户视图(脱敏)
10 | *
11 | * @author qiutuan
12 | * @date 2024/11/02
13 | */
14 | @Data
15 | public class UserVO implements Serializable {
16 |
17 | /**
18 | * id
19 | */
20 | private Long id;
21 |
22 | /**
23 | * 用户昵称
24 | */
25 | private String userName;
26 |
27 | /**
28 | * 用户头像
29 | */
30 | private String userAvatar;
31 |
32 | /**
33 | * 用户简介
34 | */
35 | private String userProfile;
36 |
37 | /**
38 | * 用户角色
39 | */
40 | private String userRole;
41 |
42 | /**
43 | * 创建时间
44 | */
45 | private Date createTime;
46 |
47 | private static final long serialVersionUID = 1L;
48 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/exception/BusinessException.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.exception;
2 |
3 | import lombok.Getter;
4 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
5 |
6 | /**
7 | * 自定义异常类
8 | *
9 | * @author qiutuan
10 | * @date 2024/11/02
11 | */
12 | @Getter
13 | public class BusinessException extends RuntimeException {
14 |
15 | /**
16 | * 错误码
17 | */
18 | private final int code;
19 |
20 | public BusinessException(int code, String message) {
21 | super(message);
22 | this.code = code;
23 | }
24 |
25 | public BusinessException(ErrorCode errorCode) {
26 | super(errorCode.getMessage());
27 | this.code = errorCode.getCode();
28 | }
29 |
30 | public BusinessException(ErrorCode errorCode, String message) {
31 | super(message);
32 | this.code = errorCode.getCode();
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/exception/GlobalExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.exception;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.validation.BindException;
5 | import org.springframework.validation.FieldError;
6 | import org.springframework.web.bind.MethodArgumentNotValidException;
7 | import org.springframework.web.bind.annotation.ExceptionHandler;
8 | import org.springframework.web.bind.annotation.RestControllerAdvice;
9 | import top.qtcc.qiutuanallpowerfulspringboot.common.BaseResponse;
10 | import top.qtcc.qiutuanallpowerfulspringboot.common.ResultUtils;
11 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
12 |
13 | import javax.validation.ConstraintViolation;
14 | import javax.validation.ConstraintViolationException;
15 | import java.util.List;
16 | import java.util.Set;
17 | import java.util.stream.Collectors;
18 |
19 | /**
20 | * 全局异常处理器
21 | *
22 | * @author qiutuan
23 | * @date 2024/11/02
24 | */
25 | @RestControllerAdvice
26 | @Slf4j
27 | public class GlobalExceptionHandler {
28 |
29 | /**
30 | * 处理自定义异常
31 | *
32 | * @param e {@link BusinessException }
33 | * @return {@link BaseResponse }<{@link ? }>
34 | */
35 | @ExceptionHandler(BusinessException.class)
36 | public BaseResponse> handleBusinessException(BusinessException e) {
37 | log.error("业务异常", e);
38 | return ResultUtils.error(e.getCode(), e.getMessage());
39 | }
40 |
41 | /**
42 | * 处理参数校验异常
43 | *
44 | * @param e {@link MethodArgumentNotValidException }
45 | * @return {@link BaseResponse }<{@link ? }>
46 | */
47 | @ExceptionHandler(MethodArgumentNotValidException.class)
48 | public BaseResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
49 | List fieldErrors = e.getBindingResult().getFieldErrors();
50 | String message = fieldErrors.stream()
51 | .map(FieldError::getDefaultMessage)
52 | .collect(Collectors.joining(", "));
53 | return ResultUtils.error(ErrorCode.PARAMS_ERROR.getCode(), message);
54 | }
55 |
56 | /**
57 | * 处理参数校验异常
58 | *
59 | * @param e {@link ConstraintViolationException }
60 | * @return {@link BaseResponse }<{@link ? }>
61 | */
62 | @ExceptionHandler(ConstraintViolationException.class)
63 | public BaseResponse> handleConstraintViolationException(ConstraintViolationException e) {
64 | Set> violations = e.getConstraintViolations();
65 | String message = violations.stream()
66 | .map(ConstraintViolation::getMessage)
67 | .collect(Collectors.joining(", "));
68 | return ResultUtils.error(ErrorCode.PARAMS_ERROR.getCode(), message);
69 | }
70 |
71 | /**
72 | * 处理参数校验异常
73 | *
74 | * @param e {@link BindException }
75 | * @return {@link BaseResponse }<{@link ? }>
76 | */
77 | @ExceptionHandler(BindException.class)
78 | public BaseResponse> handleBindException(BindException e) {
79 | List fieldErrors = e.getBindingResult().getFieldErrors();
80 | String message = fieldErrors.stream()
81 | .map(FieldError::getDefaultMessage)
82 | .collect(Collectors.joining(", "));
83 | return ResultUtils.error(ErrorCode.PARAMS_ERROR.getCode(), message);
84 | }
85 |
86 | /**
87 | * 处理系统异常
88 | *
89 | * @param e {@link Exception }
90 | * @return {@link BaseResponse }<{@link ? }>
91 | */
92 | @ExceptionHandler(Exception.class)
93 | public BaseResponse> handleException(Exception e) {
94 | log.error("系统异常", e);
95 | return ResultUtils.error(ErrorCode.SYSTEM_ERROR.getCode(), "系统异常");
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/exception/ThrowUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.exception;
2 |
3 |
4 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
5 |
6 | /**
7 | * 抛异常工具类
8 | *
9 | * @author qiutuan
10 | * @date 2024/11/02
11 | */
12 | public class ThrowUtils {
13 |
14 | /**
15 | * 条件成立则抛异常
16 | *
17 | * @param condition 条件
18 | * @param runtimeException 异常
19 | */
20 | public static void throwIf(boolean condition, RuntimeException runtimeException) {
21 | if (condition) {
22 | throw runtimeException;
23 | }
24 | }
25 |
26 | /**
27 | * 条件成立则抛异常
28 | *
29 | * @param condition 条件
30 | * @param errorCode 错误码
31 | */
32 | public static void throwIf(boolean condition, ErrorCode errorCode) {
33 | throwIf(condition, new BusinessException(errorCode));
34 | }
35 |
36 | /**
37 | * 条件成立则抛异常
38 | *
39 | * @param condition 条件
40 | * @param errorCode 错误码
41 | * @param message 信息
42 | */
43 | public static void throwIf(boolean condition, ErrorCode errorCode, String message) {
44 | throwIf(condition, new BusinessException(errorCode, message));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/filter/TraceIdFilter.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.filter;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.slf4j.MDC;
5 | import org.springframework.core.annotation.Order;
6 | import org.springframework.stereotype.Component;
7 | import org.springframework.web.filter.OncePerRequestFilter;
8 |
9 | import javax.servlet.FilterChain;
10 | import javax.servlet.ServletException;
11 | import javax.servlet.http.HttpServletRequest;
12 | import javax.servlet.http.HttpServletResponse;
13 | import java.io.IOException;
14 | import java.util.UUID;
15 |
16 | /**
17 | * 日志链路追踪
18 | *
19 | * @author qiutuan
20 | * @date 2024/12/06
21 | */
22 | @Component
23 | @Order(1)
24 | public class TraceIdFilter extends OncePerRequestFilter {
25 |
26 | private static final String TRACE_ID = "traceId";
27 |
28 | /**
29 | * 日志链路追踪
30 | *
31 | * @param request 请求
32 | * @param response 响应
33 | * @param filterChain 过滤器链
34 | * @throws ServletException 异常
35 | * @throws IOException 异常
36 | */
37 | @Override
38 | protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
39 | FilterChain filterChain) throws ServletException, IOException {
40 | try {
41 | String traceId = UUID.randomUUID().toString().replace("-", "");
42 | MDC.put(TRACE_ID, traceId);
43 | filterChain.doFilter(request, response);
44 | } finally {
45 | MDC.remove(TRACE_ID);
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/filter/XssFilter.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.filter;
2 |
3 | import org.springframework.core.annotation.Order;
4 | import org.springframework.stereotype.Component;
5 |
6 | import javax.servlet.*;
7 | import javax.servlet.annotation.WebFilter;
8 | import javax.servlet.http.HttpServletRequest;
9 | import java.io.IOException;
10 |
11 | /**
12 | * 防止XSS攻击 Filter
13 | *
14 | * @author qiutuan
15 | * @date 2024/12/07
16 | */
17 | @Component
18 | @WebFilter(urlPatterns = "/*")
19 | @Order(1)
20 | public class XssFilter implements Filter {
21 |
22 | @Override
23 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
24 | throws IOException, ServletException {
25 | XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
26 | (HttpServletRequest) request);
27 | chain.doFilter(xssRequest, response);
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/filter/XssHttpServletRequestWrapper.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.filter;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.apache.commons.text.StringEscapeUtils;
5 |
6 | import javax.servlet.http.HttpServletRequest;
7 | import javax.servlet.http.HttpServletRequestWrapper;
8 |
9 | /**
10 | * 防止XSS攻击
11 | *
12 | * @author qiutuan
13 | * @date 2024/12/07
14 | */
15 | public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
16 |
17 | public XssHttpServletRequestWrapper(HttpServletRequest request) {
18 | super(request);
19 | }
20 |
21 | /**
22 | * 重写getParameter方法,对获取的参数值进行HTML转义,防止XSS攻击
23 | *
24 | * @param name 参数名
25 | * @return {@link String }
26 | */
27 | @Override
28 | public String getParameter(String name) {
29 | String value = super.getParameter(name);
30 | return StringUtils.isNotBlank(value) ? StringEscapeUtils.escapeHtml4(value) : value;
31 | }
32 |
33 | @Override
34 | public String[] getParameterValues(String name) {
35 | String[] values = super.getParameterValues(name);
36 | if (values != null) {
37 | int length = values.length;
38 | String[] escapeValues = new String[length];
39 | for (int i = 0; i < length; i++) {
40 | escapeValues[i] = StringUtils.isNotBlank(values[i]) ?
41 | StringEscapeUtils.escapeHtml4(values[i]) : values[i];
42 | }
43 | return escapeValues;
44 | }
45 | return null;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/interceptor/EncryptInterceptor.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.interceptor;
2 |
3 | import cn.hutool.crypto.SecureUtil;
4 | import cn.hutool.crypto.symmetric.AES;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.springframework.web.servlet.HandlerInterceptor;
7 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
8 | import top.qtcc.qiutuanallpowerfulspringboot.exception.BusinessException;
9 |
10 | import javax.servlet.http.HttpServletRequest;
11 | import javax.servlet.http.HttpServletResponse;
12 | import java.nio.charset.StandardCharsets;
13 |
14 | /**
15 | * 加密拦截器
16 | *
17 | * @author qiutuan
18 | * @date 2024/12/10
19 | */
20 | public class EncryptInterceptor implements HandlerInterceptor {
21 |
22 | /**
23 | * 16位密钥
24 | */
25 | private static final String SECRET_KEY = "your_secret_key_16";
26 | private static final AES AES = SecureUtil.aes(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
27 |
28 | @Override
29 | public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
30 | try {
31 | // 获取加密的请求数据
32 | String encryptedData = request.getHeader("X-Encrypted-Data");
33 | if (encryptedData != null) {
34 | // 解密数据
35 | String decryptedData = AES.decryptStr(encryptedData);
36 | // 将解密后的数据设置到request属性中
37 | request.setAttribute("decryptedData", decryptedData);
38 | }
39 | return true;
40 | } catch (Exception e) {
41 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "数据解密失败");
42 | }
43 | }
44 |
45 | @Override
46 | public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
47 | @NotNull Object handler, Exception ex) {
48 | try {
49 | // 获取响应数据
50 | String responseData = response.getHeader("X-Response-Data");
51 | if (responseData != null) {
52 | // 加密响应数据
53 | String encryptedData = AES.encryptHex(responseData);
54 | // 设置加密后的响应数据
55 | response.setHeader("X-Encrypted-Response", encryptedData);
56 | }
57 | } catch (Exception e) {
58 | throw new BusinessException(ErrorCode.SYSTEM_ERROR, "数据加密失败");
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/manager/file/CosManager.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.manager.file;
2 |
3 | import com.qcloud.cos.COSClient;
4 | import com.qcloud.cos.model.PutObjectRequest;
5 | import org.springframework.stereotype.Component;
6 | import top.qtcc.qiutuanallpowerfulspringboot.config.CosClientConfig;
7 |
8 | import javax.annotation.Resource;
9 | import java.io.File;
10 |
11 | /**
12 | * Cos 对象存储操作
13 | *
14 | * @author qiutuan
15 | * @date 2024/11/02
16 | */
17 | @Component("CosManager")
18 | public class CosManager implements FileManager {
19 |
20 | @Resource
21 | private CosClientConfig cosClientConfig;
22 |
23 | @Resource
24 | private COSClient cosClient;
25 |
26 | /**
27 | * 上传对象
28 | *
29 | * @param key 唯一键
30 | * @param localFilePath 本地文件路径
31 | * @return 上传结果
32 | */
33 | @Override
34 | public void putObject(String key, String localFilePath) {
35 | PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
36 | new File(localFilePath));
37 | cosClient.putObject(putObjectRequest);
38 | }
39 |
40 | /**
41 | * 上传对象
42 | *
43 | * @param key 唯一键
44 | * @param file 文件
45 | */
46 | @Override
47 | public void putObject(String key, File file) {
48 | PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
49 | file);
50 | cosClient.putObject(putObjectRequest);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/manager/file/FileManager.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.manager.file;
2 |
3 | import java.io.File;
4 |
5 | /**
6 | * @author qiutuan
7 | */
8 | public interface FileManager {
9 |
10 | /**
11 | * 上传对象
12 | *
13 | * @param key 文件名
14 | * @param localFilePath 本地文件路径
15 | */
16 | public void putObject(String key, String localFilePath);
17 |
18 | /**
19 | * 上传对象
20 | *
21 | * @param key 文件名
22 | * @param file 文件
23 | */
24 | public void putObject(String key, File file);
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/manager/file/MinioManager.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.manager.file;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import io.minio.MinioClient;
5 | import io.minio.PutObjectArgs;
6 | import io.minio.RemoveObjectArgs;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.stereotype.Component;
9 | import top.qtcc.qiutuanallpowerfulspringboot.config.MinioClientConfig;
10 |
11 | import javax.annotation.Resource;
12 | import java.io.File;
13 |
14 | /**
15 | * COS 管理器
16 | *
17 | * @author qiutuan
18 | * @date 2024/11/16
19 | */
20 | @Slf4j
21 | @Component("MinioManager")
22 | public class MinioManager implements FileManager {
23 |
24 | @Resource
25 | private MinioClientConfig minioClientConfig;
26 |
27 | @Resource
28 | private MinioClient minioClient;
29 |
30 |
31 | /**
32 | * 上传对象
33 | *
34 | * @param key 唯一键
35 | * @param localFilePath 本地文件路径
36 | */
37 | @Override
38 | public void putObject(String key, String localFilePath) {
39 | try {
40 | PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(minioClientConfig.getBucket()).object(key)
41 | .stream(FileUtil.getInputStream(localFilePath), new File(localFilePath).length(), -1).contentType("application/octet-stream").build();
42 | minioClient.putObject(objectArgs);
43 | } catch (Exception e) {
44 | log.error("上传对象失败", e);
45 | }
46 | }
47 |
48 | /**
49 | * 上传对象
50 | *
51 | * @param key 唯一键
52 | * @param file 文件
53 | */
54 | @Override
55 | public void putObject(String key, File file) {
56 |
57 | try {
58 | PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(minioClientConfig.getBucket()).object(key)
59 | .stream(FileUtil.getInputStream(file), file.length(), -1).contentType("application/octet-stream").build();
60 | minioClient.putObject(objectArgs);
61 | } catch (Exception e) {
62 | log.error("上传对象失败", e);
63 | }
64 | }
65 |
66 | /**
67 | * 删除
68 | *
69 | * @param fileName 文件名
70 | * @return 是否删除成功
71 | */
72 | public boolean remove(String fileName) {
73 | try {
74 | minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioClientConfig.getBucket()).object(fileName).build());
75 | } catch (Exception e) {
76 | return false;
77 | }
78 | return true;
79 | }
80 |
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/manager/file/proxy/FileManagerProxy.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.manager.file.proxy;
2 |
3 | import org.springframework.beans.factory.annotation.Qualifier;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import top.qtcc.qiutuanallpowerfulspringboot.manager.file.FileManager;
8 |
9 | /**
10 | * 文件管理器代理
11 | *
12 | * @author qiutuan
13 | * @date 2024/11/18
14 | */
15 | @Configuration
16 | public class FileManagerProxy {
17 |
18 | @Value("${file.manager}")
19 | private String fileManagerClassName;
20 |
21 | @Bean
22 | public FileManager fileManager(@Qualifier("CosManager") FileManager cosManager,
23 | @Qualifier("MinioManager") FileManager minioManager) {
24 | if ("CosManager".equals(fileManagerClassName)) {
25 | return cosManager;
26 | }
27 | return minioManager;
28 |
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/mapper/RequestLogMapper.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.mapper;
2 |
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4 | import org.apache.ibatis.annotations.Delete;
5 | import org.apache.ibatis.annotations.Mapper;
6 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.RequestLog;
7 |
8 | import java.time.LocalDateTime;
9 |
10 | /**
11 | * 请求日志Mapper接口
12 | *
13 | * @author qiutuan
14 | * @date 2024/12/07
15 | */
16 | @Mapper
17 | public interface RequestLogMapper extends BaseMapper {
18 |
19 | /**
20 | * 删除过期日志
21 | *
22 | * @param expireTime 过期时间
23 | */
24 | @Delete("DELETE FROM request_log WHERE create_time < #{expireTime}")
25 | void deleteExpiredLogs(LocalDateTime expireTime);
26 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/mapper/UserMapper.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.mapper;
2 |
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.User;
5 |
6 |
7 | /**
8 | * 用户数据库操作
9 | *
10 | * @author qiutuan
11 | * @date 2024/11/02
12 | */
13 | public interface UserMapper extends BaseMapper {
14 |
15 | }
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/service/RequestLogService.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.service;
2 |
3 | import com.baomidou.mybatisplus.extension.service.IService;
4 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.RequestLog;
5 |
6 | /**
7 | * 请求日志服务接口
8 | *
9 | * @author qiutuan
10 | * @date 2024/12/07
11 | */
12 | public interface RequestLogService extends IService {
13 |
14 | /**
15 | * 异步保存请求日志
16 | *
17 | * @param requestLog 请求日志
18 | */
19 | void asyncSave(RequestLog requestLog);
20 |
21 | /**
22 | * 清理过期日志
23 | */
24 | void cleanExpiredLogs();
25 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/service/impl/RequestLogServiceImpl.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.service.impl;
2 |
3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.scheduling.annotation.Async;
6 | import org.springframework.scheduling.annotation.Scheduled;
7 | import org.springframework.stereotype.Service;
8 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.RequestLog;
9 | import top.qtcc.qiutuanallpowerfulspringboot.mapper.RequestLogMapper;
10 | import top.qtcc.qiutuanallpowerfulspringboot.service.RequestLogService;
11 |
12 | import java.time.LocalDateTime;
13 |
14 | /**
15 | * 请求日志服务实现类
16 | *
17 | * @author qiutuan
18 | * @date 2024/12/07
19 | */
20 | @Slf4j
21 | @Service
22 | public class RequestLogServiceImpl extends ServiceImpl implements RequestLogService {
23 |
24 | private static final Integer DAY = 30;
25 |
26 | /**
27 | * 异步保存请求日志
28 | *
29 | * @param requestLog 请求日志
30 | */
31 | @Async("asyncExecutor")
32 | @Override
33 | public void asyncSave(RequestLog requestLog) {
34 | try {
35 | this.save(requestLog);
36 | } catch (Exception e) {
37 | log.error("保存请求日志失败", e);
38 | }
39 | }
40 |
41 | /**
42 | * 每天凌晨2点执行清理30天前的日志
43 | */
44 | @Scheduled(cron = "0 0 2 * * ?")
45 | @Override
46 | public void cleanExpiredLogs() {
47 | try {
48 | LocalDateTime expireTime = LocalDateTime.now().minusDays(DAY);
49 | this.baseMapper.deleteExpiredLogs(expireTime);
50 | log.info("清理{}天前的请求日志成功", DAY);
51 | } catch (Exception e) {
52 | log.error("清理请求日志失败", e);
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/servicec/UserService.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.servicec;
2 |
3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
4 | import com.baomidou.mybatisplus.extension.service.IService;
5 | import top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user.UserQueryRequest;
6 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.User;
7 | import top.qtcc.qiutuanallpowerfulspringboot.domain.vo.user.LoginUserVO;
8 | import top.qtcc.qiutuanallpowerfulspringboot.domain.vo.user.UserVO;
9 |
10 | import javax.servlet.http.HttpServletRequest;
11 | import java.util.List;
12 |
13 | /**
14 | * 用户服务
15 | *
16 | * @author qiutuan
17 | * @date 2024/11/02
18 | */
19 | public interface UserService extends IService {
20 |
21 | /**
22 | * 用户注册
23 | *
24 | * @param userAccount 用户账户
25 | * @param userPassword 用户密码
26 | * @param checkPassword 校验密码
27 | * @return 新用户 id
28 | */
29 | long userRegister(String userAccount, String userPassword, String checkPassword);
30 |
31 | /**
32 | * 用户登录
33 | *
34 | * @param userAccount 用户账户
35 | * @param userPassword 用户密码
36 | * @param request 请求
37 | * @return 脱敏后的用户信息
38 | */
39 | LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request);
40 |
41 |
42 | /**
43 | * 获取当前登录用户
44 | *
45 | * @param request 请求
46 | * @return 当前登录用户
47 | */
48 | User getLoginUser(HttpServletRequest request);
49 |
50 | /**
51 | * 获取当前登录用户(允许未登录)
52 | *
53 | * @param request 请求
54 | * @return 当前登录用户
55 | */
56 | User getLoginUserPermitNull(HttpServletRequest request);
57 |
58 | /**
59 | * 是否为管理员
60 | *
61 | * @param request 请求
62 | * @return 是否为管理员
63 | */
64 | boolean isAdmin(HttpServletRequest request);
65 |
66 | /**
67 | * 是否为管理员
68 | *
69 | * @param user 用户
70 | * @return 是否为管理员
71 | */
72 | boolean isAdmin(User user);
73 |
74 | /**
75 | * 用户注销
76 | *
77 | * @param request 请求
78 | * @return 是否注销成功
79 | */
80 | boolean userLogout(HttpServletRequest request);
81 |
82 | /**
83 | * 获取脱敏的已登录用户信息
84 | *
85 | * @param user 用户
86 | * @return 脱敏后的用户信息
87 | */
88 | LoginUserVO getLoginUserVO(User user);
89 |
90 | /**
91 | * 获取脱敏的用户信息
92 | *
93 | * @param user 用户
94 | * @return 脱敏后的用户信息
95 | */
96 | UserVO getUserVO(User user);
97 |
98 | /**
99 | * 获取脱敏的用户信息
100 | *
101 | * @param userList 用户列表
102 | * @return 脱敏后的用户信息
103 | */
104 | List getUserVO(List userList);
105 |
106 | /**
107 | * 获取查询条件
108 | *
109 | * @param userQueryRequest 用户查询请求
110 | * @return 查询条件
111 | */
112 | QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest);
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/servicec/impl/UserServiceImpl.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.servicec.impl;
2 |
3 | import cn.hutool.core.collection.CollUtil;
4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.apache.commons.lang3.StringUtils;
8 | import org.springframework.beans.BeanUtils;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.util.DigestUtils;
11 | import top.qtcc.qiutuanallpowerfulspringboot.constant.CommonConstant;
12 | import top.qtcc.qiutuanallpowerfulspringboot.domain.dto.user.UserQueryRequest;
13 | import top.qtcc.qiutuanallpowerfulspringboot.domain.entity.User;
14 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.ErrorCode;
15 | import top.qtcc.qiutuanallpowerfulspringboot.domain.enums.UserRoleEnum;
16 | import top.qtcc.qiutuanallpowerfulspringboot.domain.vo.user.LoginUserVO;
17 | import top.qtcc.qiutuanallpowerfulspringboot.domain.vo.user.UserVO;
18 | import top.qtcc.qiutuanallpowerfulspringboot.exception.BusinessException;
19 | import top.qtcc.qiutuanallpowerfulspringboot.mapper.UserMapper;
20 | import top.qtcc.qiutuanallpowerfulspringboot.servicec.UserService;
21 | import top.qtcc.qiutuanallpowerfulspringboot.utils.SqlUtils;
22 |
23 | import javax.servlet.http.HttpServletRequest;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 | import java.util.stream.Collectors;
27 |
28 | import static top.qtcc.qiutuanallpowerfulspringboot.constant.UserConstant.USER_LOGIN_STATE;
29 |
30 | /**
31 | * 用户服务实现
32 | *
33 | * @author qiutuan
34 | * @date 2024/11/02
35 | */
36 | @Service
37 | @Slf4j
38 | public class UserServiceImpl extends ServiceImpl implements UserService {
39 |
40 | /**
41 | * 盐值,混淆密码
42 | */
43 | public static final String SALT = "qiutuan";
44 |
45 | @Override
46 | public long userRegister(String userAccount, String userPassword, String checkPassword) {
47 | // 1. 校验
48 | if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
49 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
50 | }
51 | if (userAccount.length() < 4) {
52 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
53 | }
54 | if (userPassword.length() < 8 || checkPassword.length() < 8) {
55 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
56 | }
57 | // 密码和校验密码相同
58 | if (!userPassword.equals(checkPassword)) {
59 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
60 | }
61 | synchronized (userAccount.intern()) {
62 | // 账户不能重复
63 | QueryWrapper queryWrapper = new QueryWrapper<>();
64 | queryWrapper.eq("user_account", userAccount);
65 | long count = this.baseMapper.selectCount(queryWrapper);
66 | if (count > 0) {
67 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
68 | }
69 | // 2. 加密
70 | String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
71 | // 3. 插入数据
72 | User user = new User();
73 | user.setUserAccount(userAccount);
74 | user.setUserPassword(encryptPassword);
75 | //默认用户名
76 | user.setUserName(userAccount);
77 | //默认头像
78 | user.setUserAvatar("http://img.qtcc.top/i/2024/11/02/48h8hm.png");
79 | boolean saveResult = this.save(user);
80 | if (!saveResult) {
81 | throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误");
82 | }
83 | return user.getId();
84 | }
85 | }
86 |
87 | @Override
88 | public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
89 | // 1. 校验
90 | if (StringUtils.isAnyBlank(userAccount, userPassword)) {
91 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
92 | }
93 | if (userAccount.length() < 4) {
94 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");
95 | }
96 | if (userPassword.length() < 8) {
97 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
98 | }
99 | // 2. 加密
100 | String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
101 | // 查询用户是否存在
102 | QueryWrapper queryWrapper = new QueryWrapper<>();
103 | queryWrapper.eq("user_account", userAccount);
104 | queryWrapper.eq("user_password", encryptPassword);
105 | User user = this.baseMapper.selectOne(queryWrapper);
106 | // 用户不存在
107 | if (user == null) {
108 | log.info("user login failed, userAccount cannot match userPassword");
109 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
110 | }
111 | // 3. 记录用户的登录态
112 | request.getSession().setAttribute(USER_LOGIN_STATE, user);
113 | return this.getLoginUserVO(user);
114 | }
115 |
116 | /**
117 | * 获取当前登录用户
118 | *
119 | * @param request 请求
120 | * @return 当前登录用户
121 | */
122 | @Override
123 | public User getLoginUser(HttpServletRequest request) {
124 | // 先判断是否已登录
125 | Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
126 | User currentUser = (User) userObj;
127 | if (currentUser == null || currentUser.getId() == null) {
128 | throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
129 | }
130 | // 从数据库查询(追求性能的话可以注释,直接走缓存)
131 | long userId = currentUser.getId();
132 | currentUser = this.getById(userId);
133 | if (currentUser == null) {
134 | throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
135 | }
136 | return currentUser;
137 | }
138 |
139 | /**
140 | * 获取当前登录用户(允许未登录)
141 | *
142 | * @param request 请求
143 | * @return 当前登录用户
144 | */
145 | @Override
146 | public User getLoginUserPermitNull(HttpServletRequest request) {
147 | // 先判断是否已登录
148 | Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
149 | User currentUser = (User) userObj;
150 | if (currentUser == null || currentUser.getId() == null) {
151 | return null;
152 | }
153 | // 从数据库查询(追求性能的话可以注释,直接走缓存)
154 | long userId = currentUser.getId();
155 | return this.getById(userId);
156 | }
157 |
158 | /**
159 | * 是否为管理员
160 | *
161 | * @param request 请求
162 | * @return 是否为管理员
163 | */
164 | @Override
165 | public boolean isAdmin(HttpServletRequest request) {
166 | // 仅管理员可查询
167 | Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
168 | User user = (User) userObj;
169 | return isAdmin(user);
170 | }
171 |
172 | @Override
173 | public boolean isAdmin(User user) {
174 | return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
175 | }
176 |
177 | /**
178 | * 用户注销
179 | *
180 | * @param request 请求
181 | */
182 | @Override
183 | public boolean userLogout(HttpServletRequest request) {
184 | if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) {
185 | throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
186 | }
187 | // 移除登录态
188 | request.getSession().removeAttribute(USER_LOGIN_STATE);
189 | return true;
190 | }
191 |
192 | @Override
193 | public LoginUserVO getLoginUserVO(User user) {
194 | if (user == null) {
195 | return null;
196 | }
197 | LoginUserVO loginUserVO = new LoginUserVO();
198 | BeanUtils.copyProperties(user, loginUserVO);
199 | return loginUserVO;
200 | }
201 |
202 | @Override
203 | public UserVO getUserVO(User user) {
204 | if (user == null) {
205 | return null;
206 | }
207 | UserVO userVO = new UserVO();
208 | BeanUtils.copyProperties(user, userVO);
209 | return userVO;
210 | }
211 |
212 | @Override
213 | public List getUserVO(List userList) {
214 | if (CollUtil.isEmpty(userList)) {
215 | return new ArrayList<>();
216 | }
217 | return userList.stream().map(this::getUserVO).collect(Collectors.toList());
218 | }
219 |
220 | @Override
221 | public QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest) {
222 | if (userQueryRequest == null) {
223 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
224 | }
225 | Long id = userQueryRequest.getId();
226 | String unionId = userQueryRequest.getUnionId();
227 | String mpOpenId = userQueryRequest.getMpOpenId();
228 | String userName = userQueryRequest.getUserName();
229 | String userProfile = userQueryRequest.getUserProfile();
230 | String userRole = userQueryRequest.getUserRole();
231 | String sortField = userQueryRequest.getSortField();
232 | String sortOrder = userQueryRequest.getSortOrder();
233 | QueryWrapper queryWrapper = new QueryWrapper<>();
234 | queryWrapper.eq(id != null, "id", id);
235 | queryWrapper.eq(StringUtils.isNotBlank(unionId), "unionId", unionId);
236 | queryWrapper.eq(StringUtils.isNotBlank(mpOpenId), "mpOpenId", mpOpenId);
237 | queryWrapper.eq(StringUtils.isNotBlank(userRole), "userRole", userRole);
238 | queryWrapper.like(StringUtils.isNotBlank(userProfile), "userProfile", userProfile);
239 | queryWrapper.like(StringUtils.isNotBlank(userName), "userName", userName);
240 | queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
241 | sortField);
242 | return queryWrapper;
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/utils/DateUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.utils;
2 |
3 | import java.time.LocalDateTime;
4 | import java.time.ZoneId;
5 | import java.time.format.DateTimeFormatter;
6 | import java.util.Date;
7 |
8 | /**
9 | * 日期工具类
10 | *
11 | * @author qiutuan
12 | * @date 2024/12/07
13 | */
14 | public class DateUtils {
15 |
16 | public static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";
17 | public static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_PATTERN);
18 |
19 | /**
20 | * LocalDateTime转String
21 | */
22 | public static String formatDateTime(LocalDateTime dateTime) {
23 | return dateTime.format(DEFAULT_FORMATTER);
24 | }
25 |
26 | /**
27 | * String转LocalDateTime
28 | *
29 | * @param dateStr 日期字符串
30 | * @return {@link LocalDateTime }
31 | */
32 | public static LocalDateTime parseDateTime(String dateStr) {
33 | return LocalDateTime.parse(dateStr, DEFAULT_FORMATTER);
34 | }
35 |
36 | /**
37 | * Date转LocalDateTime
38 | *
39 | * @param date 日期
40 | * @return {@link LocalDateTime }
41 | */
42 | public static LocalDateTime dateToLocalDateTime(Date date) {
43 | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
44 | }
45 |
46 | /**
47 | * LocalDateTime转Date
48 | *
49 | * @param localDateTime 日期
50 | * @return {@link Date }
51 | */
52 | public static Date localDateTimeToDate(LocalDateTime localDateTime) {
53 | return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
54 | }
55 |
56 | /**
57 | * 获取当前时间字符串
58 | *
59 | * @return {@link String }
60 | */
61 | public static String getCurrentDateTimeStr() {
62 | return formatDateTime(LocalDateTime.now());
63 | }
64 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/utils/EncryptUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.utils;
2 |
3 | import org.springframework.util.DigestUtils;
4 | import javax.crypto.Cipher;
5 | import javax.crypto.spec.SecretKeySpec;
6 | import java.nio.charset.StandardCharsets;
7 | import java.util.Base64;
8 |
9 | /**
10 | * 加密工具类
11 | *
12 | * @author qiutuan
13 | * @date 2024/12/07
14 | */
15 | public class EncryptUtils {
16 |
17 |
18 | /**
19 | * AES加密密钥
20 | * TODO AES密钥需要16位
21 | */
22 | private static final String AES_KEY = "your-secret-key-16";
23 |
24 | /**
25 | * MD5加密
26 | */
27 | public static String md5(String str) {
28 | return DigestUtils.md5DigestAsHex(str.getBytes(StandardCharsets.UTF_8));
29 | }
30 |
31 | /**
32 | * AES加密
33 | */
34 | public static String aesEncrypt(String data) throws Exception {
35 | Cipher cipher = Cipher.getInstance("AES");
36 | SecretKeySpec key = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
37 | cipher.init(Cipher.ENCRYPT_MODE, key);
38 | byte[] encryptedBytes = cipher.doFinal(data.getBytes());
39 | return Base64.getEncoder().encodeToString(encryptedBytes);
40 | }
41 |
42 | /**
43 | * AES解密
44 | */
45 | public static String aesDecrypt(String encryptedData) throws Exception {
46 | Cipher cipher = Cipher.getInstance("AES");
47 | SecretKeySpec key = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
48 | cipher.init(Cipher.DECRYPT_MODE, key);
49 | byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
50 | return new String(decryptedBytes);
51 | }
52 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.utils;
2 |
3 | import org.springframework.web.multipart.MultipartFile;
4 | import java.io.*;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.nio.file.Paths;
8 | import java.util.UUID;
9 |
10 | /**
11 | * 文件工具类
12 | *
13 | * @author qiutuan
14 | * @date 2024/12/07
15 | */
16 | public class FileUtils {
17 |
18 | /**
19 | * 获取文件扩展名
20 | */
21 | public static String getExtension(String fileName) {
22 | if (fileName == null) {
23 | return null;
24 | }
25 | int index = fileName.lastIndexOf(".");
26 | if (index == -1) {
27 | return "";
28 | }
29 | return fileName.substring(index + 1);
30 | }
31 |
32 | /**
33 | * 生成唯一文件名
34 | */
35 | public static String generateUniqueFileName(String originalFileName) {
36 | String extension = getExtension(originalFileName);
37 | return UUID.randomUUID() + (extension.isEmpty() ? "" : "." + extension);
38 | }
39 |
40 | /**
41 | * 保存文件
42 | */
43 | public static String saveFile(MultipartFile file, String directory) throws IOException {
44 | if (file.isEmpty()) {
45 | throw new IllegalArgumentException("文件为空");
46 | }
47 |
48 | // 确保目录存在
49 | Path directoryPath = Paths.get(directory);
50 | if (!Files.exists(directoryPath)) {
51 | Files.createDirectories(directoryPath);
52 | }
53 |
54 | // 生成唯一文件名
55 | String fileName = generateUniqueFileName(file.getOriginalFilename());
56 | Path filePath = directoryPath.resolve(fileName);
57 |
58 | // 保存文件
59 | Files.copy(file.getInputStream(), filePath);
60 | return fileName;
61 | }
62 |
63 | /**
64 | * 删除文件
65 | */
66 | public static boolean deleteFile(String filePath) {
67 | try {
68 | Path path = Paths.get(filePath);
69 | return Files.deleteIfExists(path);
70 | } catch (IOException e) {
71 | return false;
72 | }
73 | }
74 |
75 | /**
76 | * 获取文件大小的可读性表示
77 | */
78 | public static String getReadableFileSize(long size) {
79 | if (size <= 0) {
80 | return String.valueOf(0);
81 | }
82 | final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
83 | int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
84 | return String.format("%.1f %s", size / Math.pow(1024, digitGroups), units[digitGroups]);
85 | }
86 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/utils/NetUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.utils;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 |
5 | import javax.servlet.http.HttpServletRequest;
6 | import java.net.InetAddress;
7 |
8 | /**
9 | * 网络工具类
10 | *
11 | * @author qiutuan
12 | * @date 2024/11/02
13 | */
14 | @Slf4j
15 | public class NetUtils {
16 |
17 | /**
18 | * 获取客户端 IP 地址
19 | *
20 | * @param request 请求
21 | * @return IP 地址
22 | */
23 | public static String getIpAddress(HttpServletRequest request) {
24 | String ip = request.getHeader("x-forwarded-for");
25 | if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
26 | ip = request.getHeader("Proxy-Client-IP");
27 | }
28 | if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
29 | ip = request.getHeader("WL-Proxy-Client-IP");
30 | }
31 | if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
32 | ip = request.getRemoteAddr();
33 | if ("127.0.0.1".equals(ip)) {
34 | // 根据网卡取本机配置的 IP
35 | InetAddress inet = null;
36 | try {
37 | inet = InetAddress.getLocalHost();
38 | } catch (Exception e) {
39 | log.error("获取本机 IP 地址失败", e);
40 | }
41 | if (inet != null) {
42 | ip = inet.getHostAddress();
43 | }
44 | }
45 | }
46 | // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
47 | if (ip != null && ip.length() > 15) {
48 | if (ip.indexOf(",") > 0) {
49 | ip = ip.substring(0, ip.indexOf(","));
50 | }
51 | }
52 | if (ip == null) {
53 | return "127.0.0.1";
54 | }
55 | return ip;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/utils/SpringContextUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.utils;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.springframework.beans.BeansException;
5 | import org.springframework.context.ApplicationContext;
6 | import org.springframework.context.ApplicationContextAware;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * Spring 上下文获取工具
11 | *
12 | * @author qiutuan
13 | * @date 2024/11/02
14 | */
15 | @Component
16 | public class SpringContextUtils implements ApplicationContextAware {
17 |
18 | private static ApplicationContext applicationContext;
19 |
20 | @Override
21 | public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
22 | SpringContextUtils.applicationContext = applicationContext;
23 | }
24 |
25 | /**
26 | * 通过名称获取 Bean
27 | *
28 | * @param beanName Bean 名称
29 | * @return Bean
30 | */
31 | public static Object getBean(String beanName) {
32 | return applicationContext.getBean(beanName);
33 | }
34 |
35 | /**
36 | * 通过 class 获取 Bean
37 | *
38 | * @param beanClass Bean 类型
39 | * @param Bean 类型
40 | * @return Bean
41 | */
42 | public static T getBean(Class beanClass) {
43 | return applicationContext.getBean(beanClass);
44 | }
45 |
46 | /**
47 | * 通过名称和类型获取 Bean
48 | *
49 | * @param beanName Bean 名称
50 | * @param beanClass Bean 类型
51 | * @param Bean 类型
52 | * @return Bean
53 | */
54 | public static T getBean(String beanName, Class beanClass) {
55 | return applicationContext.getBean(beanName, beanClass);
56 | }
57 | }
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/utils/SqlUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.utils;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 |
5 | /**
6 | * SQL 工具
7 | *
8 | * @author qiutuan
9 | * @date 2024/11/02
10 | */
11 | public class SqlUtils {
12 |
13 | /**
14 | * 校验排序字段是否合法(防止 SQL 注入)
15 | *
16 | * @param sortField 排序字段
17 | * @return 是否合法
18 | */
19 | public static boolean validSortField(String sortField) {
20 | if (StringUtils.isBlank(sortField)) {
21 | return false;
22 | }
23 | return !StringUtils.containsAny(sortField, "=", "(", ")", " ");
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/top/qtcc/qiutuanallpowerfulspringboot/utils/StringUtils.java:
--------------------------------------------------------------------------------
1 | package top.qtcc.qiutuanallpowerfulspringboot.utils;
2 |
3 | import java.util.UUID;
4 | import java.util.regex.Pattern;
5 |
6 | /**
7 | * 字符串工具类
8 | *
9 | * @author qiutuan
10 | * @date 2024/12/07
11 | */
12 | public class StringUtils {
13 |
14 | private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
15 | private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
16 |
17 | /**
18 | * 判断字符串是否为空
19 | */
20 | public static boolean isEmpty(String str) {
21 | return str == null || str.trim().isEmpty();
22 | }
23 |
24 | /**
25 | * 生成UUID
26 | */
27 | public static String generateUUID() {
28 | return UUID.randomUUID().toString().replace("-", "");
29 | }
30 |
31 | /**
32 | * 验证邮箱格式
33 | */
34 | public static boolean isValidEmail(String email) {
35 | return email != null && EMAIL_PATTERN.matcher(email).matches();
36 | }
37 |
38 | /**
39 | * 验证手机号格式
40 | */
41 | public static boolean isValidPhone(String phone) {
42 | return phone != null && PHONE_PATTERN.matcher(phone).matches();
43 | }
44 |
45 | /**
46 | * 隐藏手机号中间四位
47 | */
48 | public static String maskPhone(String phone) {
49 | if (phone == null || phone.length() != 11) {
50 | return phone;
51 | }
52 | return phone.substring(0, 3) + "****" + phone.substring(7);
53 | }
54 |
55 | /**
56 | * 隐藏邮箱用户名部分
57 | */
58 | public static String maskEmail(String email) {
59 | if (email == null || !email.contains("@")) {
60 | return email;
61 | }
62 | int atIndex = email.indexOf('@');
63 | if (atIndex <= 1) {
64 | return email;
65 | }
66 | return email.charAt(0) + "***" + email.substring(atIndex);
67 | }
68 | }
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8800
3 |
4 |
5 | spring:
6 | # 数据源配置
7 | datasource:
8 | url: jdbc:mysql://localhost:3306/xxxxx?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
9 | username: xxxxx
10 | password: xxxxx
11 | driver-class-name: com.mysql.cj.jdbc.Driver
12 | # redis 配置
13 | redis:
14 | # 地址
15 | host: xxxxx
16 | # 端口,默认为6379
17 | port: 6379
18 | # 数据库索引
19 | database: 0
20 | # 密码
21 | password:
22 | # 连接超时时间
23 | timeout: 10s
24 | lettuce:
25 | pool:
26 | # 连接池中的最小空闲连接
27 | min-idle: 0
28 | # 连接池中的最大空闲连接
29 | max-idle: 8
30 | # 连接池的最大数据库连接数
31 | max-active: 8
32 | # #连接池最大阻塞等待时间(使用负值表示没有限制)
33 | max-wait: -1ms
34 | #上传文件大小限制
35 | servlet:
36 | multipart:
37 | max-file-size: 100MB
38 | max-request-size: 100MB
39 |
40 |
41 | mybatis-plus:
42 | configuration:
43 | # 打印sql
44 | log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
45 | #关闭驼峰命名 如果开启驼峰命名请注释此配置
46 | map-underscore-to-camel-case: false
47 | global-config:
48 | banner: false
49 |
50 |
51 | # TODO 腾讯云对象存储配置
52 | cos:
53 | client:
54 | accessKey: xxx
55 | secretKey: xxx
56 | region: xxx
57 | bucket: xxx
58 |
59 |
60 | # TODO minio对象存储配置 自行部署minio服务
61 | minio:
62 | endpoint: xxxxxx
63 | accessKey: xxxxxx
64 | secretKey: xxxxxx
65 | bucket: xxxxxxx
66 |
67 |
68 | # TODO 选择使用的对象存储 默认使用minio
69 | # MinioManager minio对象存储 需要自行部署
70 | # CosManager 腾讯云对象存储
71 | file:
72 | manager: MinioManager
73 | # manager: CosManager
74 |
75 |
76 | # 接口文档配置
77 | knife4j:
78 | enable: true
79 | openapi:
80 | title: "接口文档"
81 | version: 1.0
82 | group:
83 | default:
84 | api-rule: package
85 | api-rule-resources:
86 | - top.qtcc.qiutuanallpowerfulspringboot.controller
87 |
--------------------------------------------------------------------------------
/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ${AnsiColor.BRIGHT_YELLOW}
2 | ▐▒▒░▄
3 | ▒▒▒▒▒▒▒▒▄
4 | ▐▒▒▒▒▒▒▒▒▒▒▒▄ ▄▄▒▒
5 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▄ ▄▄▒▒▒▒▒▒▒▒
6 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄ ▄▒▒▒▒▒▒▒▒▒▒▒▒▒▌
7 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄ ▄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
8 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▄░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░
9 | ▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄ ▄▄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
10 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄▄▄▄▄░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
11 | ▀▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▀
12 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
13 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
14 | ▀▒▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▀
15 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▀
16 | ▄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░
17 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
18 | ▒▒▒▒▒▒▒▒▒▒░▄▄▄ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▄▄▄ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
19 | ▒▒▒▒▒▒▒▒▒▒▒ ▀█▀ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▐██▀ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒░
20 | ▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
21 | ▐▒▒▒▒▒▒▒▒▒▒▒▒▒▄ ▄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄ ▄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
22 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▀▀▀▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
23 | ▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄▄ ▄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
24 | ▒▒░░░░░░░░▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░▒▒▒▒▒▒▒▒
25 | ▐▒░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░▐▒▒▒▒▒▒
26 | ▒▒░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▀░░░░▄░░░▀▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░▐▒▒▒▒▒▒▒
27 | ▐▒▒░░░░░░░▄░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░▒▒▒▒▒▒▒▒
28 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒░▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░▒▒▒▒▒▒▒▒▒▒▒▒
29 | ▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒░▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
30 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▄░▀▒▒▒▒▀░▄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
31 | ▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
32 |
33 | --by qiutuan
34 | ${AnsiColor.DEFAULT}
35 | spring-boot.version:${spring-boot.version}
36 |
37 |
--------------------------------------------------------------------------------
/src/main/resources/logback-spring.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ${LOG_PATTERN}
9 |
10 |
11 |
12 |
13 | ${LOG_PATH}/application.log
14 |
15 | ${LOG_PATH}/application.%d{yyyy-MM-dd}.log
16 | 30
17 |
18 |
19 | ${LOG_PATTERN}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/resources/sql/springboot_init.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Navicat Premium Data Transfer
3 |
4 | Source Server : mysql
5 | Source Server Type : MySQL
6 | Source Server Version : 80033
7 | Source Host : localhost:3306
8 | Source Schema : spinit
9 |
10 | Target Server Type : MySQL
11 | Target Server Version : 80033
12 | File Encoding : 65001
13 |
14 | Date: 27/04/2025 14:28:49
15 | */
16 |
17 | SET NAMES utf8mb4;
18 | SET FOREIGN_KEY_CHECKS = 0;
19 |
20 | -- ----------------------------
21 | -- Table structure for request_log
22 | -- ----------------------------
23 | DROP TABLE IF EXISTS `request_log`;
24 | CREATE TABLE `request_log` (
25 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
26 | `request_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求唯一ID',
27 | `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求URL',
28 | `method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求方法',
29 | `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求参数',
30 | `ip` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求IP',
31 | `user_id` bigint NULL DEFAULT NULL COMMENT '用户ID',
32 | `status` int NOT NULL COMMENT '响应状态码',
33 | `error_msg` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误信息',
34 | `cost_time` bigint NOT NULL COMMENT '耗时(毫秒)',
35 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
36 | PRIMARY KEY (`id`) USING BTREE,
37 | INDEX `idx_request_id`(`request_id` ASC) USING BTREE,
38 | INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
39 | INDEX `idx_create_time`(`create_time` ASC) USING BTREE
40 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '请求日志表' ROW_FORMAT = DYNAMIC;
41 |
42 | -- ----------------------------
43 | -- Table structure for user
44 | -- ----------------------------
45 | DROP TABLE IF EXISTS `user`;
46 | CREATE TABLE `user` (
47 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
48 | `user_account` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '账号',
49 | `user_password` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',
50 | `user_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户昵称',
51 | `user_avatar` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户头像',
52 | `user_profile` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户简介',
53 | `user_role` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'user' COMMENT '用户角色',
54 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
55 | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
56 | `is_delete` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除',
57 | PRIMARY KEY (`id`) USING BTREE
58 | ) ENGINE = InnoDB AUTO_INCREMENT = 1857700903633555457 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户' ROW_FORMAT = DYNAMIC;
59 |
60 | SET FOREIGN_KEY_CHECKS = 1;
61 |
--------------------------------------------------------------------------------
/src/test/java/top/qtcc/qiutuanallpowerfulspringboot/QiutuanAllPowerfulSpringbootApplicationTests.java:
--------------------------------------------------------------------------------
1 |
2 |
3 | package top.qtcc.qiutuanallpowerfulspringboot;
4 |
5 | import org.junit.jupiter.api.Test;
6 | import org.junit.runner.RunWith;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 | import org.springframework.test.context.junit4.SpringRunner;
9 | import top.qtcc.qiutuanallpowerfulspringboot.manager.file.FileManager;
10 |
11 | import javax.annotation.Resource;
12 | import java.io.File;
13 |
14 | @SpringBootTest
15 | @RunWith(SpringRunner.class)
16 | class QiutuanAllPowerfulSpringbootApplicationTests {
17 |
18 | @Test
19 | void contextLoads() {
20 | }
21 |
22 | @Resource(name = "FileManager")
23 | private FileManager minioManager;
24 |
25 | @Test
26 | public void MinioManagerTest() {
27 | // MultipartFile file = null;
28 |
29 | minioManager.putObject("2024-11-18 16:47:06/hngy_rjxh.sql", "C:\\Users\\qiutu\\Desktop\\hngy_rjxh.sql");
30 |
31 |
32 | //读取文件C:\Users\qiutu\Desktop\springboot.zip
33 |
34 | File file = new File("C:\\Users\\qiutu\\Pictures\\00016-2372822254.png");
35 |
36 |
37 |
38 | // System.out.println(minioManager.upload(file));
39 | // String preview = minioManager.preview("2024-11-18 16:47:06/00016-2372822254.png");
40 |
41 | // minioManager.download("2024-11-18 16:47:06/springboot.zip", null);
42 |
43 |
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------