├── .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 | ![SpringBoot](https://img.shields.io/badge/SpringBoot-框架-orange)![MyBatis-Plus](https://img.shields.io/badge/MyBatis--Plus-ORM框架-orange)![Redis](https://img.shields.io/badge/Redis-缓存-orange)![WebSocket](https://img.shields.io/badge/WebSocket-实时通信-orange)![MinIO](https://img.shields.io/badge/MinIO-对象存储-orange)![腾讯云COS](https://img.shields.io/badge/腾讯云COS-对象存储-orange)![AOP](https://img.shields.io/badge/AOP-面向切面编程-orange) 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 | --------------------------------------------------------------------------------