diff --git a/README.md b/README.md index 73286b7..591139e 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,136 @@ -## 拿来即用springboot基础脚手架 +# XF-Boot-Base (Spring Boot Base Demo) + +
+ +Spring Boot +Java +Nacos +MyBatis Plus + +
+
+ +[![GitHub stars](https://img.shields.io/github/stars/RemainderTime/spring-boot-base-demo?style=social&label=Stars)](https://github.com/RemainderTime/spring-boot-base-demo) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](./LICENSE) --- -### 项目介绍 -[![](https://img.shields.io/badge/-@remaindertime-FC5531?style=flat&logo=csdn&logoColor=FC5531&labelColor=424242)](https://blog.csdn.net/qq_39818325?type=blog) -[![GitHub Stars](https://img.shields.io/github/stars/RemainderTime/spring-boot-base-demo?style=social)](https://github.com/RemainderTime/spring-boot-base-demo) -![](https://img.shields.io/badge/jdk-1.8+-blue.svg) -![](https://img.shields.io/badge/springboot-3.3.3-{徽标颜色}.svg) -![](https://img.shields.io/badge/springdoc-2.6.0-{徽标颜色}.svg) -![](https://img.shields.io/badge/elasticsearch-8.16.0-005571.svg) -![](https://img.shields.io/badge/redis-3.3.3-FF4438.svg) ---- -> 这是一个基于 **Spring Boot 3.3.3** 的快速构建单体架构脚手架,旨在帮助开发者快速搭建高效、稳定的项目基础框架。项目集成了多种常用的技术组件与功能,涵盖从用户认证到数据加密、从全局异常处理到搜索引擎操作,适合个人学习与企业级单体应用开发。 +### 「 为企业级开发而生的高效脚手架 」 -#### 分支 -- master 快速上手开发spring boot 用户端单体应用 -- feature/admin-auth-spring-security 基于master分支集成spring官方鉴权框架spring security框架,可用于后台管理系统后端项目,实现RBAC模型(角色 → 用户 → 菜单 → 权限)基于角色的访问控制 -- feature/admin-auth-sa-token 基于master分支集成国产权限框架sa-token,可用于后台管理系统后端项目,实现RBAC模型(角色 → 用户 → 菜单 → 权限)基于角色的访问控制 +

+ 💎 关于项目  |  + ⚡ 核心亮点  |  + 🌿 生态全景  |  + 🚀 快速运行  |  + 📅 项目日志 +

+
-### 集成技术与功能亮点 +
-- 身份认证与授权(JWT):基于 JWT 实现用户认证与授权,确保系统安全性。 -- 数据加密(RSA):提供 RSA 非对称加密支持,保障敏感数据安全。 -- 持久层框架(MyBatis Plus):简化数据库操作,提供高效的 CRUD 支持。 -- 数据库(MySQL):采用 MySQL 作为默认数据库,易于扩展和维护。 -- 数据连接池(Hikari):高性能数据源管理,优化数据库连接效率。 -- 缓存(Redis):支持分布式缓存,提升系统响应速度与并发能力。 -- 接口文档(springdoc-openapi):自动生成标准化 API 文档,便于调试与集成。 -- 模板引擎(Thymeleaf):支持动态页面渲染,提升前后端协同效率。 -- 容器化支持(Docker):内置 Dockerfile,轻松实现环境部署与迁移。 -- 搜索引擎(Elasticsearch 8.x):集成最新版本 Elasticsearch Java 客户端,提供高效的全文检索与复杂查询功能。 -- 全局异常处理:统一管理异常,提升代码可维护性与调试效率。 -- 拦截器支持:轻松实现请求拦截与权限控制。 +## � 关于项目 -### 项目优势 -**全面适配 Spring Boot 3.x** -- 所有组件已全面升级为支持 Spring Boot 3.x 的最新版本。解决了开发者在版本升级中遇到的各种不兼容和适配问题,大大减少了升级带来的额外工作量,让项目开发更加顺畅。 +**XF-Boot-Base** 并非仅仅是一个简单的 "Hello World" 示例,而是一个经过精心打磨、具备生产级标准的 **Spring Boot 3.3** 全栈开发底座。 -**初学者友好** -- 提供清晰的代码结构与详细的配置说明,帮助初学者快速上手微服务与单体架构的开发实践。 +我们深入分析了企业单体应用到微服务架构演进过程中的痛点,构建了一套**模块化、可插拔、高扩展**的基础框架。从底层的 **JDK 17** 优化,到顶层的 API 接口规范;从**JWT 安全认证**的丝滑接入,到 **Docker 容器化**的一键部署。XF-Boot-Base 旨在消除重复造轮子的时间成本,让开发者能够专注于核心业务逻辑的实现。 -**高扩展性** -- 丰富的功能集成,涵盖了开发中常见的场景,减少重复开发工作量,同时为定制化需求预留了扩展空间。 - -**稀缺的最新技术操作示例** -- 最新版本的 Elasticsearch 8.x 集成、Java 客户端操作示例和现代化 API 设计,让开发者能够轻松掌握分布式搜索引擎的使用。 - -### 版本更新 2024-10-12 +无论你是想要快速验证想法的独立开发者,还是寻找稳健基石的架构师,这里都有你需要的最佳实践。 --- -1. springboot版本升级3.x -2. mybatis plus版本升级3.x -3. dynamic mybatis plus版本升级3.x -4. redis版本升级3.x以及配置优化 -5. 替换swagger依赖支持spring boot3.x (knife4j->springdoc-openapi) -6. 新增请求头工具类 -7. 参数校验异常捕获优化 -8. 登录拦截器注册为spring容器管理 -9. 新增本地日志配置文件 + +## ⚡ 核心亮点 + +
+ +| 🚀 **前沿技术** | 🔐 **安全无忧** | 🐳 **云原生友好** | +| :--- | :--- | :--- | +| 紧跟 **Spring Boot 3.x** 生态,
基于 **Java 17 LTS** 构建,
享受最新技术红利。 | 深度整合 **JWT** 令牌认证
& **RSA** 非对称加密,
为数据安全保驾护航。 | 内置 **Dockerfile** 脚本,
支持 **Docker Compose** 编排,
部署快人一步。 | + +| 💾 **数据增强** | 🔌 **微服务预装** | 🛠 **极致体验** | +| :--- | :--- | :--- | +| **MyBatis Plus** 强力驱动,
**Dynamic Datasource**
轻松驾驭多数据源场景。 | 原生集成 **Nacos**,
配置中心与注册中心开箱即用,
平滑过渡微服务。 | **SpringDoc (OpenAPI 3)**
自动生成精美文档,
调试开发得心应手。 | + +
--- -如果这个项目对你有帮助,请随手点个 Star ⭐ 支持一下吧!🎉✨ 你的支持是我持续优化的动力!❤️ + +## 🌿 生态全景 + +我们采用 **"核心 + 插件化"** 的分支管理策略,以 `master` 为稳定基石,通过不同分支满足多样化的业务需求。 + +| 🌳 分支标识 | 🎯 定位 | 📝 功能描述 | 🏭 最佳应用场景 | +| :--- | :--- | :--- | :--- | +| **`master`** | **核心底座** | 标准化脚手架,含 JWT/Nacos/Redis | 🚀 快速启动标准单体项目 | +| `feature/admin-auth-spring-security` | **安全堡垒** | Spring Security 官方方案 (RBAC) | 🏦 金融级、政府级后台系统 | +| `feature/admin-auth-sa-token` | **敏捷权限** | Sa-Token 轻量级权限控制 | ⚡ 中小型项目、国内快速开发 | +| `component/rocketmq-and-es` | **高并发** | RocketMQ 5.x + Elasticsearch 8.x | 📈 海量日志、搜索、高吞吐业务 | +| `feature/master-payment` | **商业变现** | 支付宝沙盒支付 (H5/App) | 💳 电商、会员订阅、SaaS 平台 | + +> **💡 提示**: 所有分支均基于 `master` 演进,可根据项目规模灵活 `git merge` 所需功能模块。 + +--- + +## 🚀 快速运行 + +### 🛠️ 环境依赖 +* **JDK**: 17 + +* **Maven**: 3.8 + +* **MySQL**: 8.0 + +* **Redis**: 5.0 + +* **Nacos**: 2.x (可选) + +### 🏃‍♂️ 启动步骤 + +> **Step 1: 获取源码** +> ```bash +> git clone https://github.com/RemainderTime/spring-boot-base-demo.git +> cd spring-boot-base-demo +> ``` + +> **Step 2: 数据准备** +> * 创建数据库 `xf_boot_base` +> * 修改 `src/main/resources/application.yml` 配好你的数据库账号密码 +> * *(注:实体类完善,表结构建议通过 JPA 或手动创建)* + +> **Step 3: 启动服务** +> +> **方式 A: 本地 Maven 运行** +> ```bash +> mvn spring-boot:run +> ``` +> +> **方式 B: Docker Compose 一键编排** +> ```bash +> cd src/main/resources/docker +> docker-compose -f boot-docker-compose.yml up -d +> ``` + +> **Step 4: 探索接口** +> 打开浏览器访问: `http://localhost:8080/doc.html` + +--- + +## 📅 项目日志 + +### v1.0.1 (2024-10-12) +* ⬆️ **内核升级**: Spring Boot 3.3.3 & MyBatis Plus 3.5.8 +* 📝 **文档重构**: 全面拥抱 SpringDoc OpenAPI,弃用旧版 Swagger +* 🛡️ **安全加固**: 优化全局异常拦截与参数校验机制 + +--- + +## 📈 关注趋势 + +[![Star History Chart](https://api.star-history.com/svg?repos=RemainderTime/spring-boot-base-demo&type=Date)](https://star-history.com/#RemainderTime/spring-boot-base-demo&Date) + +
+
+ +**喜欢这个项目?请点个 Star ⭐ 支持作者持续更新!** + +
+ + + diff --git a/pom.xml b/pom.xml index 41e0c2b..3c08628 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ xf-boot-base 拿来即用springboot单体脚手架项目 - 1.8 + 17 8.1.0 3.5.8 4.3.1 @@ -25,7 +25,6 @@ 5.3 2023.0.1.0 2023.0.1 - 8.16.0 4.0.1 1.44.0 @@ -48,6 +47,10 @@ org.springframework.boot spring-boot-starter-actuator + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-starter-data-redis @@ -123,18 +126,6 @@ ${sverlet.version} provided - - - co.elastic.clients - elasticsearch-java - ${elasticsearch.version} - - - - cn.dev33 - sa-token-spring-boot3-starter - ${sa-token.version} - @@ -154,10 +145,17 @@ org.apache.maven.plugins maven-compiler-plugin + 3.11.0 - 14 - 14 + 17 + + + org.codehaus.plexus + plexus-compiler-javac + 2.15.0 + + diff --git a/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionHandler.java b/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..0bcc700 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,80 @@ +package cn.xf.basedemo.common.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * @Description: 全局异常处理类 (使用 @RestControllerAdvice 替代旧版 + * HandlerExceptionResolver) + * @Author: xiongfeng + * @Date: 2025/1/9 + * @Version: 2.0 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 处理登录/认证异常 + */ + @ExceptionHandler(LoginException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public GenericResponse handleLoginException(LoginException e, HttpServletRequest request) { + log.warn("认证失败 [URL:{}]: code={}, message={}", request.getRequestURI(), e.getCode(), e.getMessage()); + return new GenericResponse<>(e.getCode(), null, e.getMessage()); + } + + /** + * 处理业务逻辑异常 + */ + @ExceptionHandler(BusinessException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) // 或者使用 HttpStatus.OK,根据前端约定 + public GenericResponse handleBusinessException(BusinessException e, HttpServletRequest request) { + log.warn("业务异常 [URL:{}]: code={}, message={}", request.getRequestURI(), e.getCode(), e.getMessage()); + return new GenericResponse<>(e.getCode(), null, e.getMessage()); + } + + /** + * 处理参数校验异常 (处理 @Valid / @Validated 触发的异常) + */ + @ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class }) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public GenericResponse handleValidationException(Exception e, HttpServletRequest request) { + BindingResult bindingResult = null; + if (e instanceof MethodArgumentNotValidException) { + bindingResult = ((MethodArgumentNotValidException) e).getBindingResult(); + } else if (e instanceof BindException) { + bindingResult = ((BindException) e).getBindingResult(); + } + + String errorMsg = "参数校验失败"; + if (bindingResult != null && bindingResult.hasErrors()) { + FieldError fieldError = bindingResult.getFieldError(); + if (fieldError != null) { + errorMsg = fieldError.getDefaultMessage(); + } + } + log.warn("参数校验失败 [URL:{}]: {}", request.getRequestURI(), errorMsg); + return new GenericResponse<>(ResponseCode.USER_INPUT_ERROR.getCode(), null, errorMsg); + } + + /** + * 处理所有未知的系统异常 (兜底) + */ + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public GenericResponse handleSystemException(Exception e, HttpServletRequest request) { + // 生产级关键点:必须记录异常堆栈,否则无法排查 BUG + log.error("系统发生未知异常 [URL:{}]", request.getRequestURI(), e); + return new GenericResponse<>(500, null, "系统内部繁忙,请稍后再试"); + } +} diff --git a/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionResolver.java b/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionResolver.java deleted file mode 100644 index 09ad30f..0000000 --- a/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionResolver.java +++ /dev/null @@ -1,53 +0,0 @@ -package cn.xf.basedemo.common.exception; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.servlet.ModelAndView; - -import java.io.IOException; -import java.io.PrintWriter; - -/** - * @Description: 全局异常捕获类(所有异常(包括拦截器、Controller、视图))HandlerExceptionResolver更底层 - * @ClassName: GlobalExceptionResolver - * @Author: xiongfeng - * @Date: 2025/8/23 23:30 - * @Version: 1.0 - */ -@Slf4j -@Component -public class GlobalExceptionResolver implements HandlerExceptionResolver { - @Override - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - response.setContentType("application/json;charset=UTF-8"); - - try (PrintWriter writer = response.getWriter()) { - if (ex instanceof LoginException) { - response.setStatus(HttpStatus.FORBIDDEN.value()); - LoginException le = (LoginException) ex; - writer.write(new ObjectMapper().writeValueAsString( - new GenericResponse<>(le.getCode(), null, le.getMessage()) - )); - } else if (ex instanceof BusinessException) { - BusinessException be = (BusinessException) ex; - response.setStatus(HttpStatus.BAD_REQUEST.value()); - writer.write(new ObjectMapper().writeValueAsString( - new GenericResponse<>(be.getCode(), null, be.getMessage()) - )); - } else { - response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - writer.write(new ObjectMapper().writeValueAsString( - new GenericResponse<>(500, null, "系统异常") - )); - } - } catch (IOException ioEx) { - log.error("写响应失败", ioEx); - } - return new ModelAndView(); - } -} diff --git a/src/main/java/cn/xf/basedemo/common/model/EsBaseModel.java b/src/main/java/cn/xf/basedemo/common/model/EsBaseModel.java deleted file mode 100644 index dd32333..0000000 --- a/src/main/java/cn/xf/basedemo/common/model/EsBaseModel.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.xf.basedemo.common.model; - -import lombok.Data; - -/** - * packageName cn.xf.basedemo.common.model - * @author remaindertime - * @className EsModel - * @date 2024/12/10 - * @description es基础模型 - */ -@Data -public class EsBaseModel { - - public EsBaseModel(String indexName, String documentId, T documentModel, Class clazz) { - this.indexName = indexName; - this.documentId = documentId; - this.documentModel = documentModel; - this.clazz = clazz; - } - - /** - * 索引名称 - */ - private String indexName; - - /** - * 文档id - */ - private String documentId; - - /** - * 映射对象 - */ - private T documentModel; - - /** - * 映射对象类对象 - */ - private Class clazz; - -} diff --git a/src/main/java/cn/xf/basedemo/common/model/EsSearchModel.java b/src/main/java/cn/xf/basedemo/common/model/EsSearchModel.java deleted file mode 100644 index 13bc030..0000000 --- a/src/main/java/cn/xf/basedemo/common/model/EsSearchModel.java +++ /dev/null @@ -1,81 +0,0 @@ -package cn.xf.basedemo.common.model; - -import lombok.Data; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * packageName cn.xf.basedemo.common.model - * @author remaindertime - * @className EsSearchModel - * @date 2024/12/11 - * @description es 搜索模型 - */ -@Data -public class EsSearchModel { - - public EsSearchModel() { - // 使用 LinkedHashMap 保持插入顺序 - this.sort = new LinkedHashMap<>(); - } - - /** - * 索引名称 - */ - private String indexName; - - /** - * 文档类型 - */ - private Class clazz; - - /** - * 页数 - */ - private Integer pageNum; - - /** - * 每页数量 - */ - private Integer pageSize; - /** - * 精准查询字段 - */ - private Map termQuery; - - /** - * 模糊查询字段(一般是text类型) - */ - private Map matchQuery; - - /** - * 排序字段规则 ({"age":"desc"}) - */ - private Map sort; - - /** - * 分组去重字段(支持的字段类型:keyword、numeric、date 和 boolean ) - */ - private String repeatField;; - - /** - * 分组嵌套查询别名 - */ - private String innerAlias; - - /** - * 分组嵌套查询数量 - */ - private Integer innerSize; - - /** - * 指定需要返回的字段 - */ - private List includes; - /** - * 指定需要排除的字段 - */ - private List excludes; - -} \ No newline at end of file diff --git a/src/main/java/cn/xf/basedemo/common/utils/EsUtil.java b/src/main/java/cn/xf/basedemo/common/utils/EsUtil.java deleted file mode 100644 index 3b371e1..0000000 --- a/src/main/java/cn/xf/basedemo/common/utils/EsUtil.java +++ /dev/null @@ -1,499 +0,0 @@ -package cn.xf.basedemo.common.utils; - -import cn.xf.basedemo.common.model.EsBaseModel; -import cn.xf.basedemo.common.model.EsSearchModel; -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch._types.*; -import co.elastic.clients.elasticsearch._types.query_dsl.*; -import co.elastic.clients.elasticsearch.core.*; -import co.elastic.clients.elasticsearch.core.search.*; -import co.elastic.clients.elasticsearch.indices.CreateIndexResponse; -import co.elastic.clients.elasticsearch.indices.ExistsRequest; -import co.elastic.clients.json.JsonData; -import co.elastic.clients.transport.endpoints.BooleanResponse; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; -import java.util.*; -import java.util.stream.Collectors; - -/** - * packageName cn.xf.basedemo.common.utils - * @author remaindertime - * @className EsUtil - * @date 2024/12/10 - * @description elasticsearch工具类 - */ -@Slf4j -@Component -public class EsUtil { - - public static ElasticsearchClient esClient; - - { - esClient = (ElasticsearchClient) ApplicationContextUtils.getBean("elasticsearchClient"); - } - - /** - * 判断索引是否存在 - * @param indexName - * @return - */ - public static boolean existIndex(String indexName) { - try { - // 创建 ExistsRequest 请求 - ExistsRequest request = new ExistsRequest.Builder() - .index(indexName) - .build(); - // 发送请求并获取响应 - BooleanResponse response = esClient.indices().exists(request); - // 返回索引是否存在 - return response.value(); - } catch (Exception e) { - // 处理异常 - e.printStackTrace(); - return false; - } - } - - /** - * 删除索引 - * - * @param indexName - */ - @SneakyThrows - public static void delIndex(String indexName) { - if (existIndex(indexName)) { - return; - } - esClient.indices().delete(d -> d.index(indexName)); - } - - /** - * 创建索引 - * - * @param indexName - * @return - */ - public static void createIndex(String indexName) { - if (existIndex(indexName)) { - throw new RuntimeException("索引已经存在"); - } - try { - CreateIndexResponse createIndexResponse = esClient.indices().create(c -> c.index(indexName)); - // 处理响应 - if (createIndexResponse.acknowledged()) { - log.info(" indexed create successfully."); - } else { - log.info("Failed to create index."); - } - } catch (Exception e) { - // 捕获异常并打印详细错误信息 - e.printStackTrace(); - throw new RuntimeException("创建索引失败,索引名:" + indexName + ",错误信息:" + e.getMessage(), e); - } - } - - /** - * 新增文档 - * @param esBaseModel - * @return - */ - public static boolean addDocument(EsBaseModel esBaseModel) { - try { - ObjectMapper objectMapper = new ObjectMapper(); - String jsonString = objectMapper.writeValueAsString(esBaseModel.getDocumentModel()); - log.info("es新增文档,文档内容:{}", jsonString); - // 创建 IndexRequest 实例 - IndexRequest request = new IndexRequest.Builder() - .index(esBaseModel.getIndexName()) - .id(esBaseModel.getDocumentId()) //指定文档id,不指定会自动生成 - .document(esBaseModel.getDocumentModel()) - .opType(OpType.Create) // 只会在文档 ID 不存在时创建文档 - .build(); - - IndexResponse response = esClient.index(request); - if ("created".equals(response.result())) { - log.info("Document created: " + response.id()); - return true; - } else { - log.info("Document already exists or failed to create."); - return false; - } - } catch (Exception e) { - log.error("es新增文档失败", e); - e.printStackTrace(); - } - return false; - } - - /** - * 更新文档 - * @param esBaseModel - * @return - */ - public boolean updateDocument(EsBaseModel esBaseModel) { - try { - UpdateRequest updateRequest = new UpdateRequest.Builder<>() - .index(esBaseModel.getIndexName()) - .id(esBaseModel.getDocumentId()) - .doc(esBaseModel.getDocumentModel()).build(); - UpdateResponse updateResponse = esClient.update(updateRequest, esBaseModel.getClazz()); - log.info("Document updated: " + updateResponse.id()); - return true; - } catch (Exception e) { - e.printStackTrace(); - } - return false; - } - - /** - * 更新文档指定字段(script 脚本) - * @param esBaseModel - * @param script 脚本内容 - * @param params 传递参数内容 - */ - public void updateDocumentWithScript(EsBaseModel esBaseModel, String script, Map params) { - try { - UpdateRequest updateRequest = new UpdateRequest.Builder<>() - .index(esBaseModel.getIndexName()) - .id(esBaseModel.getDocumentId()) - .script(s -> - s.source(script)// 脚本内容:.source("ctx._source.age += params.increment") - .params(params)) // 传递参数内容:.params("increment",sonData.of(5)) - .build(); - UpdateResponse updateResponse = esClient.update(updateRequest, esBaseModel.getClazz()); - log.info("Document updated: " + updateResponse.id()); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * 根据id查询文档 - * @param esBaseModel - * @return - */ - public static T getDocumentById(EsBaseModel esBaseModel) { - try { - GetRequest getRequest = new GetRequest.Builder() - .index(esBaseModel.getIndexName()) - .id(esBaseModel.getDocumentId()) - .build(); - GetResponse getResponse = esClient.get(getRequest, esBaseModel.getClazz()); - if (getResponse.found()) { - return getResponse.source(); - } - } catch (Exception e) { - log.error("es列表查询失败", e); - } - return null; - } - - /** - * 查询文档列表 - * @param searchModel - * @return - */ - public static List getDocumentList(EsSearchModel searchModel) { - List eslist = new ArrayList<>(); - try { - SearchResponse search = esClient.search(buildSearchRequest(searchModel), searchModel.getClazz()); - if (Objects.isNull(search)) { - return eslist; - } - HitsMetadata hits = search.hits(); - if (Objects.isNull(hits)) { - return eslist; - } - List> sourceHitList = hits.hits(); - if (CollectionUtils.isEmpty(sourceHitList)) { - return eslist; - } - sourceHitList.forEach(item -> { - // 处理每个命中 - eslist.add(item.source()); - }); - return eslist; - } catch (Exception e) { - log.error("es列表查询失败", e); - } - return eslist; - } - - /** - * 查询文档数量 - * @param searchModel - * @return - */ - public static long getDocumentCount(EsSearchModel searchModel) { - try { - CountRequest.Builder countRequest = new CountRequest.Builder(); - countRequest.index(searchModel.getIndexName()); - countRequest.query(createBoolQuery(searchModel.getTermQuery(), searchModel.getMatchQuery())); - CountResponse count = esClient.count(countRequest.build()); - if (Objects.isNull(count)) { - log.info("es列表数量查询异常{}", searchModel); - return 0; - } - return count.count(); - } catch (Exception e) { - log.error("es列表数量查询失败", e); - } - return 0; - } - - /** - * 根据id删除文档 - * @param esBaseModel - * @return - */ - public static Boolean deleteDocumentById(EsBaseModel esBaseModel) { - try { - DeleteRequest deleteRequest = new DeleteRequest.Builder() - .index(esBaseModel.getDocumentId()) - .id(esBaseModel.getDocumentId()) - .build(); - DeleteResponse deleteResponse = esClient.delete(deleteRequest); - if ("deleted".equals(deleteResponse.result())) { - log.info("Document deleted: " + deleteResponse.id()); - return true; - } else { - log.info("Document delete failed: " + deleteResponse.id()); - return false; - } - } catch (Exception e) { - log.error("es列表删除失败", e); - } - return false; - } - - /** - * 根据条件删除文档 - * @param searchModel - * @return 删除数量 - */ - public static long deleteDocumentByQuery(EsSearchModel searchModel) { - try { - DeleteByQueryRequest.Builder deleteRequest = new DeleteByQueryRequest.Builder(); - deleteRequest.index(searchModel.getIndexName()); - deleteRequest.query(createBoolQuery(searchModel.getTermQuery(), searchModel.getMatchQuery())); - deleteRequest.refresh(true); //设置删除操作后是否立即刷新索引,使删除结果立即可见 - deleteRequest.timeout(new Time.Builder().time("2s").build()); //设置删除操作的超时时间 - deleteRequest.conflicts(Conflicts.Proceed); //Conflicts.Proceed:在版本冲突时继续删除操作;Conflicts.Abort:在版本冲突时中止删除操作 - DeleteByQueryResponse dResponse = esClient.deleteByQuery(deleteRequest.build()); - if (Objects.nonNull(dResponse)) { - log.info("es条件删除成功,删除数量:{}", dResponse.deleted()); - return dResponse.deleted(); - } - } catch (Exception e) { - log.error("es条件删除数据失败", e); - } - return 0; - } - - /** - * 构建搜索请求对象 - * @param searchModel - * @return - */ - private static SearchRequest buildSearchRequest(EsSearchModel searchModel) { - //定义查询对象 - SearchRequest.Builder searchRequest = new SearchRequest.Builder(); - //设置索引名称 - searchRequest.index(searchModel.getIndexName()); - //分组去重 - if (StringUtils.isNotBlank(searchModel.getRepeatField())) { - searchRequest.collapse(buildCollapse(searchModel)); - } - //设置查询条件 - searchRequest.query(createBoolQuery(searchModel.getTermQuery(), searchModel.getMatchQuery())); - //设置排序规则 - if (searchModel.getSort() != null) { - searchRequest.sort(buildSort(searchModel.getSort())); - } - //设置分页参数 - if (searchModel.getPageSize() != null && searchModel.getPageSize() != null) { - searchRequest.from(searchModel.getPageSize() * (searchModel.getPageNum() - 1)); - searchRequest.size(searchModel.getPageSize()); - } - //设置查询字段/排查字段 - SourceConfig sourceConfig = buildSourceConfig(searchModel.getIncludes(), searchModel.getExcludes()); - if (Objects.nonNull(sourceConfig)) { - searchRequest.source(sourceConfig); - } - return searchRequest.build(); - } - - /** - * 构建查询条件 - * @param termQuery - * @param matchQuery - * @return - */ - private static Query createBoolQuery(Map termQuery, Map matchQuery) { - BoolQuery.Builder cQuery = new BoolQuery.Builder(); - // TermQuery 精准匹配 - if (termQuery != null) { - for (Map.Entry entry : termQuery.entrySet()) { - if (Objects.isNull(entry.getValue())) { - continue; - } - String key = entry.getKey(); - Object value = entry.getValue(); - if (value.getClass().isArray()) { //数组查询,使用 TermsQuery - Object[] values = (Object[]) entry.getValue(); - List objs = Arrays.stream(values) - .map(v -> FieldValue.of(v)) // 将每个对象转换为 FieldValue - .collect(Collectors.toList()); - cQuery.must(new TermsQuery.Builder() - .field(key) - .terms(t -> t.value(objs)) - .build() - ._toQuery()); - } else if (value.toString().contains(" ")) { // 短语查询,使用 MatchPhraseQuery (要严格按照单词顺序字符串中有空格,短信需匹配) - cQuery.must(new MatchPhraseQuery.Builder() - .field(key) - .query(value.toString()) - .build() - ._toQuery()); - } else { // 其他情况,使用 TermQuery 精准匹配 - cQuery.must(new TermQuery.Builder() - .field(key) - .value(value.toString()) - .build() - ._toQuery()); - } - } - } - // MatchQuery 模糊匹配全文检索分词查询 - if (matchQuery != null) { - for (Map.Entry entry : matchQuery.entrySet()) { - if (Objects.isNull(entry.getValue())) { - continue; - } - cQuery.must(new MatchQuery.Builder() - .field(entry.getKey()) - .query(entry.getValue().toString()) - .build() - ._toQuery()); - } - } - return cQuery.build()._toQuery(); - } - - /** - * 构建时间区间查询 - * @param startTime 开始时间 - * @param endTime 结束时间 - * @param fieldName 时间字段 - * @return - */ - public static Query createTimeQuery(String startTime, String endTime, String fieldName) { - DateRangeQuery dataQuery = new DateRangeQuery.Builder() - .field(fieldName) - .build(); - // 时间区间查询 - dataQuery.of(o -> o.gte(startTime)); - dataQuery.of(o -> o.lte(endTime)); - return dataQuery._toRangeQuery()._toQuery(); - } - - - /** - * 设置查询字段/排查字段 - * @param includes 需要字段 - * @param excludes 排除字段 - * @return - */ - private static SourceConfig buildSourceConfig(List includes, List excludes) { - boolean isIncludes = CollectionUtils.isEmpty(includes); - boolean isExcludes = CollectionUtils.isEmpty(excludes); - //设置查询字段/排查字段 - if (isIncludes || isExcludes) { - SourceFilter.Builder sourceFilter = new SourceFilter.Builder(); - if (isIncludes) - sourceFilter.includes(includes); - if (isExcludes) - sourceFilter.excludes(excludes); - return new SourceConfig.Builder().filter(sourceFilter.build()).build(); - } - return null; - } - - - /** - * 构建分组去重 - * @param searchModel - * @return - */ - private static FieldCollapse buildCollapse(EsSearchModel searchModel) { - FieldCollapse.Builder fieldCollapse = new FieldCollapse.Builder(); - //设置分组字段 - fieldCollapse.field(searchModel.getRepeatField()); - //设置嵌套配置 - if (StringUtils.isNotBlank(searchModel.getInnerAlias())) { - InnerHits.Builder innerHits = new InnerHits.Builder(); - //设置别名 - innerHits.name(searchModel.getInnerAlias()); - //设置查询数量 - if (searchModel.getInnerSize() != null) { - innerHits.size(searchModel.getInnerSize()); - } - fieldCollapse.innerHits(InnerHits.of(i -> i.name(searchModel.getInnerAlias()).size(10))); - } - return fieldCollapse.build(); - } - - /** - * 构建排序规则 - * @param sortMap - * @return - */ - private static List buildSort(Map sortMap) { - if (sortMap == null) { - return null; - } - List sortList = new ArrayList<>(); - for (Map.Entry sort : sortMap.entrySet()) { - sortList.add(new SortOptions.Builder().field(f -> f.field(sort.getKey()).order(SortOrder.valueOf(sort.getValue()))).build()); - } - return sortList; - } - - /** - * 案例:组合多条件查询(关于 must、mustNot、should 条件的使用) - */ - public Query combinationQueryTest() { - //query.must():and 文档必须满足该条件,如果不满足,文档将不匹配。 and - //query.should():or 文档可以不满足该条件,但满足该条件时会得分更高;即使不满足,文档也会出现在查询结果中,只是查询结果靠后。 - - //场景1:文档必须符合所有 must 条件和 mustNot 条件,同时至少满足一个 should 条件。如果 should 条件都不满足,文档将被排除不查询出来。 - BoolQuery.Builder query = new BoolQuery.Builder(); - //数字范围查询 - NumberRangeQuery.Builder numberQuery = new NumberRangeQuery.Builder(); - numberQuery.field("age").lte(30.0).build(); - // 构建查询条件 - query.must(o -> o.term(t -> t.field("status").value("active"))) // 必须满足的条件 - .mustNot(o -> o.term(t -> t.field("country").value("China"))) // 不能满足的条件 - .filter(f -> f.bool(bo -> bo - .should(so -> so.range(r -> r.number(numberQuery.build()))) // 至少满足一个 should 条件 - .should(so -> so.term(t -> t.field("gender").value("male"))) // 至少满足一个 should 条件 - .minimumShouldMatch("1") // 至少满足一个 should 条件 也可设置百分比 “50%” - )); - //场景2:文档必须符合所有 must 条件和 mustNot 条件,同时至少满足一个 should 条件。如果 should 条件都不满足,不用做额外的过滤(按照should原生特性处理)。 - query.must(o -> o.bool(bo -> bo - .should(so -> so.range(r -> r.number(numberQuery.build()))) // 至少满足一个 should 条件 - .should(so -> so.term(t -> t.field("gender").value("male"))) // 至少满足一个 should 条件 - .minimumShouldMatch("1") // 至少满足一个 should 条件 - )) - .must(o -> o.term(t -> t.field("status").value("active"))) // 必须满足的条件 - .mustNot(o -> o.term(t -> t.field("country").value("China"))); // 不能满足的条件 - - return query.build()._toQuery(); - } - -} diff --git a/src/main/java/cn/xf/basedemo/config/EsConfig.java b/src/main/java/cn/xf/basedemo/config/EsConfig.java deleted file mode 100644 index 70e2c1c..0000000 --- a/src/main/java/cn/xf/basedemo/config/EsConfig.java +++ /dev/null @@ -1,79 +0,0 @@ -package cn.xf.basedemo.config; - -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.json.jackson.JacksonJsonpMapper; -import co.elastic.clients.transport.ElasticsearchTransport; -import co.elastic.clients.transport.rest_client.RestClientTransport; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestClientBuilder; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; - -/** - * packageName cn.xf.basedemo.config - * @author remaindertime - * @className ElasticsearchConfig - * @date 2024/12/9 - * @description es工具类 - */ -@Component -public class EsConfig { - - @Value("${elasticsearch.host}") - private String elasticsearchHost; - @Value("${elasticsearch.port}") - private int elasticsearchPort; - @Value("${elasticsearch.username}") - private String username; - @Value("${elasticsearch.password}") - private String password; - - /** - -最大连接数 (maxConnTotal):设置总的最大连接数,取决于业务的并发量。500-2000 之间较为合理。 - -每个节点的最大连接数 (maxConnPerRoute):控制每个节点的最大连接数,建议 50-100 之间。 - -IO 线程数 (setIoThreadCount):根据 CPU 核心数设置,通常为 2-4 倍 CPU 核心数。 - -连接超时、套接字超时、获取连接超时:一般设置为 10-30 秒,复杂查询或大数据量操作可适当增加到 20-60 秒。 - -失败监听器 (setFailureListener):自定义重试和故障处理逻辑,确保高可用性。 - */ - @Bean - public ElasticsearchClient elasticsearchClient() { - - // 创建凭证提供者 - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials( - AuthScope.ANY, - new UsernamePasswordCredentials(username, password) - ); - - // 自定义 RestClientBuilder 配置 - RestClientBuilder restClientBuilder = RestClient.builder( - new HttpHost(elasticsearchHost, elasticsearchPort, "http") - ).setHttpClientConfigCallback(httpClientBuilder -> - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider) // 配置认证信息 - ); - // 配置连接超时、套接字超时、获取连接超时 - restClientBuilder.setRequestConfigCallback(builder -> - builder.setConnectTimeout(20000) - .setSocketTimeout(20000) - .setConnectionRequestTimeout(20000) - ); - // 创建 RestClientTransport 和 ElasticsearchClient - RestClient restClient = restClientBuilder.build(); - ElasticsearchTransport transport = new RestClientTransport( - restClient, - new JacksonJsonpMapper() // 使用 Jackson 进行 JSON 处理 - ); - - return new ElasticsearchClient(transport); - } - - /** - window系统本地启动 es8.x 重置密码命令:.\elasticsearch-reset-password -u elastic - */ -} diff --git a/src/main/java/cn/xf/basedemo/config/SwaggerGroupApi.java b/src/main/java/cn/xf/basedemo/config/SwaggerGroupApi.java index 7648d59..c2eabcd 100644 --- a/src/main/java/cn/xf/basedemo/config/SwaggerGroupApi.java +++ b/src/main/java/cn/xf/basedemo/config/SwaggerGroupApi.java @@ -1,10 +1,13 @@ package cn.xf.basedemo.config; +import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.ExternalDocumentation; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @@ -21,6 +24,8 @@ public class SwaggerGroupApi { @Bean public OpenAPI springShopOpenAPI() { + // 定义全局安全方案名称 + String securitySchemeName = "Authorization"; return new OpenAPI() .info(new Info().title("Spring boot脚手架 API") .description("开箱即用的Spring boot脚手架 API") @@ -29,7 +34,16 @@ public class SwaggerGroupApi { .license(new License().name("Apache 2.0").url("http://springdoc.org"))) .externalDocs(new ExternalDocumentation() .description("Spring boot脚手架 Wiki Documentation") - .url("https://springshop.wiki.github.org/docs")); + .url("https://springshop.wiki.github.org/docs")) // 添加安全组件 + .components(new Components() + .addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + )) + // 将安全方案应用到全局 + .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)); } @Bean diff --git a/src/main/java/cn/xf/basedemo/controller/business/UserController.java b/src/main/java/cn/xf/basedemo/controller/business/UserController.java index 2c727c9..271c008 100644 --- a/src/main/java/cn/xf/basedemo/controller/business/UserController.java +++ b/src/main/java/cn/xf/basedemo/controller/business/UserController.java @@ -11,6 +11,7 @@ import cn.xf.basedemo.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** @@ -30,7 +31,7 @@ public class UserController { @Operation(summary = "用户登录", description = "用户登录") @PostMapping("/login") - public RetObj login(@RequestBody LoginInfoRes res){ + public RetObj login(@RequestBody @Validated LoginInfoRes res) { return userService.login(res); } @@ -43,23 +44,4 @@ public class UserController { return RetObj.success(loginUser); } - @Operation(summary = "es同步用户信息", description = "用户信息") - @SaCheckRole("admin") //角色校验 - @GetMapping("/syncEs") - public RetObj syncEs(Long userId){ - return userService.syncEs(userId); - } - - @Operation(summary = "es查询用户信息", description = "用户信息") - @GetMapping("/getEsId") - public RetObj getEsId(Long userId){ - return userService.getEsId(userId); - } - - @Operation(summary = "获取用户权限数据", description = "用户信息") - @GetMapping("/getPermission") - public RetObj getPermission(){ - return RetObj.success(StpUtil.getPermissionList()); - } - } diff --git a/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java b/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java index dc74858..98daf8b 100644 --- a/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java +++ b/src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java @@ -49,6 +49,12 @@ public class TokenInterceptor implements HandlerInterceptor { token = request.getParameter("token"); if (StringUtils.isEmpty(token)) { throw new LoginException("请先登录"); + }else { + //验证token + if (!token.startsWith("Bearer ")) { + throw new LoginException(ResponseCode.USER_INPUT_ERROR); + } + token = token.substring(7); } String value = (String) redisTemplate.opsForValue().get("token:" + token); if (StringUtils.isEmpty(value)) { diff --git a/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java b/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java index 7c7a5d0..d719e92 100644 --- a/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java +++ b/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java @@ -1,6 +1,7 @@ package cn.xf.basedemo.model.res; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** @@ -16,6 +17,7 @@ public class LoginInfoRes { /** * 登录密文 */ + @NotBlank(message = "登录密文不能为空") @Schema(name = "登录密文") private String encryptedData; } diff --git a/src/main/java/cn/xf/basedemo/service/UserService.java b/src/main/java/cn/xf/basedemo/service/UserService.java index 7f8dc4f..d303267 100644 --- a/src/main/java/cn/xf/basedemo/service/UserService.java +++ b/src/main/java/cn/xf/basedemo/service/UserService.java @@ -14,7 +14,4 @@ public interface UserService { RetObj login(LoginInfoRes res); - RetObj syncEs(Long userId); - - RetObj getEsId(Long userId); } diff --git a/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java b/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java index d49103a..65412be 100644 --- a/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java +++ b/src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java @@ -5,10 +5,8 @@ import cn.xf.basedemo.common.model.EsBaseModel; import cn.xf.basedemo.common.model.LoginInfo; import cn.xf.basedemo.common.model.LoginUser; import cn.xf.basedemo.common.model.RetObj; -import cn.xf.basedemo.common.utils.EsUtil; import cn.xf.basedemo.common.utils.JwtTokenUtils; import cn.xf.basedemo.common.utils.RSAUtils; -import cn.xf.basedemo.common.utils.StringUtil; import cn.xf.basedemo.config.GlobalConfig; import cn.xf.basedemo.mappers.UserMapper; import cn.xf.basedemo.model.domain.User; @@ -96,26 +94,4 @@ public class UserServiceImpl implements UserService { return RetObj.success(loginUser); } - @Override - public RetObj syncEs(Long userId) { - User user = userMapper.selectById(userId); - if (Objects.isNull(user)) { - return RetObj.error("用户不存在"); - } - String index = StringUtil.camelToKebabCase(user.getClass().getSimpleName()); - if (!EsUtil.existIndex(index)) { - EsUtil.createIndex(index); - } - EsUtil.addDocument(new EsBaseModel(index, String.valueOf(user.getId()), user, user.getClass())); - return RetObj.success(); - } - - @Override - public RetObj getEsId(Long userId) { - Object user = EsUtil.getDocumentById(new EsBaseModel("user", String.valueOf(userId), null, User.class)); - if (Objects.nonNull(user)) { - return RetObj.success(user); - } - return RetObj.error("es中不存在该用户"); - } } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 01228ee..1964d39 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,128 +1,93 @@ spring: + servlet: + multipart: + max-file-size: 20MB + max-request-size: 20MB + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + serialization: + WRITE_DATES_AS_TIMESTAMPS: false + FAIL_ON_EMPTY_BEANS: false datasource: dynamic: primary: master - strict: true #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源. + strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源. hikari: - minimum-idle: 4 maximum-pool-size: 4 + minimum-idle: 4 + leak-detection-threshold: 0 connection-init-sql: SELECT 1 connection-test-query: SELECT 1 datasource: master: #${SERVER_ADDRESS} - url: jdbc:mysql://9.9.9.9:3307/xf-boot-base?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai + url: jdbc:mysql://localhost:3307/xf-boot-base?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai username: password: driver-class-name: com.mysql.cj.jdbc.Driver slave: - url: jdbc:mysql://9.9.9.9:3307/xf-boot-base?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai + url: jdbc:mysql://localhost:3307/xf-boot-base?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai username: password: driver-class-name: com.mysql.cj.jdbc.Driver + data: redis: - port: 6379 #Redis服务器连接的端口 - host: 9.9.9.9 # Redis服务器的地址 - password: # Redis服务器连接密码(默认为空) - timeout: 5000 # 连接超时时间(毫秒) + port: 6379 + host: localhost + password: + timeout: 5000 lettuce: #参考博客 https://blog.csdn.net/weixin_43944305/article/details/124322595 pool: - maxActive: 5000 #最大连接数 - maxIdle: 30 #连接池最大空闲连接数. - minIdle: 5 #连接池最小空闲连接数. - max-wait: 2000 #从连接池中获取连接时的最大等待时间 - time-between-eviction-runs: 60s #空闲对象逐出器线程的运行间隔时间.空闲连接线程释放周期时间. + maxActive: 5000 + maxIdle: 30 + minIdle: 5 + max-wait: 2000 + time-between-eviction-runs: 60s cluster: refresh: - adaptive: true #拓扑动态感应即客户端能够根据 redis cluster 集群的变化,动态改变客户端的节点情况,完成故障转移。 - period: 60s #刷新redis集群状态周期时间 + adaptive: true + period: 60s global: rsaPublicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC_F5UQC1QWsu3QsESQBz9M-GDA9Atm0qVSvwIsy568lyRLi-nq3VvvnmgrlL4yTbngFzyfb2Dn35cNCHsBvIaGuCY3_PpzPqMzVpxr2QlEkhEX9atnJQ1rWexS8QeZtPjpiIwoQrChTzXjD_sYUkDrqSykFplyivf0NSO2WqCBdwIDAQAB rsaPrivateKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL8XlRALVBay7dCwRJAHP0z4YMD0C2bSpVK_AizLnryXJEuL6erdW--eaCuUvjJNueAXPJ9vYOfflw0IewG8hoa4Jjf8-nM-ozNWnGvZCUSSERf1q2clDWtZ7FLxB5m0-OmIjChCsKFPNeMP-xhSQOupLKQWmXKK9_Q1I7ZaoIF3AgMBAAECgYBxTUA61Ry0oL7U_86HP2TO9G4ZuhmQi9EucMaPXOPvmgYRLRIzCbDbMKc_P-BN3zwYnG57cgSZNz9OoPqeGvP_oVTnkoEpVkCSV-JP2p_DK09LdbDqszJXMrxAkPmWGUw8IRMcTJT1xJJcgzFE6T0CmTo-Vk47AnmqfJD4U6o74QJBAPRjVUJKZnrMSnSqKPDL2ThgTo8h7-KFxl_Z-g724lTOFiCmBpi6nCWAcuacFRrrYqxF-r9c4zdIyR7AvLROql8CQQDIK_kRF52dVtwShciZhyeUBLoi0nWV9F8mMGt60NTEER9zPEgPsv2aVn8h97KMWOwmd2Da4EPm25QxOuaKQC_pAkBczcfXp5co9KElkmR_pHl1jiTm97U3qSM-zPDHc_tYxvXiKgoBP4QCPbfkWMsu8MoEr4Jb3vMt0EcHlZtTQTgzAkAfmNla-lhV4sUgY1_T5EK6GbjsED6hag6u74u3ukkrnexR-10ApWdkumydBwV3I_464DM4uZfeVCDjWIHVpuYpAkEA6QLPztGD4V8Q1PqTEeSF3i68CKPM8vO1_mCH2JD7qsqDQcIKkczj5rTg7hlOKwB9V6gSw4CbnOF6moTooRD-cQ -redis: - datasource: - token: - database: 1 - host: 122.112.153.128 - port: 6379 - password: 'redis' - lettuce: - pool: - max-active: 8 - max-wait: -1ms - max-idle: 8 - min-idle: 0 - timeout: 3000ms +springdoc: + api-docs: + path: /v3/api-docs # 自定义 API 文档路径 + swagger-ui: + path: /swagger-ui.html # 自定义 Swagger UI 路径 + enabled: true + info: + title: 文撩 API 文档 + description: 这是文撩平台的 API 文档 + version: v1.0 -oss: - name: alioss - endpoint: ll-oss-pre.lianlianlvyou.com - accessKey: - secretKey: - bucketName: - args: - expireTime: 3600 #过期时间 - contentLengthRange: 2000 #大小限制 -# redis分布式锁 -redisson: - enabled: true - address: 'redis://192.168.10.113:6379' - password: '123456' - database: 5 - connectionPoolSize: 4 - connectionMinimumIdleSize: 4 - -# 阿里云rocketmq -aliyun: - rocketmq: - config: - AccessKey: 1 - SecretKey: 1 - NAMESRV_ADDR: 1 - GROUP_ID: 1 - producer: - enabled: true - -rabbitmq: - configs: - order: #实例名称 - host: 192.168.10.111 - port: 5672 - virtualHost: ll-dev - username: zhangziheng - password: zhangziheng - producer: - enabled: true - exchange: order_status - routingKey: ORDER_COMPLETE - confirmCallback: orderMqConfirmCallback - commonChange: - host: 192.168.10.111 - port: 5672 - virtualHost: ll-dev - username: zhangziheng - password: zhangziheng - producer: - enabled: false - consumer: - enabled: true - subscribeList: - - queue: 'app-business' - messageListener: commonChangeMessageListener - -#应用可以默认使用的配置文件 -#global: -# testCofnig: "aaa" -# tokenSecret: '12435twefdsfsdt4tsdcqw43tregdsgd' -# tokenExpire: 14400 #10天 -# smsMqTopic: 'topic_msg' -# smsMqTag: 'tag-sms' -# rsaPrivateKey: 'MIIEpQIBAAKCAQEAuAltXJI4kMQkucWCeLGK4Zyqw7VUp1JYS1GkJb0eJKCgxqJBzwjl8XpStA1hCv9BEX6SEsm/d2T6SDo+G6ySpfV0RQeZ7v32kE9+Eh0BK1Q8wU91nCa1CM9yfBhKXsQ3DKq2am5oLryNWXdKLXZPgoJbuIONG2G4oKakwUMX3aASp3Cj3rNXLea8ilXjFZ+OEp0DuZ4CsasO1MTaBS84mJhnzRNbuhHq5qyrVI02jw7Fim8siIBsmDDHgBd4l9hj6KAAr0jf9JOHaOp+KxfH76taqqaXI5lZIPG7lCP65iBuNNEqDSc21abcPhgvgK5K4xj9p5sG+V1FBISCE0dPrQIDAQABAoIBAG6dg/UTEhq5OhXKyDwBAqfOgbk2IVacoONMg+wG+rorLdeWKRXmlEcLLfB45i409Agu2l+ekY2SzPhiwXfixxYnLSZchkJmtS9SCEWc11oSvJ24Q8mCXmeYQIikFPdW2nurlA7uo4IL5K20jIo8xVd9QOHreAHQP6eX4gkjaZHUIOSJ2P6iffEQCHbXehoyCoTMLdK+1HTuZdO4C9r/S9f/Y1kLWfV5ogEi0DHJpUy37npinfqPp0LHbgpK2WBPOkQIhKvi/4OQ71EcYR5gyrA7nR+rQyPHdhFzTTyfTNTgmuNFuAYJODN5yd62RQd8i6chMx63tYDoYhCjI/ixv8UCgYEA9S1JTacLrFQP+2ryHnn6A3JOhbzj1Y552Nc1XixI9aJMxxCJGI0PvmzDb46BSLfoOz4yaqL1lBS2vyX4tc1rKL82JiokZhDlnFNS0yQgR03484BGPJ3D1+tTWQV0cXyq0nYOI+m9vPBciI1Lw07tJ5ZqJbTabtcu2aq8WrKMuFsCgYEAwCk0WB5TTQJQgjuuFXT/GU4cIl8/Pa3IgF7Ccd1WVkFr9uI6vBpToN+0i1zAb83Ss9maD3eH1Na/7GiKwzZOJj8Yas6b1UsbcHZA1Yt+cI2WUZf8L8QVYJrUtIkbKbG+jdg/KjjZt8mAO8IcXivUhfmj8XUIBClYCezEZmSIVpcCgYEAupHWmUfHo0Bo1QqB6l0pupuuUyj1OxprcG38B4itkHYL9OOJX+xgEalUYzzO9tYz23kuBmWxeRj2I6kyhK4noF85RnuFLUIoZ/gkK9Xu1jPogOuZByGK2XETAMgc3wteNj9t7Tg+kVtbHvJet+YEo75bUgw4uGX5GdxJ7r62RMcCgYEAgJfSaJm6oxFGcTCg+cj2oaeM2k+lEZCHWaiQNQSqr0ROjMOuDI0No92wg4aJXQh+1U5sc6dI5dzkSL9ZBPQFbkDRBUDINf9yGFt6Xa1g6s9FZcrwv8JXj/NtHneWDtvcqi2pb4bl48DbqKHou/hW22VJGd94gthsCxBACkmCl3cCgYEAp2/KJrDnLwAr4h6SVCufRRuNkZSRI+XITkE4xyQ/UDeL+iwCbX38Jcqa2lxCAXCLk++1xilSF/sJbBVkiDorBU9CC0tI5tPJFfHQodbePx1C0SQE8e0F+wtaeR9Z5m5KzHNs2Gciqw+2nJPU9uFQjUfGdXuIZF2bBvtXBWH+Prk=' -# rsaPublickey: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuAltXJI4kMQkucWCeLGK4Zyqw7VUp1JYS1GkJb0eJKCgxqJBzwjl8XpStA1hCv9BEX6SEsm/d2T6SDo+G6ySpfV0RQeZ7v32kE9+Eh0BK1Q8wU91nCa1CM9yfBhKXsQ3DKq2am5oLryNWXdKLXZPgoJbuIONG2G4oKakwUMX3aASp3Cj3rNXLea8ilXjFZ+OEp0DuZ4CsasO1MTaBS84mJhnzRNbuhHq5qyrVI02jw7Fim8siIBsmDDHgBd4l9hj6KAAr0jf9JOHaOp+KxfH76taqqaXI5lZIPG7lCP65iBuNNEqDSc21abcPhgvgK5K4xj9p5sG+V1FBISCE0dPrQIDAQAB' -# appDowloadUrl: 'http://llzby.cn/s/E9TTlQrJ' -# pcAccessUrl: 'http://llzby.cn/s/E9TTlQrJ' -# customServiceUrl: 'https://chaten.soboten.com/chat/h5/v2/index.html?sysnum=caf21f78c499463fbb54077f5c4a8efd&channelid=13&source=1&groupid=d16ef9bdcf3b46dc9726bbb00a7ee45b&partnerid=' #智齿客服 + biz_userId -# +mybatis-plus: + configuration: + map-underscore-to-camel-case: false + auto-mapping-behavior: full + #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印 + mapper-locations: classpath*:mapper/**/*Mapper.xml + global-config: + # 逻辑删除配置 + db-config: + update-strategy: IGNORED + # 删除前 + logic-not-delete-value: 1 + # 删除后 + logic-delete-value: 0 +# 参考文章 https://zhuanlan.zhihu.com/p/145359625 +management: + health: + elasticsearch: #禁用健康检查 + enabled: false + endpoints: + web: + exposure: + include: "health" + endpoint: + health: + show-details: always \ No newline at end of file diff --git a/src/main/resources/application-pre.yml b/src/main/resources/application-pre.yml index 7ab1468..a172ef3 100644 --- a/src/main/resources/application-pre.yml +++ b/src/main/resources/application-pre.yml @@ -41,98 +41,56 @@ global: rsaPublicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC_F5UQC1QWsu3QsESQBz9M-GDA9Atm0qVSvwIsy568lyRLi-nq3VvvnmgrlL4yTbngFzyfb2Dn35cNCHsBvIaGuCY3_PpzPqMzVpxr2QlEkhEX9atnJQ1rWexS8QeZtPjpiIwoQrChTzXjD_sYUkDrqSykFplyivf0NSO2WqCBdwIDAQAB rsaPrivateKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL8XlRALVBay7dCwRJAHP0z4YMD0C2bSpVK_AizLnryXJEuL6erdW--eaCuUvjJNueAXPJ9vYOfflw0IewG8hoa4Jjf8-nM-ozNWnGvZCUSSERf1q2clDWtZ7FLxB5m0-OmIjChCsKFPNeMP-xhSQOupLKQWmXKK9_Q1I7ZaoIF3AgMBAAECgYBxTUA61Ry0oL7U_86HP2TO9G4ZuhmQi9EucMaPXOPvmgYRLRIzCbDbMKc_P-BN3zwYnG57cgSZNz9OoPqeGvP_oVTnkoEpVkCSV-JP2p_DK09LdbDqszJXMrxAkPmWGUw8IRMcTJT1xJJcgzFE6T0CmTo-Vk47AnmqfJD4U6o74QJBAPRjVUJKZnrMSnSqKPDL2ThgTo8h7-KFxl_Z-g724lTOFiCmBpi6nCWAcuacFRrrYqxF-r9c4zdIyR7AvLROql8CQQDIK_kRF52dVtwShciZhyeUBLoi0nWV9F8mMGt60NTEER9zPEgPsv2aVn8h97KMWOwmd2Da4EPm25QxOuaKQC_pAkBczcfXp5co9KElkmR_pHl1jiTm97U3qSM-zPDHc_tYxvXiKgoBP4QCPbfkWMsu8MoEr4Jb3vMt0EcHlZtTQTgzAkAfmNla-lhV4sUgY1_T5EK6GbjsED6hag6u74u3ukkrnexR-10ApWdkumydBwV3I_464DM4uZfeVCDjWIHVpuYpAkEA6QLPztGD4V8Q1PqTEeSF3i68CKPM8vO1_mCH2JD7qsqDQcIKkczj5rTg7hlOKwB9V6gSw4CbnOF6moTooRD-cQ -##redis -#redis: -# datasource: -# token: -# database: 1 -# host: 122.112.153.128 -# port: 6379 -# password: 'redis' -# lettuce: -# pool: -# max-active: 8 -# max-wait: -1ms -# max-idle: 8 -# min-idle: 0 -# timeout: 3000ms +elasticsearch: + host: localhost + port: 9200 + username: elastic + password: bz5oF*MGy8pKL_I=7KxY #window系统本地启动 es8.x 重置密码命令:.\elasticsearch-reset-password -u elastic -#oss: -# name: alioss -# endpoint: ll-oss-pre.lianlianlvyou.com -# accessKey: -# secretKey: -# bucketName: -# args: -# expireTime: 3600 #过期时间 -# contentLengthRange: 2000 #大小限制 -##oss: -## name: alioss -## endpoint: ll-oss-pre.lianlianlvyou.com -## accessKey: -## secretKey: -## bucketName: -## args: -## expireTime: 3600 #过期时间 -## contentLengthRange: 2000 #大小限制 -## redis分布式锁 -#redisson: -# enabled: true -# address: 'redis://192.168.10.113:6379' -# password: '123456' -# database: 5 -# connectionPoolSize: 4 -# connectionMinimumIdleSize: 4 -# -## 阿里云rocketmq -#aliyun: -# rocketmq: -# config: -# AccessKey: 1 -# SecretKey: 1 -# NAMESRV_ADDR: 1 -# GROUP_ID: 1 -# producer: -# enabled: true -# -#rabbitmq: -# configs: -# order: #实例名称 -# host: 192.168.10.111 -# port: 5672 -# virtualHost: ll-dev -# username: zhangziheng -# password: zhangziheng -# producer: -# enabled: true -# exchange: order_status -# routingKey: ORDER_COMPLETE -# confirmCallback: orderMqConfirmCallback -# commonChange: -# host: 192.168.10.111 -# port: 5672 -# virtualHost: ll-dev -# username: zhangziheng -# password: zhangziheng -# producer: -# enabled: false -# consumer: -# enabled: true -# subscribeList: -# - queue: 'app-business' -# messageListener: commonChangeMessageListener -# -##应用可以默认使用的配置文件 -#global: -# testCofnig: "aaa" -# tokenSecret: '12435twefdsfsdt4tsdcqw43tregdsgd' -# tokenExpire: 14400 #10天 -# smsMqTopic: 'topic_msg' -# smsMqTag: 'tag-sms' -# rsaPrivateKey: 'MIIEpQIBAAKCAQEAuAltXJI4kMQkucWCeLGK4Zyqw7VUp1JYS1GkJb0eJKCgxqJBzwjl8XpStA1hCv9BEX6SEsm/d2T6SDo+G6ySpfV0RQeZ7v32kE9+Eh0BK1Q8wU91nCa1CM9yfBhKXsQ3DKq2am5oLryNWXdKLXZPgoJbuIONG2G4oKakwUMX3aASp3Cj3rNXLea8ilXjFZ+OEp0DuZ4CsasO1MTaBS84mJhnzRNbuhHq5qyrVI02jw7Fim8siIBsmDDHgBd4l9hj6KAAr0jf9JOHaOp+KxfH76taqqaXI5lZIPG7lCP65iBuNNEqDSc21abcPhgvgK5K4xj9p5sG+V1FBISCE0dPrQIDAQABAoIBAG6dg/UTEhq5OhXKyDwBAqfOgbk2IVacoONMg+wG+rorLdeWKRXmlEcLLfB45i409Agu2l+ekY2SzPhiwXfixxYnLSZchkJmtS9SCEWc11oSvJ24Q8mCXmeYQIikFPdW2nurlA7uo4IL5K20jIo8xVd9QOHreAHQP6eX4gkjaZHUIOSJ2P6iffEQCHbXehoyCoTMLdK+1HTuZdO4C9r/S9f/Y1kLWfV5ogEi0DHJpUy37npinfqPp0LHbgpK2WBPOkQIhKvi/4OQ71EcYR5gyrA7nR+rQyPHdhFzTTyfTNTgmuNFuAYJODN5yd62RQd8i6chMx63tYDoYhCjI/ixv8UCgYEA9S1JTacLrFQP+2ryHnn6A3JOhbzj1Y552Nc1XixI9aJMxxCJGI0PvmzDb46BSLfoOz4yaqL1lBS2vyX4tc1rKL82JiokZhDlnFNS0yQgR03484BGPJ3D1+tTWQV0cXyq0nYOI+m9vPBciI1Lw07tJ5ZqJbTabtcu2aq8WrKMuFsCgYEAwCk0WB5TTQJQgjuuFXT/GU4cIl8/Pa3IgF7Ccd1WVkFr9uI6vBpToN+0i1zAb83Ss9maD3eH1Na/7GiKwzZOJj8Yas6b1UsbcHZA1Yt+cI2WUZf8L8QVYJrUtIkbKbG+jdg/KjjZt8mAO8IcXivUhfmj8XUIBClYCezEZmSIVpcCgYEAupHWmUfHo0Bo1QqB6l0pupuuUyj1OxprcG38B4itkHYL9OOJX+xgEalUYzzO9tYz23kuBmWxeRj2I6kyhK4noF85RnuFLUIoZ/gkK9Xu1jPogOuZByGK2XETAMgc3wteNj9t7Tg+kVtbHvJet+YEo75bUgw4uGX5GdxJ7r62RMcCgYEAgJfSaJm6oxFGcTCg+cj2oaeM2k+lEZCHWaiQNQSqr0ROjMOuDI0No92wg4aJXQh+1U5sc6dI5dzkSL9ZBPQFbkDRBUDINf9yGFt6Xa1g6s9FZcrwv8JXj/NtHneWDtvcqi2pb4bl48DbqKHou/hW22VJGd94gthsCxBACkmCl3cCgYEAp2/KJrDnLwAr4h6SVCufRRuNkZSRI+XITkE4xyQ/UDeL+iwCbX38Jcqa2lxCAXCLk++1xilSF/sJbBVkiDorBU9CC0tI5tPJFfHQodbePx1C0SQE8e0F+wtaeR9Z5m5KzHNs2Gciqw+2nJPU9uFQjUfGdXuIZF2bBvtXBWH+Prk=' -# rsaPublickey: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuAltXJI4kMQkucWCeLGK4Zyqw7VUp1JYS1GkJb0eJKCgxqJBzwjl8XpStA1hCv9BEX6SEsm/d2T6SDo+G6ySpfV0RQeZ7v32kE9+Eh0BK1Q8wU91nCa1CM9yfBhKXsQ3DKq2am5oLryNWXdKLXZPgoJbuIONG2G4oKakwUMX3aASp3Cj3rNXLea8ilXjFZ+OEp0DuZ4CsasO1MTaBS84mJhnzRNbuhHq5qyrVI02jw7Fim8siIBsmDDHgBd4l9hj6KAAr0jf9JOHaOp+KxfH76taqqaXI5lZIPG7lCP65iBuNNEqDSc21abcPhgvgK5K4xj9p5sG+V1FBISCE0dPrQIDAQAB' -# appDowloadUrl: 'http://llzby.cn/s/E9TTlQrJ' -# pcAccessUrl: 'http://llzby.cn/s/E9TTlQrJ' -# customServiceUrl: 'https://chaten.soboten.com/chat/h5/v2/index.html?sysnum=caf21f78c499463fbb54077f5c4a8efd&channelid=13&source=1&groupid=d16ef9bdcf3b46dc9726bbb00a7ee45b&partnerid=' #智齿客服 + biz_userId -# +rocketmq: + name-server: ${SERVER_ADDRESS}:9876 + producer: + group: producer-group + consumer: + group: consumer-group + enable-orderly: false + +springdoc: + api-docs: + path: /v3/api-docs # 自定义 API 文档路径 + swagger-ui: + path: /swagger-ui.html # 自定义 Swagger UI 路径 + enabled: true + info: + title: 文撩 API 文档 + description: 这是文撩平台的 API 文档 + version: v1.0 + +mybatis-plus: + configuration: + map-underscore-to-camel-case: false + auto-mapping-behavior: full + #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印 + mapper-locations: classpath*:mapper/**/*Mapper.xml + global-config: + # 逻辑删除配置 + db-config: + update-strategy: IGNORED + # 删除前 + logic-not-delete-value: 1 + # 删除后 + logic-delete-value: 0 + +# 参考文章 https://zhuanlan.zhihu.com/p/145359625 +management: + health: + elasticsearch: #禁用健康检查 + enabled: false + endpoints: + web: + exposure: + include: "health" + endpoint: + health: + show-details: always