├── .gitignore ├── .idea ├── .gitignore ├── dataSources.xml ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── sqldialects.xml └── vcs.xml ├── README.md ├── asset └── sql │ └── table.sql ├── common ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── rosy │ └── common │ ├── annotation │ ├── LogTag.java │ └── ValidateRequest.java │ ├── constant │ └── CommonConstant.java │ ├── domain │ └── entity │ │ ├── ApiResponse.java │ │ ├── IdRequest.java │ │ └── PageRequest.java │ ├── enums │ ├── ErrorCode.java │ └── UserRoleEnum.java │ ├── exception │ └── BusinessException.java │ └── utils │ ├── PageUtils.java │ ├── QueryWrapperUtil.java │ ├── RedisCache.java │ ├── SqlUtils.java │ ├── StringUtils.java │ └── ThrowUtils.java ├── framework ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── rosy │ └── framework │ ├── aspect │ ├── LogAspect.java │ └── ValidationAspect.java │ ├── config │ ├── MybatisPlusConfig.java │ ├── OpenAPIConfig.java │ ├── RedisConfig.java │ ├── SecurityConfig.java │ ├── WebClientConfig.java │ └── properties │ │ ├── ApplicationProperties.java │ │ └── PathProperties.java │ └── handler │ ├── CustomMetaObjectHandler.java │ └── GlobalExceptionHandler.java ├── main ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── rosy │ │ └── main │ │ ├── domain │ │ ├── dto │ │ │ └── item │ │ │ │ ├── ItemAddRequest.java │ │ │ │ ├── ItemQueryRequest.java │ │ │ │ └── ItemUpdateRequest.java │ │ ├── entity │ │ │ └── Item.java │ │ └── vo │ │ │ ├── ItemVO.java │ │ │ ├── LoginUserVO.java │ │ │ └── UserVO.java │ │ ├── mapper │ │ └── ItemMapper.java │ │ └── service │ │ ├── IItemService.java │ │ └── impl │ │ └── ItemServiceImpl.java │ └── resources │ └── mapper │ └── ItemMapper.xml ├── pom.xml └── web ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── rosy │ │ ├── WebApplication.java │ │ └── web │ │ └── controller │ │ └── main │ │ └── ItemController.java └── resources │ ├── application-dev.yml │ ├── application-prod.yml │ ├── application.yml │ └── logback.xml └── test └── java └── com └── rosy └── MybaitisPlusCodeGenerator.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mysql.8 6 | true 7 | com.mysql.cj.jdbc.Driver 8 | jdbc:mysql://localhost:3306 9 | 10 | 11 | 12 | 13 | 14 | $ProjectFileDir$ 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 54 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/sqldialects.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpringBoot3.4.x 项目初始模板 2 | 3 | > 作者:Rosy 4 | 5 | 基于 Java SpringBoot 的项目初始模板,整合了常用框架和主流业务的示例代码。 6 | 7 | 只需 1 分钟即可完成内容网站的后端!在此基础上快速开发自己的项目。 8 | 我在使用其他脚手架时,总是觉得要么有些功能过于复杂,不够简洁,这样内容过多,在版本升级重构的时候会很麻烦;要么就是版本老旧,很多都是SpringBoot2.x的版本,SpringBoot3已经出来两三年了,是时候拥抱一下了。 9 | 因此我决定自己写一个简单的模板,尽量减少内容,只保留最核心的功能,目的就是为了在不让大家对原有的内容做过多的改动就可以根据自己需求对项目做适应性修改,快速上手,快速开发。 10 | 同时也欢迎大家提出宝贵意见,一起完善这个模板。 11 | 12 | ## 模板特点 13 | 14 | ### 主流框架 & 特性 15 | 16 | - Spring Boot 3.4.x(贼新) 17 | - MyBatis + MyBatis Plus 数据访问(开启分页) 18 | - Spring Boot 调试工具和项目处理器 19 | - Spring AOP 切面编程 20 | 21 | ### 数据存储 22 | 23 | - MySQL 数据库 24 | - Redis 内存数据库 25 | 26 | ### 工具类 27 | 28 | - Easy Excel 表格处理 29 | - Hutool 工具库 30 | - Gson 解析库 31 | - Apache Commons Lang3 工具类 32 | - Lombok 注解 33 | 34 | ### 业务特性 35 | 36 | - 全局请求响应拦截器(记录日志) 37 | - 全局异常处理器 38 | - 自定义错误码 39 | - 封装通用响应类 40 | - Spring OpenAPI 接口文档 41 | - 自定义权限注解 + 全局校验 42 | - 全局跨域处理 43 | - 多环境配置 44 | 45 | ### 单元测试 46 | 47 | - JUnit5 单元测试 48 | - 示例单元测试类 49 | 50 | ### 架构设计 51 | 52 | - 合理分层 53 | 54 | ## 快速上手 55 | 56 | > 所有需要修改的地方都标记了 `todo`,便于大家找到修改的位置~ 57 | 58 | -------------------------------------------------------------------------------- /asset/sql/table.sql: -------------------------------------------------------------------------------- 1 | USE `example`; 2 | 3 | DROP TABLE IF EXISTS `item`; 4 | CREATE TABLE IF NOT EXISTS `item` 5 | ( 6 | `id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT 'ID,必须为正整数' PRIMARY KEY, 7 | `name` VARCHAR(100) DEFAULT NULL COMMENT '名称,最大长度 100,可选', 8 | `description` VARCHAR(500) DEFAULT NULL COMMENT '简介/内容,最大长度 500,可选', 9 | `type` TINYINT UNSIGNED DEFAULT NULL COMMENT '类型,值范围为 1-127,可选', 10 | `status` TINYINT UNSIGNED DEFAULT NULL COMMENT '状态,0 或 1,可选', 11 | `creator_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '创建者ID,关联用户表,必须为正整数,可选', 12 | `create_time` DATETIME DEFAULT NULL COMMENT '创建时间,可选', 13 | `updater_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '更新者ID,关联用户表,必须为正整数,可选', 14 | `update_time` DATETIME DEFAULT NULL COMMENT '更新时间,可选', 15 | `sort_order` INT DEFAULT NULL COMMENT '排序字段,最大长度 50', 16 | `version` TINYINT UNSIGNED DEFAULT NULL COMMENT 'MP乐观锁版本', 17 | `is_deleted` TINYINT UNSIGNED DEFAULT NULL COMMENT 'MP逻辑删除字段,0 或 1' 18 | ) COMMENT 'item' COLLATE = utf8mb4_unicode_ci; 19 | 20 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.rosy 6 | springboot3.4.x-init 7 | 0.0.1 8 | 9 | 10 | common 11 | 12 | common 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-configuration-processor 18 | 19 | 20 | 21 | 22 | org.apache.commons 23 | commons-lang3 24 | 3.17.0 25 | 26 | 27 | 28 | com.alibaba.fastjson2 29 | fastjson2 30 | 31 | 32 | com.alibaba.fastjson2 33 | fastjson2-extension 34 | 35 | 36 | com.alibaba.fastjson2 37 | fastjson2-extension-spring6 38 | 39 | 40 | 41 | com.google.guava 42 | guava 43 | 44 | 45 | 46 | cn.hutool 47 | hutool-all 48 | 49 | 50 | 51 | org.projectlombok 52 | lombok 53 | 54 | 55 | 56 | com.alibaba 57 | easyexcel 58 | 59 | 60 | 61 | com.squareup.okhttp3 62 | okhttp 63 | 64 | 65 | 66 | com.google.code.gson 67 | gson 68 | 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-data-redis 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-starter-validation 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-starter-cache 84 | 85 | 86 | 87 | jakarta.servlet 88 | jakarta.servlet-api 89 | 6.1.0 90 | 91 | 92 | com.baomidou 93 | mybatis-plus-extension 94 | 3.5.9 95 | compile 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/annotation/LogTag.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.annotation; 2 | 3 | public @interface LogTag { 4 | } 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/annotation/ValidateRequest.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.annotation; 2 | 3 | public @interface ValidateRequest { 4 | } 5 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/constant/CommonConstant.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.constant; 2 | 3 | import java.util.Locale; 4 | 5 | public class CommonConstant { 6 | /** 7 | * UTF-8 字符集 8 | */ 9 | public static final String UTF8 = "UTF-8"; 10 | 11 | /** 12 | * GBK 字符集 13 | */ 14 | public static final String GBK = "GBK"; 15 | 16 | /** 17 | * 系统语言 18 | */ 19 | public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; 20 | 21 | /** 22 | * www主域 23 | */ 24 | public static final String WWW = "www."; 25 | 26 | /** 27 | * http请求 28 | */ 29 | public static final String HTTP = "http://"; 30 | 31 | /** 32 | * https请求 33 | */ 34 | public static final String HTTPS = "https://"; 35 | 36 | /** 37 | * 通用成功标识 38 | */ 39 | public static final String SUCCESS = "0"; 40 | 41 | /** 42 | * 通用失败标识 43 | */ 44 | public static final String FAIL = "1"; 45 | 46 | /** 47 | * 登录成功 48 | */ 49 | public static final String LOGIN_SUCCESS = "Success"; 50 | 51 | /** 52 | * 注销 53 | */ 54 | public static final String LOGOUT = "Logout"; 55 | 56 | /** 57 | * 注册 58 | */ 59 | public static final String REGISTER = "Register"; 60 | 61 | /** 62 | * 登录失败 63 | */ 64 | public static final String LOGIN_FAIL = "Error"; 65 | 66 | /** 67 | * 所有权限标识 68 | */ 69 | public static final String ALL_PERMISSION = "*:*:*"; 70 | 71 | /** 72 | * 管理员角色权限标识 73 | */ 74 | public static final String SUPER_ADMIN = "admin"; 75 | 76 | /** 77 | * 角色权限分隔符 78 | */ 79 | public static final String ROLE_DELIMETER = ","; 80 | 81 | /** 82 | * 权限标识分隔符 83 | */ 84 | public static final String PERMISSION_DELIMETER = ","; 85 | 86 | /** 87 | * 验证码有效期(分钟) 88 | */ 89 | public static final Integer CAPTCHA_EXPIRATION = 2; 90 | 91 | /** 92 | * 令牌 93 | */ 94 | public static final String TOKEN = "token"; 95 | 96 | /** 97 | * 令牌前缀 98 | */ 99 | public static final String TOKEN_PREFIX = "Bearer "; 100 | 101 | /** 102 | * 令牌前缀 103 | */ 104 | public static final String LOGIN_USER_KEY = "login_user_key"; 105 | 106 | /** 107 | * 用户ID 108 | */ 109 | public static final String JWT_USERID = "userid"; 110 | 111 | /** 112 | * 用户头像 113 | */ 114 | public static final String JWT_AVATAR = "avatar"; 115 | 116 | /** 117 | * 创建时间 118 | */ 119 | public static final String JWT_CREATED = "created"; 120 | 121 | /** 122 | * 用户权限 123 | */ 124 | public static final String JWT_AUTHORITIES = "authorities"; 125 | 126 | /** 127 | * 资源映射路径 前缀 128 | */ 129 | public static final String RESOURCE_PREFIX = "/profile"; 130 | 131 | /** 132 | * RMI 远程方法调用 133 | */ 134 | public static final String LOOKUP_RMI = "rmi:"; 135 | 136 | /** 137 | * LDAP 远程方法调用 138 | */ 139 | public static final String LOOKUP_LDAP = "ldap:"; 140 | 141 | /** 142 | * LDAPS 远程方法调用 143 | */ 144 | public static final String LOOKUP_LDAPS = "ldaps:"; 145 | 146 | /** 147 | * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) 148 | */ 149 | public static final String[] JSON_WHITELIST_STR = {"org.springframework", "com.rosy"}; 150 | 151 | /** 152 | * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) 153 | */ 154 | public static final String[] JOB_WHITELIST_STR = {"com.rosy.quartz.task"}; 155 | 156 | /** 157 | * 定时任务违规的字符 158 | */ 159 | public static final String[] JOB_ERROR_STR = {"java.net.URL", "jakarta.naming.InitialContext", "org.yaml.snakeyaml", 160 | "org.springframework", "org.apache", "com.rosy.common.utils.file", "com.rosy.common.config", "com.rosy.generator"}; 161 | public static final String DEFAULT_SORT_FIELD = "sortOrder"; 162 | 163 | /** 164 | * 升序 165 | */ 166 | public static String SORT_ORDER_ASC = "ascend"; 167 | 168 | /** 169 | * 降序 170 | */ 171 | public static String SORT_ORDER_DESC = " descend"; 172 | } 173 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/domain/entity/ApiResponse.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.domain.entity; 2 | 3 | import com.rosy.common.enums.ErrorCode; 4 | import com.rosy.common.utils.StringUtils; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | import java.io.Serial; 9 | import java.util.HashMap; 10 | import java.util.Objects; 11 | 12 | @EqualsAndHashCode(callSuper = true) 13 | @Data 14 | public class ApiResponse extends HashMap { 15 | /** 16 | * 状态码 17 | */ 18 | public static final String CODE_TAG = "code"; 19 | /** 20 | * 返回内容 21 | */ 22 | public static final String MSG_TAG = "msg"; 23 | /** 24 | * 数据对象 25 | */ 26 | public static final String DATA_TAG = "data"; 27 | @Serial 28 | private static final long serialVersionUID = 1L; 29 | 30 | /** 31 | * 初始化一个新创建的 ApiResponse 对象,使其表示一个空消息。 32 | */ 33 | public ApiResponse() { 34 | } 35 | 36 | /** 37 | * 初始化一个新创建的 ApiResponse 对象 38 | * 39 | * @param code 状态码 40 | * @param msg 返回内容 41 | */ 42 | public ApiResponse(int code, String msg) { 43 | super.put(CODE_TAG, code); 44 | super.put(MSG_TAG, msg); 45 | } 46 | 47 | /** 48 | * 初始化一个新创建的 ApiResponse 对象 49 | * 50 | * @param code 状态码 51 | * @param msg 返回内容 52 | * @param data 数据对象 53 | */ 54 | public ApiResponse(int code, String msg, Object data) { 55 | super.put(CODE_TAG, code); 56 | super.put(MSG_TAG, msg); 57 | if (StringUtils.isNotNull(data)) { 58 | super.put(DATA_TAG, data); 59 | } 60 | } 61 | 62 | /** 63 | * 返回成功消息 64 | * 65 | * @return 成功消息 66 | */ 67 | public static ApiResponse success() { 68 | return ApiResponse.success("操作成功"); 69 | } 70 | 71 | /** 72 | * 返回成功数据 73 | * 74 | * @return 成功消息 75 | */ 76 | public static ApiResponse success(Object data) { 77 | return ApiResponse.success("操作成功", data); 78 | } 79 | 80 | /** 81 | * 返回成功消息 82 | * 83 | * @param msg 返回内容 84 | * @return 成功消息 85 | */ 86 | public static ApiResponse success(String msg) { 87 | return ApiResponse.success(msg, null); 88 | } 89 | 90 | /** 91 | * 返回成功消息 92 | * 93 | * @param msg 返回内容 94 | * @param data 数据对象 95 | * @return 成功消息 96 | */ 97 | public static ApiResponse success(String msg, Object data) { 98 | return new ApiResponse(ErrorCode.SUCCESS.getCode(), msg, data); 99 | } 100 | 101 | /** 102 | * 返回警告消息 103 | * 104 | * @param msg 返回内容 105 | * @return 警告消息 106 | */ 107 | public static ApiResponse warn(String msg) { 108 | return ApiResponse.warn(msg, null); 109 | } 110 | 111 | /** 112 | * 返回警告消息 113 | * 114 | * @param msg 返回内容 115 | * @param data 数据对象 116 | * @return 警告消息 117 | */ 118 | public static ApiResponse warn(String msg, Object data) { 119 | return new ApiResponse(ErrorCode.WARN.getCode(), msg, data); 120 | } 121 | 122 | /** 123 | * 返回错误消息 124 | * 125 | * @return 错误消息 126 | */ 127 | public static ApiResponse error() { 128 | return ApiResponse.error("操作失败"); 129 | } 130 | 131 | /** 132 | * 返回错误消息 133 | * 134 | * @param msg 返回内容 135 | * @return 错误消息 136 | */ 137 | public static ApiResponse error(String msg) { 138 | return ApiResponse.error(msg, null); 139 | } 140 | 141 | /** 142 | * 返回错误消息 143 | * 144 | * @param msg 返回内容 145 | * @param data 数据对象 146 | * @return 错误消息 147 | */ 148 | public static ApiResponse error(String msg, Object data) { 149 | return new ApiResponse(ErrorCode.ERROR.getCode(), msg, data); 150 | } 151 | 152 | /** 153 | * 返回错误消息 154 | * 155 | * @param code 状态码 156 | * @param msg 返回内容 157 | * @return 错误消息 158 | */ 159 | public static ApiResponse error(int code, String msg) { 160 | return new ApiResponse(code, msg, null); 161 | } 162 | 163 | /** 164 | * 是否为成功消息 165 | * 166 | * @return 结果 167 | */ 168 | public boolean isSuccess() { 169 | return Objects.equals(ErrorCode.SUCCESS.getCode(), this.get(CODE_TAG)); 170 | } 171 | 172 | /** 173 | * 是否为警告消息 174 | * 175 | * @return 结果 176 | */ 177 | public boolean isWarn() { 178 | return Objects.equals(ErrorCode.WARN.getCode(), this.get(CODE_TAG)); 179 | } 180 | 181 | /** 182 | * 是否为错误消息 183 | * 184 | * @return 结果 185 | */ 186 | public boolean isError() { 187 | return Objects.equals(ErrorCode.ERROR.getCode(), this.get(CODE_TAG)); 188 | } 189 | 190 | /** 191 | * 方便链式调用 192 | * 193 | * @param key 键 194 | * @param value 值 195 | * @return 数据对象 196 | */ 197 | @Override 198 | public ApiResponse put(String key, Object value) { 199 | super.put(key, value); 200 | return this; 201 | } 202 | } -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/domain/entity/IdRequest.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.domain.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serial; 6 | import java.io.Serializable; 7 | 8 | @Data 9 | public class IdRequest implements Serializable { 10 | @Serial 11 | private static final long serialVersionUID = 1L; 12 | /** 13 | * id 14 | */ 15 | private Long id; 16 | } -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/domain/entity/PageRequest.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.domain.entity; 2 | 3 | import com.rosy.common.constant.CommonConstant; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class PageRequest { 8 | 9 | /** 10 | * 当前页号 11 | */ 12 | private long current = 1; 13 | 14 | /** 15 | * 页面大小 16 | */ 17 | private long pageSize = 10; 18 | 19 | /** 20 | * 排序字段 21 | */ 22 | private String sortField = CommonConstant.DEFAULT_SORT_FIELD; 23 | 24 | /** 25 | * 排序顺序(默认升序) 26 | */ 27 | private String sortOrder = CommonConstant.SORT_ORDER_ASC; 28 | } -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/enums/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.enums; 2 | 3 | import lombok.Getter; 4 | 5 | 6 | @Getter 7 | public enum ErrorCode { 8 | 9 | SUCCESS(0, "success"), 10 | WARN(10000, "warn"), 11 | ERROR(20000, "error"), 12 | PARAMS_ERROR(40000, "请求参数错误"), 13 | NOT_LOGIN_ERROR(40100, "未登录"), 14 | NO_AUTH_ERROR(40101, "无权限"), 15 | NOT_FOUND_ERROR(40400, "请求数据不存在"), 16 | FORBIDDEN_ERROR(40300, "禁止访问"), 17 | SYSTEM_ERROR(50000, "系统内部异常"), 18 | OPERATION_ERROR(50001, "操作失败"); 19 | 20 | /** 21 | * 状态码 22 | */ 23 | private final int code; 24 | 25 | /** 26 | * 信息 27 | */ 28 | private final String message; 29 | 30 | ErrorCode(int code, String message) { 31 | this.code = code; 32 | this.message = message; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/enums/UserRoleEnum.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.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 | @Getter 14 | public enum UserRoleEnum { 15 | 16 | USER("用户", "user"), 17 | ADMIN("管理员", "admin"), 18 | BAN("被封号", "ban"); 19 | 20 | private final String text; 21 | 22 | private final String value; 23 | 24 | UserRoleEnum(String text, String value) { 25 | this.text = text; 26 | this.value = value; 27 | } 28 | 29 | /** 30 | * 获取值列表 31 | * 32 | * @return 33 | */ 34 | public static List getValues() { 35 | return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList()); 36 | } 37 | 38 | /** 39 | * 根据 value 获取枚举 40 | * 41 | * @param value 42 | * @return 43 | */ 44 | public static UserRoleEnum getEnumByValue(String value) { 45 | if (ObjectUtils.isEmpty(value)) { 46 | return null; 47 | } 48 | for (UserRoleEnum anEnum : UserRoleEnum.values()) { 49 | if (anEnum.value.equals(value)) { 50 | return anEnum; 51 | } 52 | } 53 | return null; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.exception; 2 | 3 | 4 | import com.rosy.common.enums.ErrorCode; 5 | import lombok.Getter; 6 | 7 | /** 8 | * 自定义异常类 9 | */ 10 | @Getter 11 | public class BusinessException extends RuntimeException { 12 | 13 | /** 14 | * 错误码 15 | */ 16 | private final int code; 17 | 18 | public BusinessException(int code, String message) { 19 | super(message); 20 | this.code = code; 21 | } 22 | 23 | public BusinessException(ErrorCode errorCode) { 24 | super(errorCode.getMessage()); 25 | this.code = errorCode.getCode(); 26 | } 27 | 28 | public BusinessException(ErrorCode errorCode, String message) { 29 | super(message); 30 | this.code = errorCode.getCode(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/utils/PageUtils.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.utils; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | 5 | import java.util.List; 6 | import java.util.function.Function; 7 | import java.util.stream.Collectors; 8 | 9 | public class PageUtils { 10 | public static Page convert(Page sourcePage, Function converter) { 11 | Page targetPage = new Page<>(sourcePage.getCurrent(), sourcePage.getSize(), sourcePage.getTotal()); 12 | List convertedRecords = sourcePage.getRecords().stream().map(converter).collect(Collectors.toList()); 13 | targetPage.setRecords(convertedRecords); 14 | return targetPage; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/utils/QueryWrapperUtil.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.utils; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.core.toolkit.support.SFunction; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | public class QueryWrapperUtil { 12 | 13 | /** 14 | * 添加等值条件 15 | * 16 | * @param queryWrapper 查询构造器 17 | * @param value 条件值 18 | * @param column 数据库字段映射 19 | * @param 实体类型 20 | */ 21 | public static void addCondition(LambdaQueryWrapper queryWrapper, Object value, SFunction column) { 22 | if (value != null) { 23 | queryWrapper.eq(column, value); 24 | } 25 | } 26 | 27 | /** 28 | * 添加模糊查询条件 29 | * 30 | * @param queryWrapper 查询构造器 31 | * @param value 条件值 32 | * @param column 数据库字段映射 33 | * @param 实体类型 34 | */ 35 | public static void addLikeCondition(LambdaQueryWrapper queryWrapper, String value, SFunction column) { 36 | if (StringUtils.isNotBlank(value)) { 37 | queryWrapper.like(column, value); 38 | } 39 | } 40 | 41 | /** 42 | * 添加排序条件 43 | * 44 | * @param queryWrapper 查询构造器 45 | * @param sortField 排序字段 46 | * @param sortOrder 排序顺序,"asc" 或 "desc" 47 | * @param defaultField 默认排序字段 48 | * @param 实体类型 49 | */ 50 | public static void addSortCondition(LambdaQueryWrapper queryWrapper, String sortField, String sortOrder, SFunction defaultField) { 51 | // 判断排序顺序,默认升序 52 | boolean isAsc = "asc".equalsIgnoreCase(Optional.ofNullable(sortOrder).orElse("asc")); 53 | 54 | // 如果指定了排序字段且有效 55 | if (StringUtils.isNotBlank(sortField)) { 56 | try { 57 | // 尝试将排序字段转换为对应的 Lambda 表达式 58 | SFunction field = getFieldByName(sortField, defaultField); 59 | queryWrapper.orderBy(true, isAsc, field); 60 | } catch (IllegalArgumentException e) { 61 | // 如果字段无效,回退到默认排序字段 62 | queryWrapper.orderBy(true, isAsc, defaultField); 63 | } 64 | } else { 65 | // 使用默认排序字段 66 | queryWrapper.orderBy(true, isAsc, defaultField); 67 | } 68 | } 69 | 70 | /** 71 | * 根据字段名获取对应的 SFunction 72 | * 73 | * @param fieldName 字段名 74 | * @param defaultField 默认字段(如果找不到时返回) 75 | * @param 实体类型 76 | * @return 对应的 SFunction 77 | */ 78 | private static SFunction getFieldByName(String fieldName, SFunction defaultField) { 79 | // 示例逻辑:可以根据实际业务实现字段映射 80 | // 假设字段映射是一个 Map> 81 | Map> fieldMapping = getFieldMapping(); 82 | 83 | // 返回字段映射或默认字段 84 | return Optional.ofNullable(fieldMapping.get(fieldName)) 85 | .orElseThrow(() -> new IllegalArgumentException("Invalid sort field: " + fieldName)); 86 | } 87 | 88 | /** 89 | * 获取字段映射(示例方法) 90 | * 91 | * @param 实体类型 92 | * @return 字段映射 93 | */ 94 | private static Map> getFieldMapping() { 95 | // TODO: 实现字段映射逻辑 96 | return new HashMap<>(); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/utils/RedisCache.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.utils; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.redis.core.BoundSetOperations; 5 | import org.springframework.data.redis.core.HashOperations; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.data.redis.core.ValueOperations; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.*; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Component 14 | public class RedisCache { 15 | @Autowired 16 | public RedisTemplate redisTemplate; 17 | 18 | /** 19 | * 缓存基本的对象,Integer、String、实体类等 20 | * 21 | * @param key 缓存的键值 22 | * @param value 缓存的值 23 | */ 24 | public void setCacheObject(final String key, final T value) { 25 | redisTemplate.opsForValue().set(key, value); 26 | } 27 | 28 | /** 29 | * 缓存基本的对象,Integer、String、实体类等 30 | * 31 | * @param key 缓存的键值 32 | * @param value 缓存的值 33 | * @param timeout 时间 34 | * @param timeUnit 时间颗粒度 35 | */ 36 | public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { 37 | redisTemplate.opsForValue().set(key, value, timeout, timeUnit); 38 | } 39 | 40 | /** 41 | * 设置有效时间 42 | * 43 | * @param key Redis键 44 | * @param timeout 超时时间 45 | * @return true=设置成功;false=设置失败 46 | */ 47 | public boolean expire(final String key, final long timeout) { 48 | return expire(key, timeout, TimeUnit.SECONDS); 49 | } 50 | 51 | /** 52 | * 设置有效时间 53 | * 54 | * @param key Redis键 55 | * @param timeout 超时时间 56 | * @param unit 时间单位 57 | * @return true=设置成功;false=设置失败 58 | */ 59 | public boolean expire(final String key, final long timeout, final TimeUnit unit) { 60 | return redisTemplate.expire(key, timeout, unit); 61 | } 62 | 63 | /** 64 | * 获取有效时间 65 | * 66 | * @param key Redis键 67 | * @return 有效时间 68 | */ 69 | public long getExpire(final String key) { 70 | return redisTemplate.getExpire(key); 71 | } 72 | 73 | /** 74 | * 判断 key是否存在 75 | * 76 | * @param key 键 77 | * @return true 存在 false不存在 78 | */ 79 | public Boolean hasKey(String key) { 80 | return redisTemplate.hasKey(key); 81 | } 82 | 83 | /** 84 | * 获得缓存的基本对象。 85 | * 86 | * @param key 缓存键值 87 | * @return 缓存键值对应的数据 88 | */ 89 | public T getCacheObject(final String key) { 90 | ValueOperations operation = redisTemplate.opsForValue(); 91 | return operation.get(key); 92 | } 93 | 94 | /** 95 | * 删除单个对象 96 | * 97 | * @param key 98 | */ 99 | public boolean deleteObject(final String key) { 100 | return redisTemplate.delete(key); 101 | } 102 | 103 | /** 104 | * 删除集合对象 105 | * 106 | * @param collection 多个对象 107 | * @return 108 | */ 109 | public boolean deleteObject(final Collection collection) { 110 | return redisTemplate.delete(collection) > 0; 111 | } 112 | 113 | /** 114 | * 缓存List数据 115 | * 116 | * @param key 缓存的键值 117 | * @param dataList 待缓存的List数据 118 | * @return 缓存的对象 119 | */ 120 | public long setCacheList(final String key, final List dataList) { 121 | Long count = redisTemplate.opsForList().rightPushAll(key, dataList); 122 | return count == null ? 0 : count; 123 | } 124 | 125 | /** 126 | * 缓存List数据并设置过期时间 127 | * 128 | * @param key 缓存的键值 129 | * @param dataList 待缓存的List数据 130 | * @param timeout 超时时间 131 | * @param unit 时间单位 132 | * @return 缓存的对象 133 | */ 134 | public long setCacheList(final String key, final List dataList, final long timeout, final TimeUnit unit) { 135 | Long count = redisTemplate.opsForList().rightPushAll(key, dataList); 136 | if (count != null && count > 0) { 137 | // 设置缓存的过期时间 138 | expire(key, timeout, unit); 139 | } 140 | return count == null ? 0 : count; 141 | } 142 | 143 | /** 144 | * 获得缓存的list对象 145 | * 146 | * @param key 缓存的键值 147 | * @return 缓存键值对应的数据 148 | */ 149 | public List getCacheList(final String key) { 150 | return redisTemplate.opsForList().range(key, 0, -1); 151 | } 152 | 153 | /** 154 | * 缓存Set 155 | * 156 | * @param key 缓存键值 157 | * @param dataSet 缓存的数据 158 | * @return 缓存数据的对象 159 | */ 160 | public BoundSetOperations setCacheSet(final String key, final Set dataSet) { 161 | BoundSetOperations setOperation = redisTemplate.boundSetOps(key); 162 | Iterator it = dataSet.iterator(); 163 | while (it.hasNext()) { 164 | setOperation.add(it.next()); 165 | } 166 | return setOperation; 167 | } 168 | 169 | /** 170 | * 获得缓存的set 171 | * 172 | * @param key 173 | * @return 174 | */ 175 | public Set getCacheSet(final String key) { 176 | return redisTemplate.opsForSet().members(key); 177 | } 178 | 179 | /** 180 | * 缓存Map 181 | * 182 | * @param key 183 | * @param dataMap 184 | */ 185 | public void setCacheMap(final String key, final Map dataMap) { 186 | if (dataMap != null) { 187 | redisTemplate.opsForHash().putAll(key, dataMap); 188 | } 189 | } 190 | 191 | /** 192 | * 获得缓存的Map 193 | * 194 | * @param key 195 | * @return 196 | */ 197 | public Map getCacheMap(final String key) { 198 | return redisTemplate.opsForHash().entries(key); 199 | } 200 | 201 | /** 202 | * 往Hash中存入数据 203 | * 204 | * @param key Redis键 205 | * @param hKey Hash键 206 | * @param value 值 207 | */ 208 | public void setCacheMapValue(final String key, final String hKey, final T value) { 209 | redisTemplate.opsForHash().put(key, hKey, value); 210 | } 211 | 212 | /** 213 | * 获取Hash中的数据 214 | * 215 | * @param key Redis键 216 | * @param hKey Hash键 217 | * @return Hash中的对象 218 | */ 219 | public T getCacheMapValue(final String key, final String hKey) { 220 | HashOperations opsForHash = redisTemplate.opsForHash(); 221 | return opsForHash.get(key, hKey); 222 | } 223 | 224 | /** 225 | * 获取多个Hash中的数据 226 | * 227 | * @param key Redis键 228 | * @param hKeys Hash键集合 229 | * @return Hash对象集合 230 | */ 231 | public List getMultiCacheMapValue(final String key, final Collection hKeys) { 232 | return redisTemplate.opsForHash().multiGet(key, hKeys); 233 | } 234 | 235 | /** 236 | * 删除Hash中的某条数据 237 | * 238 | * @param key Redis键 239 | * @param hKey Hash键 240 | * @return 是否成功 241 | */ 242 | public boolean deleteCacheMapValue(final String key, final String hKey) { 243 | return redisTemplate.opsForHash().delete(key, hKey) > 0; 244 | } 245 | 246 | /** 247 | * 获得缓存的基本对象列表 248 | * 249 | * @param pattern 字符串前缀 250 | * @return 对象列表 251 | */ 252 | public Collection keys(final String pattern) { 253 | return redisTemplate.keys(pattern); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/utils/SqlUtils.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.utils; 2 | 3 | /** 4 | * SQL 工具 5 | *

