From f768ea78f04071d33782f77310e8bea0323c9e03 Mon Sep 17 00:00:00 2001 From: whaifree Date: Sat, 12 Oct 2024 11:52:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=A8=E5=B1=80=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=E5=92=8C=E5=B9=82=E7=AD=89=E6=80=A7?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加全局异常处理器 GlobalExceptionHandler,统一处理系统异常- 新增 HTTP 状态码常量类 HttpStatus - 实现幂等性测试控制器 IdempotenceController,包括插入、更新和 Token验证操作 - 优化 TestController,调整请求映射为 POST 方法 - 新增 pom.xml 文件,配置 Spring Boot 项目依赖,包括 Knife4j、FastJson、hutool、AOP、MySQL 连接器、RabbitMQ 和 Redis 等 - 创建 SpringDemoApplication 启动类,启动 Spring Boot 应用程序 --- .../redo/redo_all_240924/LeetCode274.java | 30 +++ springDemo/pom.xml | 119 +++++++++++ .../springdemo/SpringDemoApplication.java | 13 ++ .../config/GlobalExceptionHandler.java | 23 ++ .../springdemo/controller/TestController.java | 6 +- .../idempotence/IdempotenceController.java | 135 ++++++++++++ .../cn/whaifree/springdemo/utils/ResVo.java | 196 ++++++++++++++++++ .../utils/constants/HttpStatus.java | 95 +++++++++ .../src/main/resources/application.yaml | 22 ++ 9 files changed, 636 insertions(+), 3 deletions(-) create mode 100644 ForJdk17/src/main/java/cn/whaifree/redo/redo_all_240924/LeetCode274.java create mode 100644 springDemo/pom.xml create mode 100644 springDemo/src/main/java/cn/whaifree/springdemo/SpringDemoApplication.java create mode 100644 springDemo/src/main/java/cn/whaifree/springdemo/config/GlobalExceptionHandler.java create mode 100644 springDemo/src/main/java/cn/whaifree/springdemo/controller/idempotence/IdempotenceController.java create mode 100644 springDemo/src/main/java/cn/whaifree/springdemo/utils/ResVo.java create mode 100644 springDemo/src/main/java/cn/whaifree/springdemo/utils/constants/HttpStatus.java diff --git a/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_240924/LeetCode274.java b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_240924/LeetCode274.java new file mode 100644 index 0000000..9d32d58 --- /dev/null +++ b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_240924/LeetCode274.java @@ -0,0 +1,30 @@ +package cn.whaifree.redo.redo_all_240924; + +import java.util.Arrays; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 11:49 + * @注释 + */ +public class LeetCode274 { + + class Solution { + public int hIndex(int[] citations) { + Arrays.sort(citations); + + // 0 1 3 5 6 + // | i | h | + for (int i = 0; i < citations.length; i++) { + int h = citations.length - i; + if (citations[i] >= h) { + return h; + } + } + return 0; + } + + } + +} diff --git a/springDemo/pom.xml b/springDemo/pom.xml new file mode 100644 index 0000000..c9a71c1 --- /dev/null +++ b/springDemo/pom.xml @@ -0,0 +1,119 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.4 + + + cn.whaifree + springDemo + 0.0.1-SNAPSHOT + springDemo + springDemo + + + + + + + + + + + + + + + 17 + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + 4.4.0 + + + + + + com.alibaba + fastjson + 2.0.36 + + + + cn.hutool + hutool-all + 5.8.13 + + + + org.springframework.boot + spring-boot-starter-aop + + + mysql-connector-java + + mysql + 8.0.33 + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.amqp + spring-rabbit-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/springDemo/src/main/java/cn/whaifree/springdemo/SpringDemoApplication.java b/springDemo/src/main/java/cn/whaifree/springdemo/SpringDemoApplication.java new file mode 100644 index 0000000..8e9b70c --- /dev/null +++ b/springDemo/src/main/java/cn/whaifree/springdemo/SpringDemoApplication.java @@ -0,0 +1,13 @@ +package cn.whaifree.springdemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringDemoApplication.class, args); + } + +} diff --git a/springDemo/src/main/java/cn/whaifree/springdemo/config/GlobalExceptionHandler.java b/springDemo/src/main/java/cn/whaifree/springdemo/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..9f73033 --- /dev/null +++ b/springDemo/src/main/java/cn/whaifree/springdemo/config/GlobalExceptionHandler.java @@ -0,0 +1,23 @@ +package cn.whaifree.springdemo.config; + +import cn.whaifree.springdemo.utils.ResVo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 16:28 + * @注释 + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler{ + @ExceptionHandler(Exception.class) + public ResVo handleException(Exception e) { + log.error("系统异常", e); + return ResVo.error(e.getMessage()); + } + +} diff --git a/springDemo/src/main/java/cn/whaifree/springdemo/controller/TestController.java b/springDemo/src/main/java/cn/whaifree/springdemo/controller/TestController.java index e830029..388a970 100644 --- a/springDemo/src/main/java/cn/whaifree/springdemo/controller/TestController.java +++ b/springDemo/src/main/java/cn/whaifree/springdemo/controller/TestController.java @@ -2,8 +2,8 @@ package cn.whaifree.springdemo.controller; import cn.whaifree.springdemo.aspect.annotation.RateLimiter; import cn.whaifree.springdemo.constant.LimitType; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -17,7 +17,7 @@ import java.util.concurrent.*; */ @RestController public class TestController { - @RequestMapping("/test") + @PostMapping("/test") @RateLimiter(key = "test:", time = 10, count = 1) // 10s只能1次 public String test() { return "test"; @@ -31,7 +31,7 @@ public class TestController { * * @return */ - @RequestMapping("addContainer") + @PostMapping("addContainer") @RateLimiter(key = "addContainer:", limitType = LimitType.USER, time = 5, count = 1) // 10s只能1次 public String addContainerInstanceToCluster(@RequestBody List instances, int userId) { // 导入容器节点到集群中 diff --git a/springDemo/src/main/java/cn/whaifree/springdemo/controller/idempotence/IdempotenceController.java b/springDemo/src/main/java/cn/whaifree/springdemo/controller/idempotence/IdempotenceController.java new file mode 100644 index 0000000..f4265dd --- /dev/null +++ b/springDemo/src/main/java/cn/whaifree/springdemo/controller/idempotence/IdempotenceController.java @@ -0,0 +1,135 @@ +package cn.whaifree.springdemo.controller.idempotence; + +import cn.whaifree.springdemo.utils.ResVo; +import com.alibaba.fastjson.JSON; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.sql.PreparedStatement; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * 幂等性测试 + * + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 16:06 + * @注释 + */ +@RestController +@RequestMapping("/idempotence") +public class IdempotenceController { + + @Resource + JdbcTemplate jdbcTemplate; + @Resource + RedisTemplate redisTemplate; + + @PostMapping("query") + public ResVo query() { + List> maps = jdbcTemplate.queryForList("select * from orders"); + return ResVo.success(JSON.toJSONString(maps)); + } + + /** + * CREATE TABLE `orders` ( + * `id` INT AUTO_INCREMENT PRIMARY KEY, + * `order_no` VARCHAR(100) UNIQUE NOT NULL COMMENT '订单号', -- 唯一 + * `user_id` INT NOT NULL COMMENT '用户ID', + * `total_amount` DECIMAL(10,2) NOT NULL COMMENT '订单总金额', + * `status` TINYINT DEFAULT 0 COMMENT '订单状态: 0-未支付, 1-已支付', + * `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' + * ); + *

+ * java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'order_no' + * at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:118) ~[mysql-connector-j-8.0.33.jar:8.0.33] + * at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-j-8.0.33.jar:8.0.33] + * + * @param orderId + * @param userId + * @param totalAmount + * @return + */ + @PostMapping("insertByMySQL") + public ResVo insertMySQL(String orderId, String userId, Integer totalAmount) { + + int update = jdbcTemplate.update(con -> { + PreparedStatement preparedStatement = con.prepareStatement( + "INSERT INTO orders (order_no, user_id, total_amount) " + + "VALUES (?, ?, ?);" + ); + preparedStatement.setString(1, orderId); + preparedStatement.setString(2, userId); + preparedStatement.setString(3, String.valueOf(totalAmount)); + return preparedStatement; + }); + + return update > 0 ? ResVo.success("插入成功") : ResVo.error("插入失败"); + } + + /** + * 适用于更新操作 + * + * @return + */ + @PostMapping("upadteCAS") + public ResVo updateCAS(String orderId, Integer totalAmount, Integer originVersion) { + + int update = jdbcTemplate.update(con -> { + PreparedStatement preparedStatement = con.prepareStatement( + "UPDATE orders " + + "SET total_amount = ?, version=version+1 " + + "WHERE order_no = ? AND version = ?"); + preparedStatement.setInt(1, totalAmount); + preparedStatement.setString(2, orderId); + preparedStatement.setInt(3, originVersion); + return preparedStatement; + }); + + + return update > 0 ? ResVo.success("更新成功") : ResVo.error("更新失败"); + } + + + final String tokenV = "tokenV"; + + /** + * Token防刷 + */ + @PostMapping("token") + public ResVo generateToken() { + // 生成UUID + String s = UUID.randomUUID().toString(); + String key = "token:" + s; + redisTemplate.opsForValue().set(key, tokenV, 60, TimeUnit.SECONDS); + return ResVo.success(key); + } + + @PostMapping("tokenCheck") + public ResVo tokenCheck(String key) { + String script = + // 有这个token则通过,并且删除,只能一次性使用 + "if redis.call('get',KEYS[1]) == ARGV[1] " + // key argv为参数 + "then " + + "return redis.call('del',KEYS[1]) " + + "else " + + "return 0 end"; + + DefaultRedisScript tDefaultRedisScript = new DefaultRedisScript<>(script, Long.class); + + Long execute = redisTemplate.execute(tDefaultRedisScript, Arrays.asList(key), tokenV); + if (execute == 0) { + throw new RuntimeException("未通过"); + } + return ResVo.success("通过"); + } +} diff --git a/springDemo/src/main/java/cn/whaifree/springdemo/utils/ResVo.java b/springDemo/src/main/java/cn/whaifree/springdemo/utils/ResVo.java new file mode 100644 index 0000000..0231bd5 --- /dev/null +++ b/springDemo/src/main/java/cn/whaifree/springdemo/utils/ResVo.java @@ -0,0 +1,196 @@ +package cn.whaifree.springdemo.utils; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 16:30 + * @注释 + */ + + +import cn.whaifree.springdemo.utils.constants.HttpStatus; + +import java.util.HashMap; + +/** + * 操作消息提醒 + * + * @author / + */ +public class ResVo extends HashMap +{ + private static final long serialVersionUID = 1L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 ResVo 对象,使其表示一个空消息。 + */ + public ResVo() + { + } + + /** + * 初始化一个新创建的 ResVo 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public ResVo(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + public boolean isEmpty(){ + return super.get(CODE_TAG).equals(HttpStatus.FOUND); + } + + /** + * 初始化一个新创建的 ResVo 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public ResVo(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (data != null) { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static ResVo success() + { + return ResVo.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static ResVo success(Object data) + { + return ResVo.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static ResVo success(String msg) + { + return ResVo.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static ResVo success(String msg, Object data) + { + return new ResVo(HttpStatus.SUCCESS, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static ResVo warn(String msg) + { + return ResVo.warn(msg, null); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static ResVo warn(String msg, Object data) + { + return new ResVo(HttpStatus.WARN, msg, data); + } + + /** + * 返回错误消息 + * + * @return 错误消息 + */ + public static ResVo error() + { + return ResVo.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 错误消息 + */ + public static ResVo error(String msg) + { + return ResVo.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 错误消息 + */ + public static ResVo error(String msg, Object data) + { + return new ResVo(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static ResVo error(int code, String msg) + { + return new ResVo(code, msg, null); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public ResVo put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/springDemo/src/main/java/cn/whaifree/springdemo/utils/constants/HttpStatus.java b/springDemo/src/main/java/cn/whaifree/springdemo/utils/constants/HttpStatus.java new file mode 100644 index 0000000..c119d94 --- /dev/null +++ b/springDemo/src/main/java/cn/whaifree/springdemo/utils/constants/HttpStatus.java @@ -0,0 +1,95 @@ +package cn.whaifree.springdemo.utils.constants; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 16:32 + * @注释 + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 0; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int FOUND = 40004; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/springDemo/src/main/resources/application.yaml b/springDemo/src/main/resources/application.yaml index 89721b3..1aa6f99 100644 --- a/springDemo/src/main/resources/application.yaml +++ b/springDemo/src/main/resources/application.yaml @@ -1,3 +1,4 @@ + spring: datasource: url: jdbc:mysql://localhost:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai @@ -11,3 +12,24 @@ spring: port: 6379 # 选择db1 database: 3 + + +# springdoc-openapi项目配置 +springdoc: + swagger-ui: + path: /swagger-ui.html + tags-sorter: alpha + operations-sorter: alpha + api-docs: + path: /v3/api-docs + group-configs: + - group: 'default' + paths-to-match: '/**' + packages-to-scan: cn.whaifree.springdemo.controller +# knife4j的增强配置,不需要增强可以不配 +knife4j: + enable: true + setting: + language: zh_cn +server: + port: 8080