6 | * 校验排序字段是否合法(防止 SQL 注入) 7 | */ 8 | public class SqlUtils { 9 | 10 | /** 11 | * 校验排序字段是否合法(防止 SQL 注入) 12 | */ 13 | public static boolean validSortField(String sortField) { 14 | if (StringUtils.isBlank(sortField)) { 15 | return false; 16 | } 17 | return !StringUtils.containsAny(sortField, "=", "(", ")", " "); 18 | } 19 | } -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.utils; 2 | 3 | import cn.hutool.core.text.AntPathMatcher; 4 | import cn.hutool.core.text.StrFormatter; 5 | 6 | import java.util.*; 7 | 8 | public class StringUtils extends org.apache.commons.lang3.StringUtils { 9 | /** 10 | * 空字符串 11 | */ 12 | private static final String NULLSTR = ""; 13 | 14 | /** 15 | * 下划线 16 | */ 17 | private static final char SEPARATOR = '_'; 18 | 19 | /** 20 | * 星号 21 | */ 22 | private static final char ASTERISK = '*'; 23 | 24 | /** 25 | * 获取参数不为空值 26 | * 27 | * @param value defaultValue 要判断的value 28 | * @return value 返回值 29 | */ 30 | public static T nvl(T value, T defaultValue) { 31 | return value != null ? value : defaultValue; 32 | } 33 | 34 | /** 35 | * * 判断一个Collection是否为空, 包含List,Set,Queue 36 | * 37 | * @param coll 要判断的Collection 38 | * @return true:为空 false:非空 39 | */ 40 | public static boolean isEmpty(Collection coll) { 41 | return isNull(coll) || coll.isEmpty(); 42 | } 43 | 44 | /** 45 | * * 判断一个Collection是否非空,包含List,Set,Queue 46 | * 47 | * @param coll 要判断的Collection 48 | * @return true:非空 false:空 49 | */ 50 | public static boolean isNotEmpty(Collection coll) { 51 | return !isEmpty(coll); 52 | } 53 | 54 | /** 55 | * * 判断一个对象数组是否为空 56 | * 57 | * @param objects 要判断的对象数组 58 | * * @return true:为空 false:非空 59 | */ 60 | public static boolean isEmpty(Object[] objects) { 61 | return isNull(objects) || (objects.length == 0); 62 | } 63 | 64 | /** 65 | * * 判断一个对象数组是否非空 66 | * 67 | * @param objects 要判断的对象数组 68 | * @return true:非空 false:空 69 | */ 70 | public static boolean isNotEmpty(Object[] objects) { 71 | return !isEmpty(objects); 72 | } 73 | 74 | /** 75 | * * 判断一个Map是否为空 76 | * 77 | * @param map 要判断的Map 78 | * @return true:为空 false:非空 79 | */ 80 | public static boolean isEmpty(Map map) { 81 | return isNull(map) || map.isEmpty(); 82 | } 83 | 84 | /** 85 | * * 判断一个Map是否为空 86 | * 87 | * @param map 要判断的Map 88 | * @return true:非空 false:空 89 | */ 90 | public static boolean isNotEmpty(Map map) { 91 | return !isEmpty(map); 92 | } 93 | 94 | /** 95 | * * 判断一个字符串是否为空串 96 | * 97 | * @param str String 98 | * @return true:为空 false:非空 99 | */ 100 | public static boolean isEmpty(String str) { 101 | return isNull(str) || NULLSTR.equals(str.trim()); 102 | } 103 | 104 | /** 105 | * * 判断一个字符串是否为非空串 106 | * 107 | * @param str String 108 | * @return true:非空串 false:空串 109 | */ 110 | public static boolean isNotEmpty(String str) { 111 | return !isEmpty(str); 112 | } 113 | 114 | /** 115 | * * 判断一个对象是否为空 116 | * 117 | * @param object Object 118 | * @return true:为空 false:非空 119 | */ 120 | public static boolean isNull(Object object) { 121 | return object == null; 122 | } 123 | 124 | /** 125 | * * 判断一个对象是否非空 126 | * 127 | * @param object Object 128 | * @return true:非空 false:空 129 | */ 130 | public static boolean isNotNull(Object object) { 131 | return !isNull(object); 132 | } 133 | 134 | /** 135 | * * 判断一个对象是否是数组类型(Java基本型别的数组) 136 | * 137 | * @param object 对象 138 | * @return true:是数组 false:不是数组 139 | */ 140 | public static boolean isArray(Object object) { 141 | return isNotNull(object) && object.getClass().isArray(); 142 | } 143 | 144 | /** 145 | * 去空格 146 | */ 147 | public static String trim(String str) { 148 | return (str == null ? "" : str.trim()); 149 | } 150 | 151 | /** 152 | * 替换指定字符串的指定区间内字符为"*" 153 | * 154 | * @param str 字符串 155 | * @param startInclude 开始位置(包含) 156 | * @param endExclude 结束位置(不包含) 157 | * @return 替换后的字符串 158 | */ 159 | public static String hide(CharSequence str, int startInclude, int endExclude) { 160 | if (isEmpty(str)) { 161 | return NULLSTR; 162 | } 163 | final int strLength = str.length(); 164 | if (startInclude > strLength) { 165 | return NULLSTR; 166 | } 167 | if (endExclude > strLength) { 168 | endExclude = strLength; 169 | } 170 | if (startInclude > endExclude) { 171 | // 如果起始位置大于结束位置,不替换 172 | return NULLSTR; 173 | } 174 | final char[] chars = new char[strLength]; 175 | for (int i = 0; i < strLength; i++) { 176 | if (i >= startInclude && i < endExclude) { 177 | chars[i] = ASTERISK; 178 | } else { 179 | chars[i] = str.charAt(i); 180 | } 181 | } 182 | return new String(chars); 183 | } 184 | 185 | /** 186 | * 截取字符串 187 | * 188 | * @param str 字符串 189 | * @param start 开始 190 | * @return 结果 191 | */ 192 | public static String substring(final String str, int start) { 193 | if (str == null) { 194 | return NULLSTR; 195 | } 196 | 197 | if (start < 0) { 198 | start = str.length() + start; 199 | } 200 | 201 | if (start < 0) { 202 | start = 0; 203 | } 204 | if (start > str.length()) { 205 | return NULLSTR; 206 | } 207 | 208 | return str.substring(start); 209 | } 210 | 211 | /** 212 | * 截取字符串 213 | * 214 | * @param str 字符串 215 | * @param start 开始 216 | * @param end 结束 217 | * @return 结果 218 | */ 219 | public static String substring(final String str, int start, int end) { 220 | if (str == null) { 221 | return NULLSTR; 222 | } 223 | 224 | if (end < 0) { 225 | end = str.length() + end; 226 | } 227 | if (start < 0) { 228 | start = str.length() + start; 229 | } 230 | 231 | if (end > str.length()) { 232 | end = str.length(); 233 | } 234 | 235 | if (start > end) { 236 | return NULLSTR; 237 | } 238 | 239 | if (start < 0) { 240 | start = 0; 241 | } 242 | if (end < 0) { 243 | end = 0; 244 | } 245 | 246 | return str.substring(start, end); 247 | } 248 | 249 | /** 250 | * 判断是否为空,并且不是空白字符 251 | * 252 | * @param str 要判断的value 253 | * @return 结果 254 | */ 255 | public static boolean hasText(String str) { 256 | return (str != null && !str.isEmpty() && containsText(str)); 257 | } 258 | 259 | private static boolean containsText(CharSequence str) { 260 | int strLen = str.length(); 261 | for (int i = 0; i < strLen; i++) { 262 | if (!Character.isWhitespace(str.charAt(i))) { 263 | return true; 264 | } 265 | } 266 | return false; 267 | } 268 | 269 | /** 270 | * 格式化文本, {} 表示占位符
271 | * 此方法只是简单将占位符 {} 按照顺序替换为参数
272 | * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
273 | * 例:
274 | * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
275 | * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
276 | * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
277 | * 278 | * @param template 文本模板,被替换的部分用 {} 表示 279 | * @param params 参数值 280 | * @return 格式化后的文本 281 | */ 282 | public static String format(String template, Object... params) { 283 | if (isEmpty(params) || isEmpty(template)) { 284 | return template; 285 | } 286 | return StrFormatter.format(template, params); 287 | } 288 | 289 | /** 290 | * 是否为http(s)://开头 291 | * 292 | * @param link 链接 293 | * @return 结果 294 | */ 295 | public static boolean ishttp(String link) { 296 | return StringUtils.startsWithAny(link, "http://", "https://"); 297 | } 298 | 299 | /** 300 | * 字符串转set 301 | * 302 | * @param str 字符串 303 | * @param sep 分隔符 304 | * @return set集合 305 | */ 306 | public static final Set str2Set(String str, String sep) { 307 | return new HashSet(str2List(str, sep, true, false)); 308 | } 309 | 310 | /** 311 | * 字符串转list 312 | * 313 | * @param str 字符串 314 | * @param sep 分隔符 315 | * @param filterBlank 过滤纯空白 316 | * @param trim 去掉首尾空白 317 | * @return list集合 318 | */ 319 | public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) { 320 | List list = new ArrayList(); 321 | if (StringUtils.isEmpty(str)) { 322 | return list; 323 | } 324 | 325 | // 过滤空白字符串 326 | if (filterBlank && StringUtils.isBlank(str)) { 327 | return list; 328 | } 329 | String[] split = str.split(sep); 330 | for (String string : split) { 331 | if (filterBlank && StringUtils.isBlank(string)) { 332 | continue; 333 | } 334 | if (trim) { 335 | string = string.trim(); 336 | } 337 | list.add(string); 338 | } 339 | 340 | return list; 341 | } 342 | 343 | /** 344 | * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value 345 | * 346 | * @param collection 给定的集合 347 | * @param array 给定的数组 348 | * @return boolean 结果 349 | */ 350 | public static boolean containsAny(Collection collection, String... array) { 351 | if (isEmpty(collection) || isEmpty(array)) { 352 | return false; 353 | } else { 354 | for (String str : array) { 355 | if (collection.contains(str)) { 356 | return true; 357 | } 358 | } 359 | return false; 360 | } 361 | } 362 | 363 | /** 364 | * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 365 | * 366 | * @param cs 指定字符串 367 | * @param searchCharSequences 需要检查的字符串数组 368 | * @return 是否包含任意一个字符串 369 | */ 370 | public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { 371 | if (isEmpty(cs) || isEmpty(searchCharSequences)) { 372 | return false; 373 | } 374 | for (CharSequence testStr : searchCharSequences) { 375 | if (containsIgnoreCase(cs, testStr)) { 376 | return true; 377 | } 378 | } 379 | return false; 380 | } 381 | 382 | /** 383 | * 驼峰转下划线命名 384 | */ 385 | public static String toUnderScoreCase(String str) { 386 | if (str == null) { 387 | return null; 388 | } 389 | StringBuilder sb = new StringBuilder(); 390 | // 前置字符是否大写 391 | boolean preCharIsUpperCase = true; 392 | // 当前字符是否大写 393 | boolean curreCharIsUpperCase = true; 394 | // 下一字符是否大写 395 | boolean nexteCharIsUpperCase = true; 396 | for (int i = 0; i < str.length(); i++) { 397 | char c = str.charAt(i); 398 | if (i > 0) { 399 | preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); 400 | } else { 401 | preCharIsUpperCase = false; 402 | } 403 | 404 | curreCharIsUpperCase = Character.isUpperCase(c); 405 | 406 | if (i < (str.length() - 1)) { 407 | nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); 408 | } 409 | 410 | if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { 411 | sb.append(SEPARATOR); 412 | } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { 413 | sb.append(SEPARATOR); 414 | } 415 | sb.append(Character.toLowerCase(c)); 416 | } 417 | 418 | return sb.toString(); 419 | } 420 | 421 | /** 422 | * 是否包含字符串 423 | * 424 | * @param str 验证字符串 425 | * @param strs 字符串组 426 | * @return 包含返回true 427 | */ 428 | public static boolean inStringIgnoreCase(String str, String... strs) { 429 | if (str != null && strs != null) { 430 | for (String s : strs) { 431 | if (str.equalsIgnoreCase(trim(s))) { 432 | return true; 433 | } 434 | } 435 | } 436 | return false; 437 | } 438 | 439 | /** 440 | * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld 441 | * 442 | * @param name 转换前的下划线大写方式命名的字符串 443 | * @return 转换后的驼峰式命名的字符串 444 | */ 445 | public static String convertToCamelCase(String name) { 446 | StringBuilder result = new StringBuilder(); 447 | // 快速检查 448 | if (name == null || name.isEmpty()) { 449 | // 没必要转换 450 | return ""; 451 | } else if (!name.contains("_")) { 452 | // 不含下划线,仅将首字母大写 453 | return name.substring(0, 1).toUpperCase() + name.substring(1); 454 | } 455 | // 用下划线将原始字符串分割 456 | String[] camels = name.split("_"); 457 | for (String camel : camels) { 458 | // 跳过原始字符串中开头、结尾的下换线或双重下划线 459 | if (camel.isEmpty()) { 460 | continue; 461 | } 462 | // 首字母大写 463 | result.append(camel.substring(0, 1).toUpperCase()); 464 | result.append(camel.substring(1).toLowerCase()); 465 | } 466 | return result.toString(); 467 | } 468 | 469 | /** 470 | * 驼峰式命名法 471 | * 例如:user_name->userName 472 | */ 473 | public static String toCamelCase(String s) { 474 | if (s == null) { 475 | return null; 476 | } 477 | if (s.indexOf(SEPARATOR) == -1) { 478 | return s; 479 | } 480 | s = s.toLowerCase(); 481 | StringBuilder sb = new StringBuilder(s.length()); 482 | boolean upperCase = false; 483 | for (int i = 0; i < s.length(); i++) { 484 | char c = s.charAt(i); 485 | 486 | if (c == SEPARATOR) { 487 | upperCase = true; 488 | } else if (upperCase) { 489 | sb.append(Character.toUpperCase(c)); 490 | upperCase = false; 491 | } else { 492 | sb.append(c); 493 | } 494 | } 495 | return sb.toString(); 496 | } 497 | 498 | /** 499 | * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 500 | * 501 | * @param str 指定字符串 502 | * @param strs 需要检查的字符串数组 503 | * @return 是否匹配 504 | */ 505 | public static boolean matches(String str, List strs) { 506 | if (isEmpty(str) || isEmpty(strs)) { 507 | return false; 508 | } 509 | for (String pattern : strs) { 510 | if (isMatch(pattern, str)) { 511 | return true; 512 | } 513 | } 514 | return false; 515 | } 516 | 517 | /** 518 | * 判断url是否与规则配置: 519 | * ? 表示单个字符; 520 | * * 表示一层路径内的任意字符串,不可跨层级; 521 | * ** 表示任意层路径; 522 | * 523 | * @param pattern 匹配规则 524 | * @param url 需要匹配的url 525 | * @return 526 | */ 527 | public static boolean isMatch(String pattern, String url) { 528 | AntPathMatcher matcher = new AntPathMatcher(); 529 | return matcher.match(pattern, url); 530 | } 531 | 532 | @SuppressWarnings("unchecked") 533 | public static T cast(Object obj) { 534 | return (T) obj; 535 | } 536 | 537 | /** 538 | * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 539 | * 540 | * @param num 数字对象 541 | * @param size 字符串指定长度 542 | * @return 返回数字的字符串格式,该字符串为指定长度。 543 | */ 544 | public static final String padl(final Number num, final int size) { 545 | return padl(num.toString(), size, '0'); 546 | } 547 | 548 | /** 549 | * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 550 | * 551 | * @param s 原始字符串 552 | * @param size 字符串指定长度 553 | * @param c 用于补齐的字符 554 | * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 555 | */ 556 | public static final String padl(final String s, final int size, final char c) { 557 | final StringBuilder sb = new StringBuilder(size); 558 | if (s != null) { 559 | final int len = s.length(); 560 | if (s.length() <= size) { 561 | for (int i = size - len; i > 0; i--) { 562 | sb.append(c); 563 | } 564 | sb.append(s); 565 | } else { 566 | return s.substring(len - size, len); 567 | } 568 | } else { 569 | for (int i = size; i > 0; i--) { 570 | sb.append(c); 571 | } 572 | } 573 | return sb.toString(); 574 | } 575 | } -------------------------------------------------------------------------------- /common/src/main/java/com/rosy/common/utils/ThrowUtils.java: -------------------------------------------------------------------------------- 1 | package com.rosy.common.utils; 2 | 3 | import com.rosy.common.enums.ErrorCode; 4 | import com.rosy.common.exception.BusinessException; 5 | 6 | /** 7 | * 抛异常工具类 8 | */ 9 | public class ThrowUtils { 10 | 11 | /** 12 | * 条件成立则抛异常 13 | */ 14 | public static void throwIf(boolean condition, RuntimeException runtimeException) { 15 | if (condition) { 16 | throw runtimeException; 17 | } 18 | } 19 | 20 | /** 21 | * 条件成立则抛异常 22 | */ 23 | public static void throwIf(boolean condition, ErrorCode errorCode) { 24 | throwIf(condition, new BusinessException(errorCode)); 25 | } 26 | 27 | /** 28 | * 条件成立则抛异常 29 | */ 30 | public static void throwIf(boolean condition, ErrorCode errorCode, String message) { 31 | throwIf(condition, new BusinessException(errorCode, message)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /framework/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.rosy 6 | springboot3.4.x-init 7 | 0.0.1 8 | 9 | 10 | framework 11 | 12 | framework 13 | 14 | 15 | 16 | com.rosy 17 | main 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-security 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-aop 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-webflux 33 | 34 | 35 | 36 | org.springdoc 37 | springdoc-openapi-starter-webmvc-ui 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/aspect/LogAspect.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.aspect; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import lombok.extern.slf4j.Slf4j; 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 java.util.Arrays; 15 | import java.util.UUID; 16 | 17 | @Aspect 18 | @Component 19 | @Slf4j 20 | public class LogAspect { 21 | 22 | /** 23 | * 拦截带有 @LogTag 注解的方法 24 | */ 25 | @Around("@annotation(com.rosy.common.annotation.LogTag)") 26 | public Object doInterceptor(ProceedingJoinPoint point) throws Throwable { 27 | // 计时 28 | StopWatch stopWatch = new StopWatch(); 29 | stopWatch.start(); 30 | // 获取请求路径 31 | RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); 32 | HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); 33 | // 生成请求唯一 id 34 | String requestId = UUID.randomUUID().toString(); 35 | String url = httpServletRequest.getRequestURI(); 36 | // 获取请求参数 37 | Object[] args = point.getArgs(); 38 | String reqParam = "[" + String.join(", ", Arrays.stream(args).map(Object::toString).toArray(String[]::new)) + "]"; 39 | // 输出请求日志 40 | log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url, 41 | httpServletRequest.getRemoteHost(), reqParam); 42 | // 执行原方法 43 | Object result = point.proceed(); 44 | // 输出响应日志 45 | stopWatch.stop(); 46 | long totalTimeMillis = stopWatch.getTotalTimeMillis(); 47 | log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis); 48 | return result; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/aspect/ValidationAspect.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.aspect; 2 | 3 | import com.rosy.common.enums.ErrorCode; 4 | import com.rosy.common.exception.BusinessException; 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 | 10 | @Aspect 11 | @Component 12 | public class ValidationAspect { 13 | @Around("@annotation(com.rosy.common.annotation.ValidateRequest)") 14 | public Object validateRequest(ProceedingJoinPoint joinPoint) throws Throwable { 15 | for (Object arg : joinPoint.getArgs()) { 16 | if (arg == null) { 17 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 18 | } 19 | } 20 | return joinPoint.proceed(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/config/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.config; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 5 | import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; 6 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class MybatisPlusConfig { 12 | @Bean 13 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 14 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 15 | interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); //乐观锁 16 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加 17 | // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType 18 | return interceptor; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/config/OpenAPIConfig.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.config; 2 | 3 | import io.swagger.v3.oas.models.OpenAPI; 4 | import io.swagger.v3.oas.models.info.Contact; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import io.swagger.v3.oas.models.info.License; 7 | import org.springdoc.core.models.GroupedOpenApi; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class OpenAPIConfig { 13 | 14 | // 定制 OpenAPI 的基本信息 15 | @Bean 16 | public OpenAPI customOpenAPI() { 17 | return new OpenAPI() 18 | .info(new Info() 19 | .title("SpringBoot3.4.x-init") 20 | .description("一个简易的 SpringBoot 项目初始化模板——帮助开发者快速搭建项目") 21 | .version("1.0.0") 22 | .contact(new Contact() 23 | .name("Rosy") 24 | .email("2156722358@qq.com")) 25 | .license(new License() 26 | .name("Apache 2.0") 27 | .url("https://www.apache.org/licenses/LICENSE-2.0.html"))); 28 | } 29 | 30 | // 配置 API 分组 31 | @Bean 32 | public GroupedOpenApi publicApi() { 33 | return GroupedOpenApi.builder() 34 | .group("public") 35 | .pathsToMatch("/**") 36 | .build(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.config; 2 | 3 | import com.alibaba.fastjson2.JSON; 4 | import com.alibaba.fastjson2.JSONReader; 5 | import com.alibaba.fastjson2.JSONWriter; 6 | import com.alibaba.fastjson2.filter.Filter; 7 | import com.rosy.common.constant.CommonConstant; 8 | import org.springframework.cache.annotation.CachingConfigurer; 9 | import org.springframework.cache.annotation.EnableCaching; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.data.redis.connection.RedisConnectionFactory; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.data.redis.core.script.DefaultRedisScript; 15 | import org.springframework.data.redis.serializer.RedisSerializer; 16 | import org.springframework.data.redis.serializer.SerializationException; 17 | import org.springframework.data.redis.serializer.StringRedisSerializer; 18 | 19 | import java.nio.charset.Charset; 20 | import java.nio.charset.StandardCharsets; 21 | 22 | @Configuration 23 | @EnableCaching 24 | public class RedisConfig implements CachingConfigurer { 25 | @Bean 26 | public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { 27 | RedisTemplate template = new RedisTemplate<>(); 28 | template.setConnectionFactory(connectionFactory); 29 | 30 | FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer<>(Object.class); 31 | 32 | // 使用StringRedisSerializer来序列化和反序列化redis的key值 33 | template.setKeySerializer(new StringRedisSerializer()); 34 | template.setValueSerializer(serializer); 35 | 36 | // Hash的key也采用StringRedisSerializer的序列化方式 37 | template.setHashKeySerializer(new StringRedisSerializer()); 38 | template.setHashValueSerializer(serializer); 39 | 40 | template.afterPropertiesSet(); 41 | return template; 42 | } 43 | 44 | @Bean 45 | public DefaultRedisScript limitScript() { 46 | DefaultRedisScript redisScript = new DefaultRedisScript<>(); 47 | redisScript.setScriptText(limitScriptText()); 48 | redisScript.setResultType(Long.class); 49 | return redisScript; 50 | } 51 | 52 | /** 53 | * 限流脚本 54 | */ 55 | private String limitScriptText() { 56 | return """ 57 | local key = KEYS[1] 58 | local count = tonumber(ARGV[1]) 59 | local time = tonumber(ARGV[2]) 60 | local current = redis.call('get', key); 61 | if current and tonumber(current) > count then 62 | return tonumber(current); 63 | end 64 | current = redis.call('incr', key) 65 | if tonumber(current) == 1 then 66 | redis.call('expire', key, time) 67 | end 68 | return tonumber(current);"""; 69 | } 70 | 71 | public static class FastJson2JsonRedisSerializer implements RedisSerializer { 72 | public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; 73 | 74 | static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(CommonConstant.JSON_WHITELIST_STR); 75 | 76 | private final Class clazz; 77 | 78 | public FastJson2JsonRedisSerializer(Class clazz) { 79 | super(); 80 | this.clazz = clazz; 81 | } 82 | 83 | @Override 84 | public byte[] serialize(T t) throws SerializationException { 85 | if (t == null) { 86 | return new byte[0]; 87 | } 88 | return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); 89 | } 90 | 91 | @Override 92 | public T deserialize(byte[] bytes) throws SerializationException { 93 | if (bytes == null || bytes.length == 0) { 94 | return null; 95 | } 96 | String str = new String(bytes, DEFAULT_CHARSET); 97 | 98 | return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 8 | import org.springframework.security.config.http.SessionCreationPolicy; 9 | import org.springframework.security.web.SecurityFilterChain; 10 | 11 | @Configuration 12 | @EnableMethodSecurity 13 | public class SecurityConfig { 14 | @Bean 15 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 16 | http 17 | .csrf(AbstractHttpConfigurer::disable) // 禁用 CSRF 保护 18 | .sessionManagement(sessionManagement -> sessionManagement 19 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 配置为无状态会话 20 | ) 21 | .authorizeHttpRequests(authorize -> authorize 22 | .requestMatchers("/**").permitAll() // 所有请求都允许 可以根据自己需求更改 23 | ) 24 | .formLogin(AbstractHttpConfigurer::disable) 25 | .logout(AbstractHttpConfigurer::disable); 26 | 27 | return http.build(); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/config/WebClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.http.codec.json.Jackson2JsonDecoder; 8 | import org.springframework.web.reactive.function.client.ExchangeStrategies; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | 11 | @Configuration 12 | public class WebClientConfig { 13 | @Bean 14 | public WebClient webClient(WebClient.Builder builder, ObjectMapper objectMapper) { 15 | return builder 16 | .exchangeStrategies(ExchangeStrategies.builder() 17 | .codecs(configurer -> { 18 | configurer.customCodecs().registerWithDefaultConfig(new Jackson2JsonDecoder(objectMapper, MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON)); 19 | }) 20 | .build()) 21 | .build(); 22 | } 23 | } -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/config/properties/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.config.properties; 2 | 3 | import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import java.util.TimeZone; 8 | 9 | @Configuration 10 | public class ApplicationProperties { 11 | /** 12 | * 时区配置 13 | */ 14 | @Bean 15 | public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { 16 | return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/config/properties/PathProperties.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.config.properties; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | 8 | @ConfigurationProperties(prefix = "path") 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class PathProperties { 13 | public String fileUploadPath; 14 | } 15 | -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/handler/CustomMetaObjectHandler.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.handler; 2 | 3 | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 4 | import org.apache.ibatis.reflection.MetaObject; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Date; 8 | 9 | //TODO: 1.这里的createTime和updateTime的类型要根据数据库字段的类型来修改 10 | //TODO: 2.这里的createBy和updateBy的类型要根据数据库字段的类型来修改, 生成的逻辑也要根据实际情况来修改,这里0表示系统超级管理员或者默认值 11 | @Component 12 | public class CustomMetaObjectHandler implements MetaObjectHandler { 13 | 14 | @Override 15 | public void insertFill(MetaObject metaObject) { 16 | this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); 17 | this.strictInsertFill(metaObject, "updateTime", Date.class, new Date()); 18 | this.strictInsertFill(metaObject, "createBy", Long.class, 0L); 19 | this.strictInsertFill(metaObject, "updateBy", Long.class, 0L); 20 | } 21 | 22 | @Override 23 | public void updateFill(MetaObject metaObject) { 24 | this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); 25 | this.strictUpdateFill(metaObject, "updateBy", Long.class, 0L); 26 | } 27 | } -------------------------------------------------------------------------------- /framework/src/main/java/com/rosy/framework/handler/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.rosy.framework.handler; 2 | 3 | import com.rosy.common.domain.entity.ApiResponse; 4 | import com.rosy.common.enums.ErrorCode; 5 | import com.rosy.common.exception.BusinessException; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | 10 | /** 11 | * 全局异常处理器 12 | */ 13 | @RestControllerAdvice 14 | @Slf4j 15 | public class GlobalExceptionHandler { 16 | 17 | @ExceptionHandler(BusinessException.class) 18 | public ApiResponse businessExceptionHandler(BusinessException e) { 19 | log.error("BusinessException", e); 20 | return ApiResponse.error(e.getCode(), e.getMessage()); 21 | } 22 | 23 | @ExceptionHandler(RuntimeException.class) 24 | public ApiResponse runtimeExceptionHandler(RuntimeException e) { 25 | log.error("RuntimeException", e); 26 | return ApiResponse.error(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /main/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.rosy 6 | springboot3.4.x-init 7 | 0.0.1 8 | 9 | 10 | main 11 | 12 | main 13 | 14 | 15 | UTF-8 16 | 17 | 18 | 19 | 20 | com.rosy 21 | common 22 | 23 | 24 | 25 | 26 | mysql 27 | mysql-connector-java 28 | 29 | 30 | 31 | 32 | com.baomidou 33 | mybatis-plus-spring-boot3-starter 34 | 35 | 36 | 37 | com.baomidou 38 | mybatis-plus-jsqlparser 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-data-redis 45 | 46 | 47 | org.springframework.session 48 | spring-session-data-redis 49 | 50 | 51 | 52 | org.springframework.security 53 | spring-security-crypto 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/domain/dto/item/ItemAddRequest.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.domain.dto.item; 2 | 3 | import jakarta.validation.constraints.*; 4 | import lombok.Data; 5 | 6 | import java.io.Serial; 7 | import java.io.Serializable; 8 | 9 | @Data 10 | public class ItemAddRequest implements Serializable { 11 | 12 | @Serial 13 | private static final long serialVersionUID = 1L; 14 | 15 | /** 16 | * 名称 17 | * 必填,长度限制为 1 到 100 个字符 18 | */ 19 | @NotBlank(message = "名称不能为空") 20 | @Size(max = 100, message = "名称长度不能超过 100 个字符") 21 | private String name; 22 | 23 | /** 24 | * 简介/内容 25 | * 非必填,最大长度 500 26 | */ 27 | @Size(max = 500, message = "简介长度不能超过 500 个字符") 28 | private String description; 29 | 30 | /** 31 | * 类型 32 | * 必填,值范围为 1 到 127 33 | */ 34 | @NotNull(message = "类型不能为空") 35 | @Min(value = 1, message = "类型值不能小于 1") 36 | @Max(value = 127, message = "类型值不能大于 127") 37 | private Byte type; 38 | 39 | /** 40 | * 状态 41 | * 必填,值范围为 0 或 1 42 | */ 43 | @NotNull(message = "状态不能为空") 44 | @Min(value = 0, message = "状态值只能为 0 或 1") 45 | @Max(value = 1, message = "状态值只能为 0 或 1") 46 | private Byte status; 47 | 48 | /** 49 | * 排序字段,用于控制物品显示顺序 50 | * 非必填,值必须为正整数 51 | */ 52 | @PositiveOrZero(message = "排序字段必须为非负整数") 53 | private Integer sortOrder; 54 | } 55 | -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/domain/dto/item/ItemQueryRequest.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.domain.dto.item; 2 | 3 | import com.rosy.common.domain.entity.PageRequest; 4 | import jakarta.validation.constraints.*; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | import java.io.Serial; 9 | import java.io.Serializable; 10 | import java.time.LocalDateTime; 11 | 12 | @EqualsAndHashCode(callSuper = true) 13 | @Data 14 | public class ItemQueryRequest extends PageRequest implements Serializable { 15 | 16 | @Serial 17 | private static final long serialVersionUID = 1L; 18 | 19 | /** 20 | * ID 21 | * 必须为正整数 22 | */ 23 | @Positive(message = "ID 必须为正整数") 24 | private Long id; 25 | 26 | /** 27 | * 名称 28 | * 最大长度 100,可选 29 | */ 30 | @Size(max = 100, message = "名称长度不能超过 100 个字符") 31 | private String name; 32 | 33 | /** 34 | * 简介/内容 35 | * 最大长度 500,可选 36 | */ 37 | @Size(max = 500, message = "简介长度不能超过 500 个字符") 38 | private String description; 39 | 40 | /** 41 | * 类型 42 | * 值范围为 1-127,可选 43 | */ 44 | @Min(value = 1, message = "类型值不能小于 1") 45 | @Max(value = 127, message = "类型值不能大于 127") 46 | private Byte type; 47 | 48 | /** 49 | * 状态 50 | * 只能为 0 或 1,可选 51 | */ 52 | @Min(value = 0, message = "状态值只能为 0 或 1") 53 | @Max(value = 1, message = "状态值只能为 0 或 1") 54 | private Byte status; 55 | 56 | /** 57 | * 创建者ID,关联用户表 58 | * 必须为正整数,可选 59 | */ 60 | @Positive(message = "创建者ID 必须为正整数") 61 | private Long creatorId; 62 | 63 | /** 64 | * 创建时间 65 | * 可选 66 | */ 67 | private LocalDateTime createTime; 68 | 69 | /** 70 | * 更新者ID,关联用户表 71 | * 必须为正整数,可选 72 | */ 73 | @Positive(message = "更新者ID 必须为正整数") 74 | private Long updaterId; 75 | 76 | /** 77 | * 更新时间 78 | * 可选 79 | */ 80 | private LocalDateTime updateTime; 81 | 82 | /** 83 | * 排序顺序 84 | */ 85 | @PositiveOrZero(message = "排序字段必须为非负整数") 86 | private String sortOrder; 87 | } 88 | -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/domain/dto/item/ItemUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.domain.dto.item; 2 | 3 | import jakarta.validation.constraints.*; 4 | import lombok.Data; 5 | 6 | import java.io.Serial; 7 | import java.io.Serializable; 8 | 9 | @Data 10 | public class ItemUpdateRequest implements Serializable { 11 | 12 | @Serial 13 | private static final long serialVersionUID = 1L; 14 | 15 | /** 16 | * ID 17 | * 必须为正整数,不能为空 18 | */ 19 | @NotNull(message = "ID 不能为空") 20 | @Positive(message = "ID 必须为正整数") 21 | private Long id; 22 | 23 | /** 24 | * 名称 25 | * 最大长度 100,可选 26 | */ 27 | @Size(max = 100, message = "名称长度不能超过 100 个字符") 28 | private String name; 29 | 30 | /** 31 | * 简介/内容 32 | * 最大长度 500,可选 33 | */ 34 | @Size(max = 500, message = "简介长度不能超过 500 个字符") 35 | private String description; 36 | 37 | /** 38 | * 类型 39 | * 值范围为 1-127,可选 40 | */ 41 | @Min(value = 1, message = "类型值不能小于 1") 42 | @Max(value = 127, message = "类型值不能大于 127") 43 | private Byte type; 44 | 45 | /** 46 | * 状态 47 | * 只能为 0 或 1,可选 48 | */ 49 | @Min(value = 0, message = "状态值只能为 0 或 1") 50 | @Max(value = 1, message = "状态值只能为 0 或 1") 51 | private Byte status; 52 | 53 | /** 54 | * 排序字段,用于控制物品显示顺序 55 | * 必须为非负整数,可选 56 | */ 57 | @PositiveOrZero(message = "排序字段必须为非负整数") 58 | private Integer sortOrder; 59 | } 60 | -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/domain/entity/Item.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.domain.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.Data; 5 | 6 | import java.io.Serial; 7 | import java.io.Serializable; 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | *

12 | * 物品表 13 | *

14 | * 15 | * @author Rosy 16 | * @since 2025-01-19 17 | */ 18 | @Data 19 | @TableName("item") 20 | public class Item implements Serializable { 21 | 22 | @Serial 23 | private static final long serialVersionUID = 1L; 24 | 25 | /** 26 | * ID 27 | */ 28 | @TableId(value = "id", type = IdType.AUTO) 29 | private Long id; 30 | 31 | /** 32 | * 名称 33 | */ 34 | private String name; 35 | 36 | /** 37 | * 简介/内容 38 | */ 39 | private String description; 40 | 41 | /** 42 | * 类型 43 | */ 44 | private Byte type; 45 | 46 | /** 47 | * 类型 48 | */ 49 | private Byte status; 50 | 51 | /** 52 | * 创建者ID,关联用户表 53 | */ 54 | @TableField(fill = FieldFill.INSERT) 55 | private Long creatorId; 56 | 57 | /** 58 | * 创建时间 59 | */ 60 | @TableField(fill = FieldFill.INSERT) 61 | private LocalDateTime createTime; 62 | 63 | /** 64 | * 更新者ID,关联用户表 65 | */ 66 | @TableField(fill = FieldFill.INSERT_UPDATE) 67 | private Long updaterId; 68 | 69 | /** 70 | * 更新时间 71 | */ 72 | @TableField(fill = FieldFill.INSERT_UPDATE) 73 | private LocalDateTime updateTime; 74 | 75 | /** 76 | * 乐观锁版本号 77 | */ 78 | @Version 79 | private Byte version; 80 | 81 | /** 82 | * 是否删除:0-未删除,1-已删除 83 | */ 84 | @TableLogic 85 | private Byte isDeleted; 86 | 87 | /** 88 | * 排序字段,用于控制物品显示顺序 89 | */ 90 | private Integer sortOrder; 91 | } 92 | -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/domain/vo/ItemVO.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.domain.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serial; 6 | import java.io.Serializable; 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | public class ItemVO implements Serializable { 11 | 12 | @Serial 13 | private static final long serialVersionUID = 1L; 14 | 15 | /** 16 | * ID 17 | */ 18 | private Long id; 19 | 20 | /** 21 | * 名称 22 | */ 23 | private String name; 24 | 25 | /** 26 | * 简介/内容 27 | */ 28 | private String description; 29 | 30 | /** 31 | * 类型 32 | */ 33 | private Byte type; 34 | 35 | /** 36 | * 类型 37 | */ 38 | private Byte status; 39 | 40 | /** 41 | * 创建者ID,关联用户表 42 | */ 43 | private Long creatorId; 44 | 45 | /** 46 | * 创建时间 47 | */ 48 | private LocalDateTime createTime; 49 | 50 | /** 51 | * 更新者ID,关联用户表 52 | */ 53 | private Long updaterId; 54 | 55 | /** 56 | * 更新时间 57 | */ 58 | private LocalDateTime updateTime; 59 | 60 | /** 61 | * 排序字段,用于控制物品显示顺序 62 | */ 63 | private Integer sortOrder; 64 | } 65 | -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/domain/vo/LoginUserVO.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.domain.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serial; 6 | import java.io.Serializable; 7 | 8 | @Data 9 | public class LoginUserVO implements Serializable { 10 | 11 | @Serial 12 | private static final long serialVersionUID = 1L; 13 | /** 14 | * 用户 id 15 | */ 16 | private Long id; 17 | /** 18 | * 用户昵称 19 | */ 20 | private String userName; 21 | /** 22 | * 用户角色:user/admin/ban 23 | */ 24 | private String userRole; 25 | } -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/domain/vo/UserVO.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.domain.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serial; 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | @Data 10 | public class UserVO implements Serializable { 11 | 12 | @Serial 13 | private static final long serialVersionUID = 1L; 14 | /** 15 | * id 16 | */ 17 | private Long id; 18 | /** 19 | * 用户昵称 20 | */ 21 | private String userName; 22 | /** 23 | * 用户头像 24 | */ 25 | private String userAvatar; 26 | /** 27 | * 用户简介 28 | */ 29 | private String userProfile; 30 | /** 31 | * 用户角色:user/admin/ban 32 | */ 33 | private String userRole; 34 | /** 35 | * 创建时间 36 | */ 37 | private Date createTime; 38 | } -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/mapper/ItemMapper.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.rosy.main.domain.entity.Item; 5 | 6 | /** 7 | *

8 | * 物品表 Mapper 接口 9 | *

10 | * 11 | * @author Rosy 12 | * @since 2025-01-19 13 | */ 14 | public interface ItemMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/service/IItemService.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.service; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.rosy.main.domain.dto.item.ItemQueryRequest; 6 | import com.rosy.main.domain.entity.Item; 7 | import com.rosy.main.domain.vo.ItemVO; 8 | 9 | /** 10 | *

11 | * 物品表 服务类 12 | *

13 | * 14 | * @author Rosy 15 | * @since 2025-01-19 16 | */ 17 | public interface IItemService extends IService { 18 | 19 | ItemVO getItemVO(Item item); 20 | 21 | LambdaQueryWrapper getQueryWrapper(ItemQueryRequest itemQueryRequest); 22 | } 23 | -------------------------------------------------------------------------------- /main/src/main/java/com/rosy/main/service/impl/ItemServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.rosy.main.service.impl; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.rosy.common.enums.ErrorCode; 7 | import com.rosy.common.exception.BusinessException; 8 | import com.rosy.common.utils.QueryWrapperUtil; 9 | import com.rosy.main.domain.dto.item.ItemQueryRequest; 10 | import com.rosy.main.domain.entity.Item; 11 | import com.rosy.main.domain.vo.ItemVO; 12 | import com.rosy.main.mapper.ItemMapper; 13 | import com.rosy.main.service.IItemService; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.Optional; 17 | 18 | /** 19 | *

20 | * 物品表 服务实现类 21 | *

22 | * 23 | * @author Rosy 24 | * @since 2025-01-19 25 | */ 26 | @Service 27 | public class ItemServiceImpl extends ServiceImpl implements IItemService { 28 | @Override 29 | public ItemVO getItemVO(Item item) { 30 | return Optional.ofNullable(item) 31 | .map(i -> BeanUtil.copyProperties(i, ItemVO.class)) 32 | .orElse(null); 33 | } 34 | 35 | 36 | @Override 37 | public LambdaQueryWrapper getQueryWrapper(ItemQueryRequest itemQueryRequest) { 38 | if (itemQueryRequest == null) { 39 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空"); 40 | } 41 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); 42 | 43 | // 动态添加条件 44 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getId(), Item::getId); 45 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getName(), Item::getName); 46 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getDescription(), Item::getDescription); 47 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getType(), Item::getType); 48 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getStatus(), Item::getStatus); 49 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getCreatorId(), Item::getCreatorId); 50 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getCreateTime(), Item::getCreateTime); 51 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getUpdaterId(), Item::getUpdaterId); 52 | QueryWrapperUtil.addCondition(queryWrapper, itemQueryRequest.getUpdateTime(), Item::getUpdateTime); 53 | 54 | // 添加排序条件 55 | QueryWrapperUtil.addSortCondition(queryWrapper, 56 | itemQueryRequest.getSortField(), 57 | itemQueryRequest.getSortOrder(), 58 | Item::getId); 59 | 60 | return queryWrapper; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /main/src/main/resources/mapper/ItemMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.rosy 6 | springboot3.4.x-init 7 | 0.0.1 8 | pom 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 3.4.0 14 | 15 | 16 | springboot3.4.x-init 17 | 18 | common 19 | framework 20 | web 21 | main 22 | 23 | 24 | 25 | 26 | 18 27 | 18 28 | ${target.java.version} 29 | ${target.java.version} 30 | UTF-8 31 | 32 | 3.5.9 33 | 5.8.34 34 | 33.3.1-jre 35 | 4.12.0 36 | 2.0.53 37 | 8.0.33 38 | 3.0.0 39 | 3.18.1 40 | 4.0.3 41 | 0.12.6 42 | 3.5.9 43 | 2.11.0 44 | 2.7.0 45 | 3.17.0 46 | 2.4.1 47 | 6.1.0 48 | 49 | 2023.0.3.2 50 | 2023.0.5 51 | 52 | 53 | 54 | 55 | 56 | 57 | com.rosy 58 | common 59 | 0.0.1 60 | 61 | 62 | 63 | 64 | com.rosy 65 | framework 66 | 0.0.1 67 | 68 | 69 | 70 | 71 | com.rosy 72 | web 73 | 0.0.1 74 | 75 | 76 | 77 | 78 | com.rosy 79 | main 80 | 0.0.1 81 | 82 | 83 | 84 | 85 | cn.hutool 86 | hutool-all 87 | ${hutool-all.version} 88 | 89 | 90 | 91 | 92 | com.google.guava 93 | guava 94 | ${guava.version} 95 | 96 | 97 | 98 | 99 | com.squareup.okhttp3 100 | okhttp 101 | ${okhttp.version} 102 | 103 | 104 | 105 | 106 | com.alibaba.fastjson2 107 | fastjson2 108 | ${fastjson2.version} 109 | 110 | 111 | com.alibaba.fastjson2 112 | fastjson2-extension 113 | ${fastjson2.version} 114 | 115 | 116 | com.alibaba.fastjson2 117 | fastjson2-extension-spring6 118 | ${fastjson2.version} 119 | 120 | 121 | 122 | com.google.code.gson 123 | gson 124 | ${gson.version} 125 | 126 | 127 | 128 | org.apache.commons 129 | commons-lang3 130 | ${commons-lang3.version} 131 | 132 | 133 | 134 | 135 | com.alibaba 136 | easyexcel 137 | ${easyexcel.version} 138 | 139 | 140 | 141 | 142 | io.jsonwebtoken 143 | jjwt 144 | ${jjwt.version} 145 | 146 | 147 | 148 | 149 | com.baomidou 150 | mybatis-plus-spring-boot3-starter 151 | ${mybatis-plus-spring-boot3-starter.version} 152 | 153 | 154 | com.baomidou 155 | mybatis-plus-jsqlparser 156 | ${mybatis-plus-jsqlparser.version} 157 | 158 | 159 | 160 | 161 | mysql 162 | mysql-connector-java 163 | ${mysql-connector-java.version} 164 | 165 | 166 | 167 | 168 | org.springdoc 169 | springdoc-openapi-starter-webmvc-ui 170 | ${springdoc-openapi-starter-webmvc-ui.version} 171 | 172 | 173 | 174 | 175 | org.apache.velocity 176 | velocity-engine-core 177 | ${velocity.version} 178 | 179 | 180 | 181 | jakarta.servlet 182 | jakarta.servlet-api 183 | ${servlet.version} 184 | 185 | 186 | 187 | com.alibaba.cloud 188 | spring-cloud-alibaba-dependencies 189 | ${spring.cloud.alibaba.version} 190 | pom 191 | import 192 | 193 | 194 | 195 | org.springframework.cloud 196 | spring-cloud-dependencies 197 | ${spring.cloud.version} 198 | pom 199 | import 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /web/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.rosy 6 | springboot3.4.x-init 7 | 0.0.1 8 | 9 | 10 | web 11 | 12 | web 13 | 14 | 15 | 16 | com.rosy 17 | framework 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-devtools 33 | 34 | 35 | 36 | com.baomidou 37 | mybatis-plus-generator 38 | 3.5.9 39 | 40 | 41 | 42 | org.apache.velocity 43 | velocity-engine-core 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | 55 | org.projectlombok 56 | lombok 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-compiler-plugin 65 | 3.13.0 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /web/src/main/java/com/rosy/WebApplication.java: -------------------------------------------------------------------------------- 1 | package com.rosy; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 7 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 8 | 9 | @SpringBootApplication 10 | @ConfigurationPropertiesScan("com.rosy.framework.config") 11 | @EnableAspectJAutoProxy 12 | @MapperScan("com.rosy.**.mapper") 13 | public class WebApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(WebApplication.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/src/main/java/com/rosy/web/controller/main/ItemController.java: -------------------------------------------------------------------------------- 1 | package com.rosy.web.controller.main; 2 | 3 | import cn.hutool.core.bean.BeanUtil; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.rosy.common.annotation.ValidateRequest; 6 | import com.rosy.common.domain.entity.ApiResponse; 7 | import com.rosy.common.domain.entity.IdRequest; 8 | import com.rosy.common.enums.ErrorCode; 9 | import com.rosy.common.exception.BusinessException; 10 | import com.rosy.common.utils.PageUtils; 11 | import com.rosy.common.utils.ThrowUtils; 12 | import com.rosy.main.domain.dto.item.ItemAddRequest; 13 | import com.rosy.main.domain.dto.item.ItemQueryRequest; 14 | import com.rosy.main.domain.dto.item.ItemUpdateRequest; 15 | import com.rosy.main.domain.entity.Item; 16 | import com.rosy.main.domain.vo.ItemVO; 17 | import com.rosy.main.service.IItemService; 18 | import jakarta.annotation.Resource; 19 | import jakarta.servlet.http.HttpServletRequest; 20 | import org.springframework.beans.BeanUtils; 21 | import org.springframework.web.bind.annotation.*; 22 | 23 | /** 24 | *

25 | * 物品表 前端控制器 26 | *

27 | * 28 | * @author Rosy 29 | * @since 2025-01-19 30 | */ 31 | @RestController 32 | @RequestMapping("/item") 33 | public class ItemController { 34 | @Resource 35 | IItemService itemService; 36 | 37 | // region 增删改查 38 | 39 | /** 40 | * 创建 41 | */ 42 | @PostMapping("/add") 43 | @ValidateRequest 44 | public ApiResponse addItem(@RequestBody ItemAddRequest itemAddRequest) { 45 | Item item = new Item(); 46 | BeanUtils.copyProperties(itemAddRequest, item); 47 | boolean result = itemService.save(item); 48 | ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); 49 | return ApiResponse.success(item.getId()); 50 | } 51 | 52 | /** 53 | * 删除 54 | */ 55 | @PostMapping("/delete") 56 | @ValidateRequest 57 | public ApiResponse deleteItem(@RequestBody IdRequest idRequest) { 58 | boolean result = itemService.removeById(idRequest.getId()); 59 | return ApiResponse.success(result); 60 | } 61 | 62 | /** 63 | * 更新 64 | */ 65 | @PostMapping("/update") 66 | @ValidateRequest 67 | public ApiResponse updateItem(@RequestBody ItemUpdateRequest itemUpdateRequest, 68 | HttpServletRequest request) { 69 | if (itemUpdateRequest.getId() == null) { 70 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 71 | } 72 | Item item = BeanUtil.copyProperties(itemUpdateRequest, Item.class); 73 | boolean result = itemService.updateById(item); 74 | ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); 75 | return ApiResponse.success(true); 76 | } 77 | 78 | /** 79 | * 根据 id 获取 80 | */ 81 | @GetMapping("/get") 82 | public ApiResponse getItemById(long id) { 83 | if (id <= 0) { 84 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 85 | } 86 | Item item = itemService.getById(id); 87 | ThrowUtils.throwIf(item == null, ErrorCode.NOT_FOUND_ERROR); 88 | return ApiResponse.success(item); 89 | } 90 | 91 | /** 92 | * 根据 id 获取包装类 93 | */ 94 | @GetMapping("/get/vo") 95 | public ApiResponse getItemVOById(long id) { 96 | if (id <= 0) { 97 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 98 | } 99 | ApiResponse response = getItemById(id); 100 | Item item = (Item) response.get(ApiResponse.DATA_TAG); 101 | return ApiResponse.success(itemService.getItemVO(item)); 102 | } 103 | 104 | /** 105 | * 分页获取列表 106 | */ 107 | @PostMapping("/list/page") 108 | @ValidateRequest 109 | public ApiResponse listItemByPage(@RequestBody ItemQueryRequest itemQueryRequest) { 110 | long current = itemQueryRequest.getCurrent(); 111 | long size = itemQueryRequest.getPageSize(); 112 | Page itemPage = itemService.page(new Page<>(current, size), itemService.getQueryWrapper(itemQueryRequest)); 113 | return ApiResponse.success(itemPage); 114 | } 115 | 116 | /** 117 | * 分页获取封装列表 118 | */ 119 | @PostMapping("/list/page/vo") 120 | @ValidateRequest 121 | public ApiResponse listItemVOByPage(@RequestBody ItemQueryRequest itemQueryRequest) { 122 | long current = itemQueryRequest.getCurrent(); 123 | long size = itemQueryRequest.getPageSize(); 124 | // 限制爬虫 125 | ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); 126 | Page itemPage = itemService.page(new Page<>(current, size), itemService.getQueryWrapper(itemQueryRequest)); 127 | Page itemVOPage = PageUtils.convert(itemPage, itemService::getItemVO); 128 | return ApiResponse.success(itemVOPage); 129 | } 130 | 131 | // endregion 132 | } 133 | -------------------------------------------------------------------------------- /web/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vivian5510/SpringBoot3-CLI/21b949030fb31e6def747683ea3f9a24c374cd15/web/src/main/resources/application-dev.yml -------------------------------------------------------------------------------- /web/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vivian5510/SpringBoot3-CLI/21b949030fb31e6def747683ea3f9a24c374cd15/web/src/main/resources/application-prod.yml -------------------------------------------------------------------------------- /web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | address: 0.0.0.0 3 | port: 8080 4 | servlet: 5 | context-path: 6 | # cookie 30 天过期 7 | session: 8 | cookie: 9 | max-age: 2592000 10 | 11 | spring: 12 | application: 13 | name: springboot3.4.x-init 14 | # 默认 dev 环境 15 | profiles: 16 | active: dev 17 | 18 | datasource: 19 | driver-class-name: com.mysql.cj.jdbc.Driver 20 | url: ${MYSQL_URL:jdbc:mysql://localhost:3306/mysql?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai} 21 | username: ${MYSQL_USERNAME:root} 22 | password: ${MYSQL_PASSWORD:root} 23 | 24 | 25 | # redis 配置 26 | data: 27 | redis: 28 | # 地址 29 | host: ${REDIS_HOST:localhost} 30 | # 端口,默认为6379 31 | port: ${REDIS_PORT:6379} 32 | # 数据库索引 33 | database: 0 34 | # 密码 35 | password: ${REDIS_PASSWORD:redis} 36 | # 连接超时时间 37 | timeout: 10s 38 | lettuce: 39 | pool: 40 | # 连接池中的最小空闲连接 41 | min-idle: 0 42 | # 连接池中的最大空闲连接 43 | max-idle: 8 44 | # 连接池的最大数据库连接数 45 | max-active: 8 46 | # #连接池最大阻塞等待时间(使用负值表示没有限制) 47 | max-wait: -1ms 48 | 49 | mybatis-plus: 50 | configuration: 51 | log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 52 | global-config: 53 | db-config: 54 | logic-delete-field: is_deleted 55 | logic-delete-value: 1 56 | logic-not-delete-value: 0 57 | id-type: auto 58 | 59 | path: 60 | file-upload-path: xxx 61 | 62 | springdoc: 63 | api-docs: 64 | path: /v3/api-docs # 配置 OpenAPI 文档的路径 65 | swagger-ui: 66 | path: /swagger-ui.html # 配置 Swagger UI 的路径 67 | enabled: true # 启用 Swagger UI 68 | 69 | 70 | -------------------------------------------------------------------------------- /web/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | ${contextName} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 25 | UTF-8 26 | 27 | 28 | 29 | 30 | 31 | ${log.path}/info.log 32 | 33 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 34 | UTF-8 35 | 36 | 37 | 38 | ${log.path}/info-%d{yyyy-MM-dd}.%i.log.gz 39 | 40 | 100MB 41 | 42 | 15 43 | 44 | 2GB 45 | 46 | 47 | 48 | INFO 49 | ACCEPT 50 | DENY 51 | 52 | 53 | 54 | 55 | 56 | ${log.path}/error.log 57 | 58 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 59 | UTF-8 60 | 61 | 62 | ${log.path}/error-%d{yyyy-MM-dd}.%i.log.gz 63 | 100MB 64 | 15 65 | 1GB 66 | 67 | 68 | 69 | ERROR 70 | ACCEPT 71 | DENY 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /web/src/test/java/com/rosy/MybaitisPlusCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.rosy; 2 | 3 | import com.baomidou.mybatisplus.annotation.FieldFill; 4 | import com.baomidou.mybatisplus.generator.FastAutoGenerator; 5 | import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; 6 | import com.baomidou.mybatisplus.generator.fill.Column; 7 | 8 | import java.util.List; 9 | 10 | public class MybaitisPlusCodeGenerator { 11 | public static void main(String[] args) { 12 | FastAutoGenerator.create("jdbc:mysql://localhost:3306/example", "xxx", "xxx") 13 | .globalConfig(builder -> builder 14 | .author("Rosy") 15 | .outputDir("G:\\Material\\Codes\\springboot3.4.x-init\\main\\src\\main\\java") 16 | .commentDate("yyyy-MM-dd") 17 | ) 18 | .packageConfig(builder -> builder 19 | .parent("com.rosy.main") 20 | .entity("domain.entity") 21 | .mapper("mapper") 22 | .service("service") 23 | .serviceImpl("service.impl") 24 | .xml("mapper.xml") 25 | ) 26 | .strategyConfig(builder -> builder 27 | .entityBuilder() 28 | .logicDeleteColumnName("isDeleted") 29 | .versionColumnName("version") 30 | .addTableFills(List.of( 31 | new Column("create_time", FieldFill.INSERT), 32 | new Column("create_by", FieldFill.INSERT), 33 | new Column("update_time", FieldFill.INSERT_UPDATE), 34 | new Column("update_by", FieldFill.INSERT_UPDATE) 35 | )) 36 | .controllerBuilder() 37 | .enableRestStyle() 38 | ) 39 | .strategyConfig(builder -> builder 40 | .addInclude("user") 41 | .addTablePrefix("") 42 | .entityBuilder() 43 | .enableLombok() 44 | ) 45 | .templateEngine(new VelocityTemplateEngine()) 46 | .execute(); 47 | } 48 | } 49 | --------------------------------------------------------------------------------