mirror of
https://gitee.com/dromara/dax-pay.git
synced 2025-10-13 13:20:23 +00:00
ref 升级脚手架依赖
This commit is contained in:
63
README.md
63
README.md
@@ -7,33 +7,33 @@
|
||||
<img src='https://gitee.com/bootx/dax-pay/badge/star.svg?theme=dark' alt='star'/>
|
||||
<img src="https://img.shields.io/badge/Dax%20Pay-3.0.0-success.svg" alt="Build Status"/>
|
||||
<img src="https://img.shields.io/badge/Author-Daxpay-orange.svg" alt="Build Status"/>
|
||||
<img src="https://img.shields.io/badge/Spring%20Boot-3.4.3-blue.svg" alt="Downloads"/>
|
||||
<img src="https://img.shields.io/badge/Spring%20Boot-3.5.4-blue.svg" alt="Downloads"/>
|
||||
<img src="https://img.shields.io/badge/license-Apache%20License%202.0-green.svg"/>
|
||||
</p>
|
||||
|
||||
# Dromara Dax-Pay(单商户多应用版)
|
||||
# Dromara Dax-Pay(开源版)
|
||||
|
||||
## ❗使用须知
|
||||
## 使用须知
|
||||
|
||||
`DaxPay`是一款基于`Apache License 2.0`协议分发的开源软件,受中华人民共和国相关法律法规的保护和限制,可以在符合[《用户授权使用协议》](用户授权使用协议.txt)和
|
||||
[《Apache License 2.0》](LICENSE)开源协议情况下进行免费使用、学习和交流。**在使用前请阅读上述协议,如果不同意请勿进行使用。**
|
||||
|
||||
## 🍈项目介绍
|
||||
## 项目介绍
|
||||
|
||||
> DaxPay是一套开源支付网关系统,已经对接支付宝、微信支付、云闪付相关的接口。可以独立部署,提供接口供业务系统进行调用,不对原有系统产生影响。
|
||||
> 同时与商业版使用同样的底层代码,保证统一接口尽量兼容,可以方便的升级为商业版。
|
||||
|
||||
## 🧭 特色功能
|
||||
## 特色功能
|
||||
- 支持支付、退款等支付相关的核心能力
|
||||
- 封装各类支付通道的接口为统一的接口,方便业务系统进行调用,简化对接多种支付方式的复杂度
|
||||
- 已对接`微信支付`、`支付宝`和`云闪付`相关的接口,并以扩展包的方式支持更多类型的通道
|
||||
- 支持多应用配置,可以同时对接多个支付通道账号,方便多个业务系统对接
|
||||
- 支持支付、退款、分账等支付相关的能力
|
||||
- 提供网关支付功能:收银台、聚合支付、收款码牌等功能
|
||||
- 提供`HTTP`方式接口调用能力,和`Java`版本的`SDK`,方便业务系统进行对接
|
||||
- 接口请求和响应数据支持启用签名机制,保证交易安全可靠
|
||||
- 提供管理端,方便运营人员进行管理和操作
|
||||
|
||||
## 📃 文档和源码地址
|
||||
## 文档和源码地址
|
||||
### 文档地址
|
||||
在 [DaxPay文档站](https://daxpay.dromara.org/) 下的支付网关(DaxPay)模块下可以进行查阅相关文档,具体链接地址如下:
|
||||
[快速指南](https://daxpay.dromara.org/single/guides/overview/项目介绍.html)、
|
||||
@@ -49,7 +49,7 @@
|
||||
| 网关前端地址 | [GITEE](https://gitee.com/bootx/dax-pay-h5) | [GITHUB](https://github.com/xxm1995/dax-pay-h5) | |
|
||||
|
||||
|
||||
## 🏬 系统演示
|
||||
## 系统演示
|
||||
### 开源版:
|
||||
> 注:演示账号部分功能权限未开放。
|
||||
|
||||
@@ -61,43 +61,33 @@
|
||||
|
||||
### 商业版
|
||||
|
||||
商户端: https://merchant.dax-pay.test.yibeiguangnian.cn/
|
||||
运营端
|
||||
https://admin.web.daxpay.cn/
|
||||
代理端
|
||||
https://agent.web.daxpay.cn/
|
||||
商户端
|
||||
https://merchant.web.daxpay.cn/
|
||||
|
||||
运营端: https://daxpay-web.test.yibeiguangnian.cn/
|
||||
运营端演示用户: csadmin/123123
|
||||
|
||||
运营端测试: csadmin/123123
|
||||
代理端演示用户: csdls/123123
|
||||
|
||||
商户端普通商户测试: cspt/123123
|
||||
商户端普通商户演示: cspt/123123
|
||||
|
||||
商户端特约商户测试: csty/123123
|
||||
商户端特约商户演示: csdl/123123
|
||||
|
||||
|
||||
## 🥞 核心技术栈
|
||||
## 核心技术栈
|
||||
| 名称 | 描述 | 版本要求 |
|
||||
|-------------|--------|------------------|
|
||||
| Jdk | Java环境 | 21+ |
|
||||
| Spring Boot | 开发框架 | 3.4.x |
|
||||
| Redis | 分布式缓存 | 5.x版本及以上 |
|
||||
| Spring Boot | 开发框架 | 3.5.x |
|
||||
| Redis | 分布式缓存 | 7.x版本及以上 |
|
||||
| Postgresql | 数据库 | Postgresql 12及以上 |
|
||||
| MySQL | 数据库 | MySQL 8.0及以上 |
|
||||
| Vue | 前端框架 | 3.x |
|
||||
|
||||
## 🛠️ 业务系统接入
|
||||
> 业务系统想接入支付网关的话,不需要集成到业务系统里,只需要单独部署一份支付系统,然后业务系统通过接口调用即可拥有对应的支付能力,
|
||||
不会对原业务系统的架构产生影响。如果是Java项目,可以使用SDK简化接入流程, 其他语言可以参照中的说明使用HTTP接口方式接入。
|
||||
|
||||
### Java客户端SDK
|
||||
> SDK版本号与支付网关的版本保持一致,如果需要使用,请在pom.xml中添加如下依赖。SDK使用方式参考[SDK使用说明](https://daxpay.dromara.org/single/gateway/overview/SDK使用说明.html)。
|
||||
|
||||
```xml
|
||||
<!-- 支付SDK -->
|
||||
<dependency>
|
||||
<groupId>org.dromara.daxpay</groupId>
|
||||
<artifactId>daxpay-sdk</artifactId>
|
||||
<version>${latest.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 🍎 系统截图
|
||||
## 系统截图
|
||||
### 通道配置
|
||||
<img src="https://cdn.jsdelivr.net/gh/xxm1995/picx-images-hosting@master/20250427/wechat_2025-04-27_204334_543.lvxlxz86a.webp" alt="wechat_2025-04-27_204334_543" />
|
||||
|
||||
@@ -115,8 +105,6 @@
|
||||
### 小程序快捷收银
|
||||
<img src="https://cdn.jsdelivr.net/gh/xxm1995/picx-images-hosting@master/20250427/cbe6e332c55b241215787254951dc7ec.969y3b848r.webp" alt="cbe6e332c55b241215787254951dc7ec" width = "270" height = "570" />
|
||||
|
||||
## 🛣️ 路线图
|
||||
[**历史更新记录**](/_doc/ChangeLog.md)
|
||||
|
||||
## 🥪 关于我们
|
||||
|
||||
@@ -127,11 +115,6 @@
|
||||
<img src="https://cdn.jsdmirror.com/gh/xxm1995/picx-images-hosting@master/connect/1733360741745_d.83a33entp3.webp" width = "330" height = "500"/>
|
||||
</p>
|
||||
|
||||
扫码加入钉钉交流群: [加群连接](https://qr.dingtalk.com/action/joingroup?code=v1,k1,AzkcWLa8J/OHXi+nTWwNRc68IAJ0ckWXEEIvrJofq2A=&_dt_no_comment=1&origin=11)
|
||||
<p>
|
||||
<img src="https://cdn.jsdmirror.com/gh/xxm1995/picx-images-hosting@master/connect/png-(1).7egk526qnp.webp" width = "400" height = "400"/>
|
||||
</p>
|
||||
|
||||
微信扫码加小助手拉群: sdcit2020
|
||||
<p>
|
||||
<img alt="微信图片_20240226144703" height="480" src="https://cdn.jsdmirror.com/gh/xxm1995/picx-images-hosting@master/connect/微信图片_20240412152722.231nkeje2o.webp" width="330"/>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,209 +0,0 @@
|
||||
# CHANGELOG
|
||||
## [v3.0.0.beta5] 2025-05-01
|
||||
|
||||
## [v3.0.0.beta4] 2025-01-10
|
||||
- 新增: 微信服务商支付支持
|
||||
- 新增: 支付宝服务商支付支持
|
||||
- 新增: 系统首页驾驶舱数据展示页we
|
||||
- 新增: 微信支持公钥证书方式
|
||||
- 优化: 商户应用增加停用功能
|
||||
- 优化: 对各种交易增加新的同步失败异常处理, 防止同步失败后无限进行同步
|
||||
- fix: 微信支付同步V3接口金额空指针问题
|
||||
- fix: 订单支付成功重复更新问题
|
||||
- fix: 优化自动分账没有默认分账组时的处理
|
||||
## [v3.0.0.beta3] 2024-12-22
|
||||
- 新增: 支持支付宝分账功能
|
||||
- 新增: 支持微信分账功能
|
||||
- 新增: 支持分账发起和分账完结功能
|
||||
- 新增: 支持分账接收方配置功能
|
||||
- 新增: 支持支付订单自动分账功能
|
||||
- 新增: 自动同步分账订单状态功能
|
||||
- 新增: 分账回调通知和分账消息通知功能
|
||||
- 新增: 自动完结分账订单功能
|
||||
- 新增: 分账同步功能
|
||||
- 优化: 升级wxjava4.6.9.B,并处理证书配置问题
|
||||
- 优化: 调整分账接收者和分账组配置逻辑
|
||||
- fix: 修复微信v2当面付发起失败问题
|
||||
- fix: 修复微信v2分账参数未设置问题
|
||||
- fix: sm3签名校验问题
|
||||
- fix: SDK参数和返回对象与接口不一致修改
|
||||
|
||||
## [v3.0.0.beta2] 2024-12-05
|
||||
- 新增: 增加PC收银台功能
|
||||
- 新增: 增加H5收银台功能
|
||||
- 新增: 增加聚合收银台功能
|
||||
- 新增: 增加收银台配置功能
|
||||
- 新增: 交易调试页增加收银台调试选项
|
||||
- 新增: SDK增加收银台相关接口/认证相关接口
|
||||
- 重构: 原支付收银台改为支付码牌, 一个应用支持多个码牌
|
||||
- 重构: SDK命名空间更改为org.dromara.daxpay
|
||||
- 优化: 微信通道添加单独的认证跳转地址
|
||||
- 优化: 添加定时任务和事件监听服务
|
||||
- 优化: 微信支付方式的判断逻辑,提高了系统稳定性
|
||||
- fix: 系统参数使用到MySQL8保留字
|
||||
- fix: Mysql 脚本缺少 缺失 表pay_api_const
|
||||
- fix: H5构建版本限制错误, 限制为最低为node20+
|
||||
- fix: 修复商户回调和通知的延迟逻辑
|
||||
- fix: 商户应用类型命名错误
|
||||
- fix: 修复对账差异逻辑
|
||||
- fix: 修复微信支付同步金额为空的问题
|
||||
- fix: 修复 BigDecimal 类型数据序列化和签名异常问题
|
||||
## [v3.0.0.beta1] 2024-10-24
|
||||
- 重构: JDK版本升级为21+, Spring Boot 版本升级为3.3.x, 前端组件升级为Antd Vue 4.x + Vite5
|
||||
- 重构: 数据库更新为PostgreSQL + MySQL8.x 双版本支持
|
||||
- 重构: 脚手架全新重构, 精简和优化各种功能模块, 支持基于有赞文章实现的Redis延时队列
|
||||
- 重构: 支持多应用模式, 每个应用都可以配置单独一套支付通道、通知订阅、收款码牌等配置, 可以实现同时对接多个业务系统
|
||||
- 重构: 项目结构进行重构, 修改为支付核心+通道扩展+功能插件的方式, 实现功能模块的耦合拆分, 便于进行功能扩展和二次开发
|
||||
- 重构: 对账功能进行重构, 更加简单直观和易用
|
||||
- 重构: 删除订单调整相关逻辑, 分别放到订单同步和回调处理中, 只保留
|
||||
- 新增: 微信支付同时支持V2和V3版本的接口, 同时V3版本支持付款码和撤销接口
|
||||
- 新增: 交易调试接口功能, 用于开发时对交易流程进行测试
|
||||
- 新增: 获取通道认证信息的测试页, 便于获取微信、支付宝等用户的认证信息
|
||||
- 新增: 增加简易移动端收款码牌功能, 支持自动跳转到微信或支付宝对应的H5收银台, 支持自主配置所使用的通道和支付方式
|
||||
- 新增: 增加商户通知功能, 通过订阅指定类型的通知类型, 将会在符合条件时推送到预留的客户系统地址上
|
||||
- 优化: 各类错误处理进行统一化处理
|
||||
- 优化: 优化商户回调功能, 简化配置项, 使用延时器优化重复推送的逻辑
|
||||
- 优化: 减少在各种流程中上下文对象的线程变量使用, 非必需的上下文对象使用方法调用明确传输
|
||||
- 优化: 对各类状态码进行优化合并, 如转账接收方类型、分账接收方类型等
|
||||
- 优化: 所有金额统一为元, 保留两位小数
|
||||
## [v2.0.8] 2024-06-27
|
||||
- 新增: 撤销接口
|
||||
- 新增: 转账功能
|
||||
- 新增: DEMO增加转账演示功能
|
||||
- 新增: DEMO增加获取OpenID功能
|
||||
- 新增: 支付宝支持JSAPI方式支付
|
||||
- 新增: 绑定对账接收方增加扫码获取微信OpenID和支付宝OpenId功能
|
||||
- 新增: 支付宝微信等消息通知地址支持一键生成
|
||||
- 新增: 请求IP参数增加正则校验
|
||||
- 优化: 手动发起分账重试参数修正
|
||||
- 优化: 细分各种支付异常类和编码
|
||||
- 优化: 支付宝SDK修改为官方SDK
|
||||
- 优化: 界面金额统一调整为元
|
||||
- 优化: 上下文对象进行优化精简
|
||||
- 优化: 支付接口公共参数添加随机数字段, 预防重放问题
|
||||
- 优化: 请求接口增加有效期校验, 超时后失效
|
||||
- 优化: 数据库表进行规则, 字段设置长度, 增加索引, 对应请求参数添加校验
|
||||
- 优化: 订单和扩展信息进行合并
|
||||
- 优化: 支付通道两个独立的配置进行合并为一个
|
||||
- 优化: 平台配置增加接口请求有效时长配置
|
||||
- 优化: 平台配置和接口配置删除回调地址配置
|
||||
- 优化: 接口配置删除是否验签配置和回调地址
|
||||
- 优化: 分账订单相关命名统一为Alloc
|
||||
- 优化: 支付订单拆分退款状态为单独的字段
|
||||
- 优化: 策略工厂修改为统一的通用策略工厂
|
||||
- 优化: 支付和退款达到终态不可以再回退回之前的状态
|
||||
- 优化: 优化认证授权地址配置, 拆分为支持单独配置
|
||||
- 优化: 优化各类网址配置, 兼容结尾带/和不带/
|
||||
- fix: 修复支付关闭参数名称不正确问题
|
||||
- fix: 退款回调消息字段不一致导致验签不通过问题
|
||||
- fix: 云闪付空指针问题
|
||||
|
||||
## [v2.0.7] 2024-06-05
|
||||
- 新增: 资金流水记录功能
|
||||
- 新增: 分账功能支持分账组分账和自己传接收方进行分账
|
||||
- 新增: 分账接收的添加、删除、查询接口调用
|
||||
- 新增: 分账发起、完结、同步功能支持接口调用
|
||||
- 新增: 支持自动分账和手动发起分账两种
|
||||
- 新增: 分账通知发送功能
|
||||
- 优化: 对超时订单进行处理(数据库定时同步)
|
||||
- 优化: 订单金额小于0.01元直接忽略不进行分账,增加新状态,
|
||||
- 优化: 优化签名注解和上下文初始化注解切面
|
||||
- 优化: 分账重试会自动根据分账失败和
|
||||
- 优化: 优化签名注解和上下文初始化注解切面, 更方便初始化上下文
|
||||
- fix: 对账差异单数据不一致处理异常, 本地待对账订单类型记录错误
|
||||
- fix: 订单超时任务注册任务错误,id改为订单号
|
||||
- fix: 系统中金额分转元精度异常问题
|
||||
- fix: 同步回调处理参数订单号接收失败
|
||||
- fix: 支付和退款消息签名值不一致问题
|
||||
- fix: 分账发起时错误的使用订单号作为分账号
|
||||
|
||||
## [v2.0.6] 2024-05-15
|
||||
- 新增: 下载原始对账单功能,转换为指定格式进行下载
|
||||
- 新增: 增加对账结果计算和显示,以及对单差异数据查看功能
|
||||
- 新增: 自动分账功能,支付完成后自动根据默认分账组将订单分账
|
||||
- 新增: 三方支付通道订单号规则优化: 支付P、退款R、分账A,可以根据环境加前缀:DEV_、DEMO_、PRE_
|
||||
- 优化: 去除组合支付概念,删除现金支付和储值卡支付方式,系统整体复杂度降低一半以上
|
||||
- 优化: 消息通知发送流程改造,不在使用复杂继承组合关系,只保留一级类继承关系
|
||||
- 优化: 回调通知处理不再使用继承模式,修改为组合模式,提高阅读和debug的便利性
|
||||
- 优化: 支付同步、回调和退款同步、回调去除组合支付导致的特殊处理逻辑
|
||||
- 优化: 统一公共请求参数和响应参数,同时响应参数格式,便于进行统一处理
|
||||
- 优化: 统一参数命名规则,包括支付、退款、对账、分账等相关参数的属性,实现风格的统一
|
||||
- 优化: 使用切面统一处理API调用异常, 做统一包装返回
|
||||
- 优化: 金额显示统一使用元
|
||||
- 优化: 前端查询条件适配,统一页面交互逻辑,初步完成管理端的功能完备性
|
||||
- 优化: 支持自动同步对账结果,并自动对分账单进行完结
|
||||
- 优化: 基础脚手架从jar集成修改为源码集成
|
||||
- fix: 自动同步任务不生效
|
||||
- fix: 算收款金额时对产生退款的支付订单未进行计算
|
||||
## [v2.0.5] 2024-04-18
|
||||
- 新增: 支持支付宝分账功能
|
||||
- 新增: 支持微信分账功能
|
||||
- 新增: 分账接收者和分账组管理
|
||||
- 新增: 支持分账结果同步功能
|
||||
- 新增: 支付通道配置中支持是否支持分账
|
||||
- 新增: SDK支持分账接口
|
||||
- 优化: 收银台演示支持设置是否分账
|
||||
- fix: 修复创建支付订单报错时, 订单保存数据不完整
|
||||
## [v2.0.4] 2024-03-26
|
||||
- 新增: 首页驾驶舱功能: 各通道收入和支付情况
|
||||
- 新增: 云闪付支持对账功能
|
||||
- 新增: 对账文件支持手动导入
|
||||
- 新增: 结算台DEMO增加云闪付示例
|
||||
- 新增: 增加支付限额,包括整单限额和通道限额
|
||||
- 优化: 支付流程也改为先落库后支付情况, 避免极端情况导致掉单
|
||||
- 优化: 前端列表状态显示优化
|
||||
- fix: 对账订单流水号生成规则不是按天生成修改
|
||||
|
||||
## [v2.0.3] 2024-03-16
|
||||
- 增加云闪付通道,支持支付、退款、同步、回调处理
|
||||
- 增加定时同步退款中的退款订单任务
|
||||
- 增加通知任务订单的状态类型,例如订单关闭、成功、失败等
|
||||
- 增加退款操作支持重试
|
||||
- 增加手动触发通知任务消息的发送功能
|
||||
|
||||
## [v2.0.2] 2024-03-06
|
||||
- 增加微信支付对账功能
|
||||
- 增加支付宝支付对账功能
|
||||
- 优化: 修复策略对订单时间和状态字段的变更优化
|
||||
- fix: 前端支付订单查询条件中"支付ID"条件不生效
|
||||
|
||||
## [v2.0.1] 2024-02-27
|
||||
- 增加支付、退款时客户通知功能,支持多次重发
|
||||
- 开源文档增加支付通知和退款通知文档
|
||||
- 增加客户通知任务记录功能
|
||||
- 支持钱包支付、流水记录、各类操作等功能
|
||||
- 支持储值卡支付、流水记录、各类操作等功能
|
||||
- 支持现金支付和流水记录功能
|
||||
- 增加支付宝流水记录功能
|
||||
- 增加微信流水记录功能
|
||||
- 变更: 废弃调用接口时的`version`字段,调用时不再进行传输,SDK中同步进行删除
|
||||
- 优化: 订单支持关闭时间记录
|
||||
- 优化: 增加退款订单扩展记录
|
||||
- 优化: SDK增加简单退款、多通道退款等多中测试样例
|
||||
- 优化: IJPay进行Https请求时, TLS版本使用读取JDK中支持的版本
|
||||
- fix: 同步支付通道订单不能正确生成
|
||||
- fix: 修复聚合条码支付时付款码未传输问题
|
||||
- fix: 修复微信退款同步时, 错误信息未保存问题
|
||||
- fix: 修复手动发起退款时上下文未进行初始化的问题
|
||||
- fix: 修复简单退款选择全部退款时报错问题
|
||||
- fix: 修复退款时未检验退款金额问题,导致可以退款余额可以大于可退余额
|
||||
|
||||
## [v2.0.0] 2024-02-14
|
||||
- 支持支付宝支付: 扫码支付、付款码支付、PC支付、H5支付
|
||||
- 支持微信支付: 扫码支付、WAP支付、公众号支付
|
||||
- 增加聚合支付演示功能,支持支付宝和微信支付
|
||||
- 增加PC收银台演示功能,各种类型的支付
|
||||
- 增加手机收银台演示功能,支持在微信、支付宝、浏览器中发起对应的请求
|
||||
- 提供Java版本SDK,简化业务系统对支付网关的调用
|
||||
- 支持请求参数签名和验签机制,已经支持SHA256和MD5
|
||||
- 支持支付订单超时自动进行关闭
|
||||
- 支持支付订单手动关闭功能
|
||||
- 支持支付退款功能,可以进行全部退款或部分退款
|
||||
- 支持支付同步功能,通过同步接口可以获取第三方支付网关的状态
|
||||
- 支持支付和退款订单的修复功能,根据取第三方支付网关订单的状态,对订单进行修正,如支付同步、退款同步、消息回调等可触发
|
||||
- 部分支付对账功能,已经实现支付宝和微信对账单下载解析和保存的功能
|
||||
- 支持对各支付通道进行管理,包括是否启用、显示Logo图等
|
||||
- 支持对支付网关对外暴露的接口进行管理,支持启停用、是否验签、是否消息通知等功能
|
||||
- 去除调用时的用户概念,作为独立的支付网关使用
|
||||
- 组合支付已预先进行支持,支持一个异步支付+多个同步支付通道组合进行收单支付
|
||||
- 记录支付时出现的回调记录、同步记录、修复记录、关闭记录
|
||||
|
@@ -1,9 +0,0 @@
|
||||
# 单商户
|
||||
## 3.0.0.beta5 功能优化和服务商支付
|
||||
- [ ] 网关配套移动端开发
|
||||
- [ ] 同步回调页
|
||||
- [ ] 增加首页驾驶舱功能
|
||||
- [ ] 支付时付款码参数提升到PayParam, 简化调用方式
|
||||
## 任务池
|
||||
- [ ] 分账重试
|
||||
- [ ] 同步接口优化, 返回同步完的数据
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-config</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-exception-handler</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-header-holder</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-jackson</artifactId>
|
||||
|
@@ -8,6 +8,7 @@ import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* jackson常用工具类封装
|
||||
@@ -130,4 +131,24 @@ public class JacksonUtil {
|
||||
throw new RuntimeException("json反序列化失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转为map
|
||||
*/
|
||||
public Map<String, Object> parseObj(Object mchApply) {
|
||||
return parseObj(mchApply,true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> parseObj(Object mchApply, boolean ignoreNull) {
|
||||
try {
|
||||
if (ignoreNull) {
|
||||
return ignoreNullObjectMapper.convertValue(mchApply, Map.class);
|
||||
}
|
||||
return objectMapper.convertValue(mchApply, Map.class);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new RuntimeException("json反序列化失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-log</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-mybatis-plus</artifactId>
|
||||
@@ -63,6 +63,17 @@
|
||||
<artifactId>common-spring</artifactId>
|
||||
<version>${bootx-platform.version}</version>
|
||||
</dependency>
|
||||
<!-- hutool 组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package cn.bootx.platform.common.mybatisplus.handler;
|
||||
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.core.lang.TypeReference;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
@@ -35,13 +35,13 @@ public class IntegerListTypeHandler extends AbstractJsonTypeHandler<List<Integer
|
||||
@Override
|
||||
public List<Integer> parse(String json) {
|
||||
if (StrUtil.isNotBlank(json)){
|
||||
return JsonUtil.toBean(json, new TypeReference<>() {}, false);
|
||||
return JSONUtil.toBean(json, new TypeReference<>() {}, false);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJson(List<Integer> obj) {
|
||||
return JsonUtil.toJsonStr(obj);
|
||||
return JSONUtil.toJsonStr(obj);
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package cn.bootx.platform.common.mybatisplus.handler;
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.core.lang.TypeReference;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
@@ -30,13 +31,13 @@ public class LongListTypeHandler extends AbstractJsonTypeHandler<List<Long>> {
|
||||
@Override
|
||||
public List<Long> parse(String json) {
|
||||
if (StrUtil.isNotBlank(json)){
|
||||
return JsonUtil.toBean(json, new TypeReference<>() {}, false);
|
||||
return JSONUtil.toBean(json, new TypeReference<>() {}, false);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJson(List<Long> obj) {
|
||||
return JsonUtil.toJsonStr(obj);
|
||||
return JSONUtil.toJsonStr(obj);
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,6 @@ public class StringListTypeHandler extends AbstractJsonTypeHandler<List<String>>
|
||||
|
||||
@Override
|
||||
public String toJson(List<String> obj) {
|
||||
return JsonUtil.toJsonStr(obj);
|
||||
return JSONUtil.toJsonStr(obj);
|
||||
}
|
||||
}
|
||||
|
@@ -2,11 +2,13 @@ package cn.bootx.platform.common.mybatisplus.util;
|
||||
|
||||
import cn.bootx.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.bootx.platform.core.annotation.BigField;
|
||||
import cn.bootx.platform.core.exception.BizException;
|
||||
import cn.bootx.platform.core.rest.param.PageParam;
|
||||
import cn.bootx.platform.core.rest.result.PageResult;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
@@ -20,7 +22,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.apache.ibatis.reflection.property.PropertyNamer;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -159,4 +164,17 @@ public class MpUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据库类型, 别忘了关闭, 不然会连接池泄露
|
||||
*/
|
||||
public String getDbType(){
|
||||
try {
|
||||
try (Connection connection = SpringUtil.getBean(DataSource.class).getConnection()) {
|
||||
return connection.getMetaData().getDatabaseProductName();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new BizException("获取数据库类型失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,312 @@
|
||||
package cn.bootx.platform.common.mybatisplus.util;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.Db;
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.hutool.db.handler.BeanHandler;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.PropertyPlaceholderHelper;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 数据库工具类
|
||||
*
|
||||
* @author nn200433
|
||||
* @date 2024-03-28 09:32:34
|
||||
*/
|
||||
@Slf4j
|
||||
public class MybatisDbUtil {
|
||||
|
||||
private static final String PARAM_KEY_PRE = "ew.paramNameValuePairs.";
|
||||
private static final String UPDATE_SET_PREFIX = "SET";
|
||||
private static final String SQL_PREFIX_AND_SUFFIX = "'";
|
||||
public static final String PLACEHOLDER_PREFIX = "#{";
|
||||
private static final String PLACEHOLDER_SUFFIX = "}";
|
||||
private static final PropertyPlaceholderHelper PLACEHOLDER_HELPER = new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX);
|
||||
|
||||
/**
|
||||
* 物理删除
|
||||
*
|
||||
* @param tableClz 数据表实体类型
|
||||
* @param ids 主键
|
||||
* @return int 影响行数
|
||||
* @author nn200433
|
||||
*/
|
||||
public static int delete(Class<?> tableClz, String... ids) {
|
||||
final TableInfo tableInfo = getTableInfo(tableClz);
|
||||
final String tableName = tableInfo.getTableName();
|
||||
final String keyColumn = tableInfo.getKeyColumn();
|
||||
Assert.notBlank(keyColumn, "{} 未设置 @TableId 注解!", tableClz);
|
||||
int count = 0;
|
||||
try {
|
||||
count = Db.use(dataSource()).del(Entity.create(tableName).set(keyColumn, ids));
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 物理删除
|
||||
*
|
||||
* <p>
|
||||
* 请使用{@link com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper}构造wrapper
|
||||
* </p>
|
||||
*
|
||||
* @param wrapper 包装物
|
||||
* @return int 影响行数
|
||||
* @author nn200433
|
||||
*/
|
||||
public static <T> int delete(AbstractWrapper<T, ?, ?> wrapper) {
|
||||
final Class<T> entityClass = (Class<T>) getEntityClass(wrapper);
|
||||
final TableInfo tableInfo = getTableInfo(entityClass);
|
||||
final String whereSql = wrapper.getCustomSqlSegment();
|
||||
final String tableName = tableInfo.getTableName();
|
||||
final Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
|
||||
int count = 0;
|
||||
try {
|
||||
final String sql = getSql(SqlMethod.DELETE, tableName, StrUtil.EMPTY, StrUtil.EMPTY, whereSql, null, paramNameValuePairs);
|
||||
count = Db.use(dataSource()).execute(sql);
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过ID查询数据(忽略逻辑删除)
|
||||
*
|
||||
* @param id 主键id值
|
||||
* @param entityClass 返回的实体类型
|
||||
* @return {@link T }
|
||||
* @author nn200433
|
||||
*/
|
||||
public static <T> T selectById(Object id, Class<T> entityClass) {
|
||||
final TableInfo tableInfo = getTableInfo(entityClass);
|
||||
final String keyColumn = tableInfo.getKeyColumn();
|
||||
final QueryWrapper<T> wrapper = new QueryWrapper<T>().eq(keyColumn, id);
|
||||
final String tableName = tableInfo.getTableName();
|
||||
final String whereSql = wrapper.getCustomSqlSegment();
|
||||
final Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
|
||||
T result = null;
|
||||
try {
|
||||
final String sql = getSql(SqlMethod.SELECT_BY_MAP, tableName, StrUtil.EMPTY, tableInfo.getAllSqlSelect(), whereSql, null, paramNameValuePairs);
|
||||
result = Db.use(dataSource()).query(sql, new BeanHandler<T>(entityClass));
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表(忽略逻辑删除)
|
||||
*
|
||||
* <p>
|
||||
* 请使用{@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}构造wrapper。
|
||||
* <br/>
|
||||
* 举例:new LambdaQueryWrapper<SmsTemplate>(Entity.class).eq(Entity::getCode, "2");
|
||||
* </p>
|
||||
*
|
||||
* @param wrapper 包装物
|
||||
* @return {@link List }<{@link T }>
|
||||
* @author nn200433
|
||||
*/
|
||||
public static <T> List<T> selectList(AbstractWrapper<T, ?, ?> wrapper) {
|
||||
List<T> resultList = new ArrayList<T>();
|
||||
final Class<T> entityClass = (Class<T>) getEntityClass(wrapper);
|
||||
final TableInfo tableInfo = getTableInfo(entityClass);
|
||||
final String whereSql = wrapper.getCustomSqlSegment();
|
||||
final String columnSql = wrapper.getSqlSelect();
|
||||
final String tableName = tableInfo.getTableName();
|
||||
final Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
|
||||
try {
|
||||
final String sql = getSql(SqlMethod.SELECT_LIST, tableName, StrUtil.EMPTY, StrUtil.blankToDefault(columnSql, tableInfo.getAllSqlSelect()), whereSql, null, paramNameValuePairs);
|
||||
resultList = Db.use(dataSource()).query(sql, entityClass);
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新(忽略逻辑删除)
|
||||
*
|
||||
* @param entity 实体类
|
||||
* @return int 影响行数
|
||||
* @author nn200433
|
||||
*/
|
||||
public static <T> int update(T entity) {
|
||||
return update(entity, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过条件更新数据(忽略逻辑删除)
|
||||
* <p>
|
||||
* 请使用{@link com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper}构造wrapper
|
||||
* </p>
|
||||
*
|
||||
* @param entity 实体类
|
||||
* @param wrapper 包装物
|
||||
* @return int 影响行数
|
||||
* @author nn200433
|
||||
*/
|
||||
public static <T> int update(T entity, AbstractWrapper<T, ?, ?> wrapper) {
|
||||
final TableInfo tableInfo = getTableInfo(entity.getClass());
|
||||
final String tableName = tableInfo.getTableName();
|
||||
if (null == wrapper) {
|
||||
final String keyProperty = tableInfo.getKeyProperty();
|
||||
Assert.notBlank(keyProperty, "{} 未设置 @TableId 注解!", keyProperty);
|
||||
wrapper = new UpdateWrapper<T>(entity).eq(keyProperty, ReflectUtil.getFieldValue(entity, keyProperty));
|
||||
}
|
||||
final String whereSql = wrapper.getCustomSqlSegment();
|
||||
final Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
|
||||
int count = 0;
|
||||
try {
|
||||
final String sql = getSql(SqlMethod.UPDATE, tableName, StrUtil.EMPTY, tableInfo.getAllSqlSet(Boolean.TRUE, StrUtil.EMPTY), whereSql, BeanUtil.beanToMap(entity), paramNameValuePairs);
|
||||
count = Db.use(dataSource()).execute(sql);
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sql
|
||||
*
|
||||
* @param method SQL方法
|
||||
* @param tableName 数据库表名称
|
||||
* @param firstSql firtSql
|
||||
* @param columnSql 字段sql
|
||||
* @param whereSql where条件sql
|
||||
* @param setMap set参数
|
||||
* @param paramNameValuePairs where条件值
|
||||
* @return {@link String }
|
||||
* @author nn200433
|
||||
*/
|
||||
private static String getSql(SqlMethod method, String tableName, String firstSql, String columnSql, String whereSql,
|
||||
Map<String, Object> setMap, Map<String, Object> paramNameValuePairs) {
|
||||
final Map<String, Object> paramMap = new HashMap<String, Object>(paramNameValuePairs.size());
|
||||
for (final Map.Entry<String, Object> entry : paramNameValuePairs.entrySet()) {
|
||||
paramMap.put(PARAM_KEY_PRE + entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (CollUtil.isNotEmpty(setMap)) {
|
||||
paramMap.putAll(setMap);
|
||||
}
|
||||
// 害人不浅,包了这么标签
|
||||
String originalSql = null;
|
||||
switch (method) {
|
||||
case DELETE:
|
||||
originalSql = String.format(method.getSql(), tableName, whereSql, StrUtil.EMPTY);
|
||||
break;
|
||||
case UPDATE:
|
||||
columnSql = StrUtil.addPrefixIfNot(columnSql, UPDATE_SET_PREFIX + StrUtil.SPACE);
|
||||
columnSql = StrUtil.removeSuffix(columnSql, StrUtil.COMMA);
|
||||
originalSql = String.format(method.getSql(), tableName, columnSql, whereSql, StrUtil.EMPTY);
|
||||
break;
|
||||
case SELECT_LIST:
|
||||
// 判断个锤子OrderBy注解,麻烦
|
||||
originalSql = String.format(method.getSql(), firstSql, columnSql, tableName, whereSql, StrUtil.EMPTY, StrUtil.EMPTY);
|
||||
break;
|
||||
case SELECT_BY_MAP:
|
||||
// 垃圾 SELECT_BY_ID ,限制那么多
|
||||
originalSql = String.format(method.getSql(), columnSql, tableName, whereSql, StrUtil.EMPTY);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
final String preSql = StrUtil.trim(HtmlUtil.cleanHtmlTag(originalSql));
|
||||
return resolveParams(preSql, paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从wrapper中尝试获取实体类型
|
||||
*
|
||||
* @param queryWrapper 条件构造器
|
||||
* @param <T> 实体类型
|
||||
* @return 实体类型
|
||||
*/
|
||||
private static <T> Class<T> getEntityClass(AbstractWrapper<T, ?, ?> queryWrapper) {
|
||||
Class<T> entityClass = queryWrapper.getEntityClass();
|
||||
if (entityClass == null) {
|
||||
T entity = queryWrapper.getEntity();
|
||||
if (entity != null) {
|
||||
entityClass = (Class<T>) entity.getClass();
|
||||
}
|
||||
}
|
||||
Assert.notNull(entityClass, "error: can not get entityClass from wrapper");
|
||||
return entityClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表信息,获取不到报错提示
|
||||
*
|
||||
* @param entityClass 实体类
|
||||
* @param <T> 实体类型
|
||||
* @return 对应表信息
|
||||
*/
|
||||
private static <T> TableInfo getTableInfo(Class<T> entityClass) {
|
||||
return Optional.ofNullable(TableInfoHelper.getTableInfo(entityClass))
|
||||
.orElseThrow(() -> ExceptionUtils.mpe("error: can not find TableInfo from Class: \"%s\".", entityClass.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析参数
|
||||
*
|
||||
* @param str 包含要替换的占位符的值
|
||||
* @param param 参数
|
||||
* @return @return {@link String }
|
||||
* @author nn200433
|
||||
*/
|
||||
private static String resolveParams(String str, Map<String, Object> param) {
|
||||
return PLACEHOLDER_HELPER.replacePlaceholders(str, key -> {
|
||||
// final String v = MapUtil.(param, key);
|
||||
final Object v = param.get(key);
|
||||
if (null == v) {
|
||||
return "null";
|
||||
}
|
||||
final Class<?> clz = v.getClass();
|
||||
if (clz.equals(Date.class)) {
|
||||
final Date vd = (Date) v;
|
||||
return StrUtil.wrap(DateUtil.format(vd, DatePattern.NORM_DATETIME_FORMAT), SQL_PREFIX_AND_SUFFIX);
|
||||
} else if (clz.equals(LocalDateTime.class)) {
|
||||
final LocalDateTime vd = (LocalDateTime) v;
|
||||
return StrUtil.wrap(DateUtil.formatLocalDateTime(vd), SQL_PREFIX_AND_SUFFIX);
|
||||
}
|
||||
return StrUtil.wrap(Convert.toStr(v), SQL_PREFIX_AND_SUFFIX);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源
|
||||
*
|
||||
* @return {@link DataSource }
|
||||
* @author nn200433
|
||||
*/
|
||||
private static DataSource dataSource() {
|
||||
return SpringUtil.getBean(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-redis</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-spring</artifactId>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package cn.bootx.platform.common.spring.configuration;
|
||||
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
@@ -39,7 +40,7 @@ public class AsyncExecutorConfiguration implements AsyncConfigurer {
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
|
||||
|
||||
log.error("异步方法中发生异常,方法:{},参数:{},异常:{}", method.getName(), JsonUtil.toJsonStr(objects),
|
||||
log.error("异步方法中发生异常,方法:{},参数:{},异常:{}", method.getName(), JSONUtil.toJsonStr(objects),
|
||||
throwable.getMessage());
|
||||
log.error("详细异常信息", throwable);
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-common</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-swagger</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 组件忽略注解
|
||||
* 忽略租户数据隔离注解
|
||||
* @author xxm
|
||||
* @since 2024/6/25
|
||||
*/
|
||||
|
@@ -13,7 +13,7 @@ import java.time.LocalDateTime;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class BaseResult {
|
||||
public class BaseResult{
|
||||
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
@@ -8,7 +8,7 @@ import lombok.experimental.UtilityClass;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* json工具类, 基于hutool的进行封装,
|
||||
* json工具类, 基于hutool的进行封装, 仅用于与业务系统交互使用, 为了保持与SDK
|
||||
* 对java8的LocalDateTime时间格式进行转换, 但无法处理LocalDate, LocalTime格式, 需要使用JacksonUtil进行处理
|
||||
* @author xxm
|
||||
* @since 2024/6/28
|
||||
@@ -17,32 +17,6 @@ import java.util.Collection;
|
||||
public class JsonUtil {
|
||||
private final JSONConfig JSON_CONFIG = JSONConfig.create().setDateFormat(DatePattern.NORM_DATETIME_PATTERN);
|
||||
|
||||
/**
|
||||
* 转换为实体
|
||||
*/
|
||||
public <T> T toBean(String json, Class<T> clazz){
|
||||
JSONObject jsonObject = new JSONObject(json, JSON_CONFIG);
|
||||
return JSONUtil.toBean(jsonObject, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为实体
|
||||
*/
|
||||
public <T> T toBean(String json, TypeReference<T> reference){
|
||||
JSON parse = JSONUtil.parse(json, JSON_CONFIG);
|
||||
return parse.toBean(reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为实体
|
||||
*/
|
||||
public <T> T toBean(String json, TypeReference<T> reference, boolean ignoreError){
|
||||
JSONConfig jsonConfig = JSONConfig.create()
|
||||
.setDateFormat(DatePattern.NORM_DATETIME_PATTERN)
|
||||
.setIgnoreError(ignoreError);
|
||||
JSON parse = JSONUtil.parse(json, jsonConfig);
|
||||
return parse.toBean(reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为字符串
|
||||
@@ -61,9 +35,10 @@ public class JsonUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON字符串转JSONObject对象
|
||||
* 转换为实体, 仅供处理验签时使用, 其他场景不要使用
|
||||
*/
|
||||
public JSONObject parseObj(String jsonStr){
|
||||
return JSONUtil.parseObj(jsonStr, JSON_CONFIG);
|
||||
public <T> T toBean(String json, TypeReference<T> reference) {
|
||||
JSON parse = JSONUtil.parse(json, JSON_CONFIG);
|
||||
return parse.toBean(reference);
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
<modules>
|
||||
<module>service-baseapi</module>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-service</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>service-baseapi</artifactId>
|
||||
|
@@ -0,0 +1,97 @@
|
||||
package cn.bootx.platform.baseapi.controller.protocol;
|
||||
|
||||
import cn.bootx.platform.baseapi.param.protocol.UserProtocolParam;
|
||||
import cn.bootx.platform.baseapi.param.protocol.UserProtocolQuery;
|
||||
import cn.bootx.platform.baseapi.result.protocol.UserProtocolResult;
|
||||
import cn.bootx.platform.baseapi.service.protocol.UserProtocolService;
|
||||
import cn.bootx.platform.core.annotation.IgnoreAuth;
|
||||
import cn.bootx.platform.core.annotation.RequestGroup;
|
||||
import cn.bootx.platform.core.annotation.RequestPath;
|
||||
import cn.bootx.platform.core.rest.Res;
|
||||
import cn.bootx.platform.core.rest.param.PageParam;
|
||||
import cn.bootx.platform.core.rest.result.PageResult;
|
||||
import cn.bootx.platform.core.rest.result.Result;
|
||||
import cn.bootx.platform.core.validation.ValidationGroup;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 用户协议
|
||||
* @author xxm
|
||||
* @since 2025/5/9
|
||||
*/
|
||||
@Validated
|
||||
@Tag(name = "用户协议")
|
||||
@RequestGroup(groupCode = "UserProtocol", groupName = "用户协议", moduleCode = "baseapi" )
|
||||
@RestController
|
||||
@RequestMapping("/user/protocol")
|
||||
@RequiredArgsConstructor
|
||||
public class UserProtocolController {
|
||||
private final UserProtocolService userProtocolService;
|
||||
|
||||
@RequestPath("分页")
|
||||
@Operation(summary = "分页")
|
||||
@GetMapping("/page")
|
||||
public Result<PageResult<UserProtocolResult>> page(PageParam pageParam, UserProtocolQuery query){
|
||||
return Res.ok(userProtocolService.page(pageParam, query));
|
||||
}
|
||||
|
||||
@RequestPath("新增")
|
||||
@Operation(summary = "新增")
|
||||
@PostMapping("/add")
|
||||
public Result<Void> add(@RequestBody @Validated(ValidationGroup.add.class) UserProtocolParam param){
|
||||
userProtocolService.add(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@RequestPath("修改")
|
||||
@Operation(summary = "修改")
|
||||
@PostMapping("/update")
|
||||
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) UserProtocolParam param){
|
||||
userProtocolService.update(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@RequestPath("删除")
|
||||
@Operation(summary = "删除")
|
||||
@PostMapping("/delete")
|
||||
public Result<Void> delete(@NotNull(message = "主键不可为空") Long id){
|
||||
userProtocolService.delete(id);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@RequestPath("查询")
|
||||
@Operation(summary = "查询")
|
||||
@GetMapping("/findById")
|
||||
public Result<UserProtocolResult> findById(@NotNull(message = "主键不可为空") Long id){
|
||||
return Res.ok(userProtocolService.findById(id));
|
||||
}
|
||||
|
||||
@IgnoreAuth
|
||||
@Operation(summary = "查询默认协议")
|
||||
@GetMapping("/findDefault")
|
||||
public Result<UserProtocolResult> findDefault(@NotNull(message = "协议类型不可为空") String type){
|
||||
return Res.ok(userProtocolService.findDefault(type));
|
||||
}
|
||||
|
||||
@RequestPath("设置默认")
|
||||
@Operation(summary = "设置默认")
|
||||
@PostMapping("/setDefault")
|
||||
public Result<Void> setDefault(@NotNull(message = "主键不可为空") Long id){
|
||||
userProtocolService.setDefault(id);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@RequestPath("取消默认")
|
||||
@Operation(summary = "取消默认")
|
||||
@PostMapping("/cancelDefault")
|
||||
public Result<Void> cancelDefault(@NotNull(message = "主键不可为空") Long id){
|
||||
userProtocolService.cancelDefault(id);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package cn.bootx.platform.baseapi.convert.protocol;
|
||||
|
||||
import cn.bootx.platform.baseapi.entity.protocol.UserProtocol;
|
||||
import cn.bootx.platform.baseapi.param.protocol.UserProtocolParam;
|
||||
import cn.bootx.platform.baseapi.result.protocol.UserProtocolResult;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
*用户协议管理
|
||||
* @author xxm
|
||||
* @since 2025/5/9
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserProtocolConvert {
|
||||
UserProtocolConvert CONVERT = Mappers.getMapper(UserProtocolConvert.class);
|
||||
|
||||
UserProtocolResult toResult(UserProtocol userProtocol);
|
||||
|
||||
UserProtocol toEntity(UserProtocolParam param);
|
||||
}
|
@@ -2,6 +2,7 @@ package cn.bootx.platform.baseapi.dao.dict;
|
||||
|
||||
import cn.bootx.platform.baseapi.entity.dict.DictionaryItem;
|
||||
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
|
||||
import cn.bootx.platform.common.mybatisplus.base.MpRealDelEntity;
|
||||
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
|
||||
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
|
||||
import cn.bootx.platform.core.rest.param.PageParam;
|
||||
@@ -63,7 +64,11 @@ public class DictionaryItemManager extends BaseManager<DictionaryItemMapper, Dic
|
||||
}
|
||||
|
||||
public void updateDictCode(Long dictId, String dictCode) {
|
||||
lambdaUpdate().set(DictionaryItem::getDictCode, dictCode).eq(DictionaryItem::getDictId, dictId).update();
|
||||
lambdaUpdate()
|
||||
.set(DictionaryItem::getDictCode, dictCode)
|
||||
.eq(DictionaryItem::getDictId, dictId)
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
}
|
||||
|
||||
public List<DictionaryItem> findAllByEnable(boolean enable) {
|
||||
|
@@ -30,21 +30,21 @@ public class SystemParamManager extends BaseManager<SystemParamMapper, SystemPar
|
||||
* 根据键名获取键值
|
||||
*/
|
||||
public Optional<SystemParameter> findByKey(String key) {
|
||||
return this.findByField(SystemParameter::getKey, key);
|
||||
return this.findByField(SystemParameter::getParamKey, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* key重复检查
|
||||
*/
|
||||
public boolean existsByKey(String key) {
|
||||
return existedByField(SystemParameter::getKey, key);
|
||||
return existedByField(SystemParameter::getParamKey, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* key重复检查
|
||||
*/
|
||||
public boolean existsByKey(String key, Long id) {
|
||||
return existedByField(SystemParameter::getKey, key, id);
|
||||
return existedByField(SystemParameter::getParamKey, key, id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +54,7 @@ public class SystemParamManager extends BaseManager<SystemParamMapper, SystemPar
|
||||
Page<SystemParameter> mpPage = MpUtil.getMpPage(pageParam);
|
||||
return lambdaQuery().orderByDesc(MpIdEntity::getId)
|
||||
.like(StrUtil.isNotBlank(param.getName()), SystemParameter::getName, param.getName())
|
||||
.like(StrUtil.isNotBlank(param.getKey()), SystemParameter::getKey, param.getKey())
|
||||
.like(StrUtil.isNotBlank(param.getKey()), SystemParameter::getParamKey, param.getKey())
|
||||
.page(mpPage);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,79 @@
|
||||
package cn.bootx.platform.baseapi.dao.protocol;
|
||||
|
||||
import cn.bootx.platform.baseapi.entity.protocol.UserProtocol;
|
||||
import cn.bootx.platform.baseapi.param.protocol.UserProtocolQuery;
|
||||
import cn.bootx.platform.common.mybatisplus.base.MpRealDelEntity;
|
||||
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
|
||||
import cn.bootx.platform.common.mybatisplus.query.generator.QueryGenerator;
|
||||
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
|
||||
import cn.bootx.platform.core.rest.param.PageParam;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户协议管理
|
||||
* @author xxm
|
||||
* @since 2025/5/9
|
||||
*/
|
||||
@Slf4j
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class UserProtocolManager extends BaseManager<UserProtocolMapper, UserProtocol> {
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
public Page<UserProtocol> page(PageParam pageParam, UserProtocolQuery query){
|
||||
Page<UserProtocol> mpPage = MpUtil.getMpPage(pageParam, UserProtocol.class);
|
||||
QueryWrapper<UserProtocol> generator = QueryGenerator.generator(query);
|
||||
return this.page(mpPage,generator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类查询默认协议
|
||||
*/
|
||||
public Optional<UserProtocol> findDefault(String type){
|
||||
return this.lambdaQuery()
|
||||
.eq(UserProtocol::getType,type)
|
||||
.eq(UserProtocol::getDefaultProtocol,true)
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除默认协议
|
||||
*/
|
||||
public void clearDefault(String type){
|
||||
this.lambdaUpdate()
|
||||
.eq(UserProtocol::getType,type)
|
||||
.set(UserProtocol::getDefaultProtocol,false)
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认协议
|
||||
*/
|
||||
public void setDefault(Long id){
|
||||
this.lambdaUpdate()
|
||||
.eq(UserProtocol::getId,id)
|
||||
.set(UserProtocol::getDefaultProtocol,true)
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消默认协议
|
||||
*/
|
||||
public void cancelDefault(Long id){
|
||||
this.lambdaUpdate()
|
||||
.eq(UserProtocol::getId,id)
|
||||
.set(UserProtocol::getDefaultProtocol,false)
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package cn.bootx.platform.baseapi.dao.protocol;
|
||||
|
||||
import cn.bootx.platform.baseapi.entity.protocol.UserProtocol;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author xxm
|
||||
* @since 2025/5/9
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserProtocolMapper extends MPJBaseMapper<UserProtocol> {
|
||||
}
|
@@ -28,10 +28,10 @@ public class SystemParameter extends MpBaseEntity implements ToResult<SystemPara
|
||||
private String name;
|
||||
|
||||
/** 参数键名 */
|
||||
private String key;
|
||||
private String paramKey;
|
||||
|
||||
/** 参数值 */
|
||||
private String value;
|
||||
private String paramValue;
|
||||
|
||||
/** 参数类型 */
|
||||
private String type;
|
||||
|
@@ -0,0 +1,47 @@
|
||||
package cn.bootx.platform.baseapi.entity.protocol;
|
||||
|
||||
import cn.bootx.platform.baseapi.convert.protocol.UserProtocolConvert;
|
||||
import cn.bootx.platform.baseapi.param.protocol.UserProtocolParam;
|
||||
import cn.bootx.platform.baseapi.result.protocol.UserProtocolResult;
|
||||
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
|
||||
import cn.bootx.platform.common.mybatisplus.function.ToResult;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 用户协议管理
|
||||
* @author xxm
|
||||
* @since 2025/5/9
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName("base_user_protocol")
|
||||
public class UserProtocol extends MpBaseEntity implements ToResult<UserProtocolResult> {
|
||||
|
||||
/** 名称 */
|
||||
private String name;
|
||||
|
||||
/** 显示名称 */
|
||||
private String showName;
|
||||
|
||||
/** 类型 */
|
||||
private String type;
|
||||
|
||||
/** 默认协议 */
|
||||
private Boolean defaultProtocol;
|
||||
|
||||
/** 内容 */
|
||||
private String content;
|
||||
|
||||
@Override
|
||||
public UserProtocolResult toResult() {
|
||||
return UserProtocolConvert.CONVERT.toResult(this);
|
||||
}
|
||||
|
||||
public static UserProtocol init(UserProtocolParam param) {
|
||||
return UserProtocolConvert.CONVERT.toEntity(param);
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package cn.bootx.platform.baseapi.param.protocol;
|
||||
|
||||
import cn.bootx.platform.core.validation.ValidationGroup;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Null;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 用户协议管理
|
||||
* @author xxm
|
||||
* @since 2025/5/9
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "用户协议管理")
|
||||
public class UserProtocolParam {
|
||||
|
||||
@Null(message = "Id需要为空", groups = ValidationGroup.add.class)
|
||||
@NotNull(message = "Id不可为空", groups = ValidationGroup.edit.class)
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
/** 名称 */
|
||||
@NotBlank(message = "名称不能为空")
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
/** 显示名称 */
|
||||
@NotBlank(message = "显示名称不能为空")
|
||||
@Schema(description = "显示名称")
|
||||
private String showName;
|
||||
|
||||
/** 类型 */
|
||||
@NotBlank(message = "类型不能为空")
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
/** 内容 */
|
||||
@Schema(description = "内容")
|
||||
private String content;
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package cn.bootx.platform.baseapi.param.protocol;
|
||||
|
||||
import cn.bootx.platform.common.mybatisplus.query.entity.SortParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 用户协议
|
||||
* @author xxm
|
||||
* @since 2025/5/11
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "用户协议")
|
||||
public class UserProtocolQuery extends SortParam {
|
||||
|
||||
|
||||
/** 名称 */
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
/** 显示名称 */
|
||||
@Schema(description = "显示名称")
|
||||
private String showName;
|
||||
|
||||
/** 类型 */
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package cn.bootx.platform.baseapi.result.protocol;
|
||||
|
||||
import cn.bootx.platform.core.result.BaseResult;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 用户协议管理
|
||||
* @author xxm
|
||||
* @since 2025/5/9
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "用户协议管理")
|
||||
public class UserProtocolResult extends BaseResult {
|
||||
|
||||
/** 名称 */
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
/** 显示名称 */
|
||||
@Schema(description = "显示名称")
|
||||
private String showName;
|
||||
|
||||
/** 类型 */
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
/** 默认协议 */
|
||||
@Schema(description = "默认协议")
|
||||
private Boolean defaultProtocol;
|
||||
|
||||
/** 内容 */
|
||||
@Schema(description = "内容")
|
||||
private String content;
|
||||
}
|
@@ -36,7 +36,7 @@ public class SystemParamService {
|
||||
*/
|
||||
public void add(SystemParameterParam param) {
|
||||
SystemParameter systemParameter = SystemParameter.init(param);
|
||||
if (systemParamManager.existsByKey(systemParameter.getKey())) {
|
||||
if (systemParamManager.existsByKey(systemParameter.getParamKey())) {
|
||||
throw new BizException("key重复");
|
||||
}
|
||||
// 默认非内置
|
||||
@@ -81,7 +81,7 @@ public class SystemParamService {
|
||||
if (Objects.equals(param.getEnable(), false)) {
|
||||
throw new BizException("该参数已停用");
|
||||
}
|
||||
return param.getValue();
|
||||
return param.getParamValue();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,107 @@
|
||||
package cn.bootx.platform.baseapi.service.protocol;
|
||||
|
||||
import cn.bootx.platform.baseapi.dao.protocol.UserProtocolManager;
|
||||
import cn.bootx.platform.baseapi.entity.protocol.UserProtocol;
|
||||
import cn.bootx.platform.baseapi.param.protocol.UserProtocolParam;
|
||||
import cn.bootx.platform.baseapi.param.protocol.UserProtocolQuery;
|
||||
import cn.bootx.platform.baseapi.result.protocol.UserProtocolResult;
|
||||
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
|
||||
import cn.bootx.platform.core.exception.BizException;
|
||||
import cn.bootx.platform.core.exception.DataNotExistException;
|
||||
import cn.bootx.platform.core.rest.param.PageParam;
|
||||
import cn.bootx.platform.core.rest.result.PageResult;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.copier.CopyOptions;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 用户协议管理服务
|
||||
* @author xxm
|
||||
* @since 2025/5/9
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserProtocolService {
|
||||
private final UserProtocolManager userProtocolManager;
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
public PageResult<UserProtocolResult> page(PageParam pageParam, UserProtocolQuery query){
|
||||
return MpUtil.toPageResult(userProtocolManager.page(pageParam,query));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
public void add(UserProtocolParam param){
|
||||
var userProtocol = UserProtocol.init(param);
|
||||
userProtocolManager.save(userProtocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*/
|
||||
public void update(UserProtocolParam param){
|
||||
var userProtocol = userProtocolManager.findById(param.getId())
|
||||
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
|
||||
BeanUtil.copyProperties(param, userProtocol, CopyOptions.create().ignoreNullValue());
|
||||
userProtocolManager.updateById(userProtocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
public void delete(Long id){
|
||||
// 默认不可被删除
|
||||
var userProtocol = userProtocolManager.findById(id)
|
||||
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
|
||||
if (userProtocol.getDefaultProtocol()){
|
||||
throw new BizException("默认协议不可删除");
|
||||
}
|
||||
userProtocolManager.deleteById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询
|
||||
*/
|
||||
public UserProtocolResult findById(Long id){
|
||||
return userProtocolManager.findById(id)
|
||||
.map(UserProtocol::toResult)
|
||||
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类查询默认协议
|
||||
*/
|
||||
public UserProtocolResult findDefault(String type){
|
||||
return userProtocolManager.findDefault(type)
|
||||
.map(UserProtocol::toResult)
|
||||
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认协议
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void setDefault(Long id){
|
||||
var userProtocol = userProtocolManager.findById(id)
|
||||
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
|
||||
userProtocolManager.clearDefault(userProtocol.getType());
|
||||
userProtocolManager.setDefault(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消默认协议
|
||||
*/
|
||||
public void cancelDefault(Long id){
|
||||
var userProtocol = userProtocolManager.findById(id)
|
||||
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
|
||||
userProtocolManager.cancelDefault(id);
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-service</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>service-iam</artifactId>
|
||||
|
@@ -33,8 +33,10 @@ import java.util.Objects;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
public class PasswordLoginHandler implements AbstractAuthentication {
|
||||
|
||||
@Getter
|
||||
private final String ACCOUNT_PARAMETER = "account";
|
||||
|
||||
@Getter
|
||||
private final String PASSWORD_PARAMETER = "password";
|
||||
|
||||
@Resource
|
||||
|
@@ -70,7 +70,7 @@ public class UserAdminController {
|
||||
@RequestPath("重置密码")
|
||||
@Operation(summary = "重置密码")
|
||||
@PostMapping("/restartPassword")
|
||||
@OperateLog(title = "重置密码", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
|
||||
@OperateLog(title = "重置密码", businessType = OperateLog.BusinessType.UPDATE)
|
||||
public Result<Void> restartPassword(@RequestBody @Validated RestartPwdParam param) {
|
||||
userAdminService.restartPassword(param.getUserId(), param.getNewPassword());
|
||||
return Res.ok();
|
||||
@@ -79,7 +79,7 @@ public class UserAdminController {
|
||||
@RequestPath("批量重置密码")
|
||||
@Operation(summary = "批量重置密码")
|
||||
@PostMapping("/restartPasswordBatch")
|
||||
@OperateLog(title = "批量重置密码", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
|
||||
@OperateLog(title = "批量重置密码", businessType = OperateLog.BusinessType.UPDATE)
|
||||
public Result<Void> restartPasswordBatch(@RequestBody @Validated RestartPwdBatchParam param) {
|
||||
userAdminService.restartPasswordBatch(param.getUserIds(), param.getNewPassword());
|
||||
return Res.ok();
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package cn.bootx.platform.iam.dao.user;
|
||||
|
||||
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
|
||||
import cn.bootx.platform.common.mybatisplus.base.MpRealDelEntity;
|
||||
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
|
||||
import cn.bootx.platform.common.mybatisplus.query.generator.QueryGenerator;
|
||||
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
|
||||
@@ -74,7 +75,11 @@ public class UserInfoManager extends BaseManager<UserInfoMapper, UserInfo> {
|
||||
}
|
||||
|
||||
public void setUpStatus(Long userId, String status) {
|
||||
lambdaUpdate().eq(MpIdEntity::getId, userId).set(UserInfo::getStatus, status).update();
|
||||
lambdaUpdate()
|
||||
.eq(MpIdEntity::getId, userId)
|
||||
.set(UserInfo::getStatus, status)
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,6 +91,7 @@ public class UserInfoManager extends BaseManager<UserInfoMapper, UserInfo> {
|
||||
.set(UserInfo::getStatus, status)
|
||||
.set(UserInfo::getLastModifiedTime, LocalDateTime.now())
|
||||
.set(UserInfo::getLastModifier, SecurityUtil.getUserIdOrDefaultId())
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
}
|
||||
|
||||
@@ -98,6 +104,7 @@ public class UserInfoManager extends BaseManager<UserInfoMapper, UserInfo> {
|
||||
.set(UserInfo::getPassword, password)
|
||||
.set(UserInfo::getLastModifiedTime, LocalDateTime.now())
|
||||
.set(UserInfo::getLastModifier, SecurityUtil.getUserIdOrDefaultId())
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package cn.bootx.platform.iam.entity.permission;
|
||||
|
||||
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
|
||||
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
|
||||
import cn.bootx.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.bootx.platform.iam.convert.permission.PermPathConvert;
|
||||
import cn.bootx.platform.iam.result.permission.PermPathResult;
|
||||
@@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName("iam_perm_path")
|
||||
public class PermPath extends MpCreateEntity implements ToResult<PermPathResult> {
|
||||
public class PermPath extends MpIdEntity implements ToResult<PermPathResult> {
|
||||
|
||||
/** 上级编码 */
|
||||
private String parentCode;
|
||||
|
@@ -2,10 +2,8 @@ package cn.bootx.platform.iam.handler;
|
||||
|
||||
import cn.bootx.platform.core.entity.UserDetail;
|
||||
import cn.bootx.platform.iam.code.UserStatusEnum;
|
||||
import cn.bootx.platform.iam.service.user.UserAdminService;
|
||||
import cn.bootx.platform.starter.auth.authentication.UserInfoStatusCheck;
|
||||
import cn.bootx.platform.starter.auth.configuration.AuthProperties;
|
||||
import cn.bootx.platform.starter.auth.entity.AuthClient;
|
||||
import cn.bootx.platform.starter.auth.entity.AuthInfoResult;
|
||||
import cn.bootx.platform.starter.auth.entity.LoginAuthContext;
|
||||
import cn.bootx.platform.starter.auth.exception.LoginFailureException;
|
||||
@@ -24,7 +22,6 @@ import java.util.Objects;
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class UserInfoStatusCheckImpl implements UserInfoStatusCheck {
|
||||
private final UserAdminService userAdminService;
|
||||
|
||||
/**
|
||||
* 检查用户状态
|
||||
@@ -34,7 +31,6 @@ public class UserInfoStatusCheckImpl implements UserInfoStatusCheck {
|
||||
@Override
|
||||
public void check(AuthInfoResult authInfoResult, LoginAuthContext context) {
|
||||
UserDetail userDetail = authInfoResult.getUserDetail();
|
||||
AuthClient authClient = context.getAuthClient();
|
||||
AuthProperties authProperties = context.getAuthProperties();
|
||||
// 判断是否开启了超级管理员
|
||||
if (!authProperties.isEnableAdmin() && userDetail.isAdmin()) {
|
||||
|
@@ -64,12 +64,12 @@ public class PermPathSyncService {
|
||||
List<String> clientCodes = bootxConfigProperties.getClientCodes();
|
||||
// 查询数据中的数据并转换为请求信息列表
|
||||
for (String clientCode : clientCodes) {
|
||||
sync(clientCode);
|
||||
this.sync(clientCode);
|
||||
}
|
||||
} else {
|
||||
// 分模块模式同步
|
||||
String clientCode = clientCodeService.getClientCode();
|
||||
sync(clientCode);
|
||||
this.sync(clientCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public class PermPathSyncService {
|
||||
// 查询是否包含所有
|
||||
.filter(o -> o.isAllClient() || CollUtil.contains(o.getClientCodes(),clientCode))
|
||||
.toList();
|
||||
List<PermPath> permPaths = permPathManager.findAllByLeafAndClient(true,clientCode);
|
||||
List<PermPath> permPaths = permPathManager.findAllByLeafAndClient(true, clientCode);
|
||||
var requestPathMap = requestPathBos.stream()
|
||||
.collect(Collectors.toMap(o -> o.getPath() + ":" + o.getMethod(), Function.identity()));
|
||||
var permPathMap = permPaths.stream()
|
||||
@@ -95,8 +95,8 @@ public class PermPathSyncService {
|
||||
// 需要更新的数据
|
||||
List<PermPath> updateData = this.getUpdateData(requestPathMap, permPathMap);
|
||||
|
||||
// 保存新增的
|
||||
addData.forEach(o -> o.setClientCode(clientCode));
|
||||
// 保存新增的, ID 由不会变更的终端编码+请求方式+请求路径进行
|
||||
addData.forEach(o -> o.setClientCode(clientCode).setId(this.genPathId(o.getClientCode()+o.getMethod()+o.getPath())));
|
||||
permPathManager.saveAll(addData);
|
||||
// 更新存在的
|
||||
permPathManager.updateAllById(updateData);
|
||||
@@ -161,8 +161,7 @@ public class PermPathSyncService {
|
||||
.map(permPathMap::get)
|
||||
.peek(o -> {
|
||||
RequestPathBo requestPathBo = requestPathMap.get(o.getPath() + ":" + o.getMethod());
|
||||
o.setName(requestPathBo.getName())
|
||||
.setParentCode(requestPathBo.getGroupCode());
|
||||
o.setName(requestPathBo.getName()).setParentCode(requestPathBo.getGroupCode());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@@ -227,7 +226,7 @@ public class PermPathSyncService {
|
||||
.stream()
|
||||
.filter(pathKey -> {
|
||||
HandlerMethod handlerMethod = map.get(pathKey);
|
||||
return Objects.nonNull(handlerMethod.getMethodAnnotation(cn.bootx.platform.core.annotation.RequestPath.class))
|
||||
return Objects.nonNull(handlerMethod.getMethodAnnotation(RequestPath.class))
|
||||
&&Objects.nonNull(handlerMethod.getBeanType().getAnnotation(RequestGroup.class));
|
||||
}).toList();
|
||||
|
||||
@@ -303,7 +302,7 @@ public class PermPathSyncService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 给分组和模块生成ID, 防止每次更新ID都会发生变化
|
||||
* 给分组/模块/请求路径资源生成ID, 防止每次更新ID都会发生变化
|
||||
*/
|
||||
private long genPathId(String str) {
|
||||
String s = SecureUtil.sha256(str);
|
||||
|
@@ -148,7 +148,7 @@ public class RoleQueryService {
|
||||
// 获取关联的角色和子角色
|
||||
List<RoleResult> unfold = TreeBuildUtil.unfold(tree, RoleResult::getChildren).stream()
|
||||
.filter(role -> roleIds.contains(role.getId()))
|
||||
.collect(Collectors.toList());;
|
||||
.collect(Collectors.toList());
|
||||
var list = new ArrayList<>(unfold);
|
||||
// 将子孙级别的角色移除, 只保留根角色
|
||||
for (var out : unfold) {
|
||||
|
@@ -152,7 +152,7 @@ public class RoleCodeService {
|
||||
// 权限码列表
|
||||
List<PermCode> permCodes = allPermCodes.stream()
|
||||
.filter(PermCode::isLeaf)
|
||||
.toList();;
|
||||
.toList();
|
||||
// 如果有有上级角色, 显示上级角色已分配的权限
|
||||
if (Objects.nonNull(role.getPid())){
|
||||
List<Long> codeIds = roleCodeManager.findAllByRole(role.getPid())
|
||||
|
@@ -47,7 +47,6 @@ public class UserAdminService {
|
||||
|
||||
private final AuthProperties authProperties;
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
@@ -143,8 +142,7 @@ public class UserAdminService {
|
||||
|
||||
UserInfo userInfo = userInfoManager.findById(userId).orElseThrow(UserInfoNotExistsException::new);
|
||||
// 新密码进行加密
|
||||
newPassword = BCrypt.hashpw(newPassword);
|
||||
userInfo.setPassword(newPassword);
|
||||
userInfo.setPassword(BCrypt.hashpw(newPassword));
|
||||
userInfoManager.updateById(userInfo);
|
||||
}
|
||||
|
||||
@@ -154,8 +152,7 @@ public class UserAdminService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void restartPasswordBatch(List<Long> userIds, String newPassword){
|
||||
// 新密码进行加密
|
||||
String password = BCrypt.hashpw(newPassword);
|
||||
userInfoManager.restartPasswordBatch(userIds,password);
|
||||
userInfoManager.restartPasswordBatch(userIds,BCrypt.hashpw(newPassword));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -98,13 +98,16 @@ public class UserInfoService {
|
||||
public void updatePassword(String password, String newPassword) {
|
||||
UserInfo userInfo = userInfoManager.findById(SecurityUtil.getUserId())
|
||||
.orElseThrow(UserInfoNotExistsException::new);
|
||||
|
||||
UserInfo update=new UserInfo();
|
||||
update.setId(userInfo.getId());
|
||||
update.setVersion(userInfo.getVersion());
|
||||
// 判断原密码是否正确
|
||||
if (!BCrypt.checkpw(password, userInfo.getPassword())) {
|
||||
throw new BizException("旧密码错误");
|
||||
}
|
||||
userInfo.setPassword(newPassword);
|
||||
userInfoManager.updateById(userInfo);
|
||||
newPassword=BCrypt.hashpw(newPassword,BCrypt.gensalt());
|
||||
update.setPassword(newPassword);
|
||||
userInfoManager.updateById(update);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
<modules>
|
||||
<module>starter-auth</module>
|
||||
@@ -27,7 +27,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-core</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
<!-- lombok -->
|
||||
<dependency>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-starter</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>starter-audit-log</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-starter</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>starter-auth</artifactId>
|
||||
|
@@ -5,6 +5,7 @@ import cn.bootx.platform.starter.auth.entity.LoginAuthContext;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -19,11 +20,12 @@ public interface AbstractAuthentication {
|
||||
* 获取终端编码
|
||||
*/
|
||||
String getLoginType();
|
||||
|
||||
/**
|
||||
* 获取用户状态检查接口的实现类
|
||||
*/
|
||||
default UserInfoStatusCheck getUserInfoStatusCheck() {
|
||||
return SpringUtil.getBean(UserInfoStatusCheck.class);
|
||||
default List<UserInfoStatusCheck> getUserInfoStatusCheck() {
|
||||
return SpringUtil.getBeansOfType(UserInfoStatusCheck.class).values().stream().toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +64,9 @@ public interface AbstractAuthentication {
|
||||
// 添加用户信息到上下文中
|
||||
context.setUserDetail(authInfoResult.getUserDetail());
|
||||
// 检查用户信息和状态
|
||||
this.getUserInfoStatusCheck().check(authInfoResult, context);
|
||||
for (var userInfoStatusCheck : this.getUserInfoStatusCheck()) {
|
||||
userInfoStatusCheck.check(authInfoResult, context);
|
||||
}
|
||||
// 认证后处理
|
||||
this.authenticationAfter(authInfoResult, context);
|
||||
return authInfoResult;
|
||||
|
@@ -11,7 +11,7 @@ import cn.bootx.platform.starter.auth.entity.LoginAuthContext;
|
||||
public interface UserInfoStatusCheck {
|
||||
|
||||
/**
|
||||
*
|
||||
* 检查用户状态
|
||||
* @param authInfoResult 认证返回结果
|
||||
* @param context 登录认证上下文
|
||||
*/
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-starter</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>starter-cache</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-starter</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>starter-delay-queue</artifactId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-starter</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>starter-file</artifactId>
|
||||
@@ -25,11 +25,10 @@
|
||||
<version>${x-file-storage.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qcloud</groupId>
|
||||
<artifactId>cos_api</artifactId>
|
||||
<version>${qcloud.version}</version>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>${aws-s3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库 -->
|
||||
<dependency>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
|
@@ -1,8 +1,12 @@
|
||||
package cn.bootx.platform.starter.file.code;
|
||||
|
||||
import cn.bootx.platform.core.exception.BizException;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 存储平台类型
|
||||
*
|
||||
@@ -12,22 +16,32 @@ import lombok.RequiredArgsConstructor;
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum FilePlatformTypeEnum {
|
||||
LOCAL("local"),
|
||||
FTP("ftp"),
|
||||
SFTP("sftp"),
|
||||
WEB_DAV("web_dav"),
|
||||
AMAZON("amazon"),
|
||||
MINIO("minio"),
|
||||
ALI("ali"),
|
||||
HUAWEI("huawei"),
|
||||
TENCENT("tencent"),
|
||||
BAIDU("baidu"),
|
||||
UPYUN("upyun"),
|
||||
QINIU("qiniu"),
|
||||
GOOGLE_CLOUD("google_cloud"),
|
||||
FAST_DFS("fast_dfs"),
|
||||
AZURE("azure");
|
||||
LOCAL("local",false),
|
||||
FTP("ftp",false),
|
||||
SFTP("sftp",false),
|
||||
WEB_DAV("web_dav",false),
|
||||
// S3 存储, 现在系统只支持这一种方式
|
||||
AMAZON_S3("amazon-s3",true),
|
||||
MINIO("minio",true),
|
||||
ALI("ali",true),
|
||||
HUAWEI("huawei",true),
|
||||
TENCENT("tencent",true),
|
||||
BAIDU("baidu",true),
|
||||
UPYUN("upyun",true),
|
||||
QINIU("qiniu",true),
|
||||
GOOGLE_CLOUD("google_cloud",true),
|
||||
FAST_DFS("fast_dfs",true),
|
||||
AZURE("azure",true);
|
||||
|
||||
private final String code;
|
||||
/** 前端直传 */
|
||||
private final boolean frontendUpload;
|
||||
|
||||
public static FilePlatformTypeEnum findByCode(String code){
|
||||
return Arrays.stream(values())
|
||||
.filter(e -> Objects.equals(e.code, code))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BizException("不支持的类型"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,30 +0,0 @@
|
||||
package cn.bootx.platform.starter.file.configuration;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 文件上传配置
|
||||
*
|
||||
* @author xxm
|
||||
* @since 2022/1/14
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@ConfigurationProperties(prefix = "bootx-platform.starter.file-upload")
|
||||
public class FileUploadProperties {
|
||||
|
||||
/**
|
||||
* 文件访问转发地址(当前后端服务地址或被代理后的地址), 流量会经过后端服务的转发
|
||||
*/
|
||||
private String forwardServerUrl = "http://127.0.0.1:9999";
|
||||
|
||||
/**
|
||||
* 处理为 / 结尾
|
||||
*/
|
||||
public String getForwardServerUrl() {
|
||||
return StrUtil.removeSuffix(forwardServerUrl, "/");
|
||||
}
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
package cn.bootx.platform.starter.file.configuration;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@ConfigurationProperties(prefix = "bootx-platform.starter.oss")
|
||||
/***
|
||||
* oss配置
|
||||
*/
|
||||
public class OssProperties {
|
||||
|
||||
private String filePath;
|
||||
|
||||
|
||||
}
|
@@ -8,20 +8,30 @@ import cn.bootx.platform.core.rest.Res;
|
||||
import cn.bootx.platform.core.rest.param.PageParam;
|
||||
import cn.bootx.platform.core.rest.result.PageResult;
|
||||
import cn.bootx.platform.core.rest.result.Result;
|
||||
import cn.bootx.platform.starter.file.param.FileUploadRequestParams;
|
||||
import cn.bootx.platform.starter.file.param.UploadFileInfoParam;
|
||||
import cn.bootx.platform.starter.file.param.UploadFileQuery;
|
||||
import cn.bootx.platform.starter.file.result.FileUploadParamsResult;
|
||||
import cn.bootx.platform.starter.file.result.UploadFileResult;
|
||||
import cn.bootx.platform.starter.file.service.FileUploadService;
|
||||
import org.dromara.core.trans.anno.TransMethodResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.core.trans.anno.TransMethodResult;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
@@ -52,14 +62,14 @@ public class FIleUpLoadController {
|
||||
@Operation(summary = "获取单条详情")
|
||||
@GetMapping("/findById")
|
||||
public Result<UploadFileResult> findById(@NotNull(message = "主键不可为空") Long id) {
|
||||
return Res.ok(uploadService.findById(id));
|
||||
return Res.ok(uploadService.findByUrl(id));
|
||||
}
|
||||
|
||||
@IgnoreAuth
|
||||
@Operation(summary = "根据URL获取单条详情")
|
||||
@GetMapping("/findByUrl")
|
||||
public Result<UploadFileResult> findById(@NotBlank(message = "文件URL不可为空") String url) {
|
||||
return Res.ok(uploadService.findById(url));
|
||||
return Res.ok(uploadService.findByUrl(url));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除")
|
||||
@@ -71,31 +81,37 @@ public class FIleUpLoadController {
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@IgnoreAuth
|
||||
@Operation(summary = "上传")
|
||||
@PostMapping("/upload")
|
||||
public Result<UploadFileResult> local(@RequestPart MultipartFile file, String fileName) {
|
||||
return Res.ok(uploadService.upload(file, fileName));
|
||||
@IgnoreAuth(login = true)
|
||||
@Operation(summary = "下载文件")
|
||||
@GetMapping("/downloadByServer")
|
||||
public ResponseEntity<byte[]> downloadByServer(String attachName) {
|
||||
var bytes = uploadService.downloadAndCheck(attachName);
|
||||
// 设置header信息
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
headers.setContentDispositionFormData("attachment", URLEncoder.encode(attachName, StandardCharsets.UTF_8));
|
||||
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@IgnoreAuth(login = true)
|
||||
@Operation(summary = "获取前端直传参数")
|
||||
@PostMapping("/getUploadParams")
|
||||
public Result<FileUploadParamsResult> getUploadParams(@RequestBody @Valid FileUploadRequestParams params) {
|
||||
return Res.ok(uploadService.getUploadParams(params));
|
||||
}
|
||||
|
||||
@IgnoreAuth(login = true)
|
||||
@Operation(summary = "前端直传文件信息保存")
|
||||
@PostMapping("/saveFileInfo")
|
||||
public Result<Void> saveFileInfo(@RequestBody @Valid UploadFileInfoParam param) {
|
||||
uploadService.saveFileInfo(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@IgnoreAuth
|
||||
@Operation(summary = "获取文件预览地址前缀(流量会经过后端)")
|
||||
@GetMapping("/forward/getFilePreviewUrlPrefix")
|
||||
public Result<String> getFilePreviewUrlPrefix() {
|
||||
return Res.ok(uploadService.getServerFilePreviewUrlPrefix());
|
||||
}
|
||||
|
||||
@IgnoreAuth
|
||||
@Operation(summary = "预览文件(流量会经过后端)")
|
||||
@GetMapping("/forward/preview/{id}")
|
||||
public void preview(@PathVariable Long id, HttpServletResponse response) {
|
||||
uploadService.preview(id, response);
|
||||
}
|
||||
|
||||
@IgnoreAuth
|
||||
@Operation(summary = "下载文件(流量会经过后端)")
|
||||
@GetMapping("/forward/download/{id}")
|
||||
public ResponseEntity<byte[]> download(@PathVariable Long id) {
|
||||
return uploadService.download(id);
|
||||
@Operation(summary = "前端直传文件预览/下载(不需要登录)")
|
||||
@GetMapping("/download/{attachName}")
|
||||
public void download(HttpServletResponse httpServletResponse, @Schema(description = "附件名") @PathVariable("attachName") String attachName) {
|
||||
uploadService.ossDownload(httpServletResponse, attachName);
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.x.file.storage.core.FileStorageService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -24,6 +25,7 @@ import java.util.List;
|
||||
* @since 2024/8/12
|
||||
*/
|
||||
@Validated
|
||||
@Deprecated
|
||||
@RequestGroup(groupCode = "FilePlatform", groupName = "文件存储平台管理", moduleCode = "starter")
|
||||
@Tag(name = "文件存储平台")
|
||||
@RestController
|
||||
@@ -31,6 +33,7 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class FilePlatformController {
|
||||
private final FilePlatformService filePlatformService;
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
@IgnoreAuth
|
||||
@Operation(summary = "列表")
|
||||
@@ -63,4 +66,12 @@ public class FilePlatformController {
|
||||
filePlatformService.setDefault(id);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@IgnoreAuth
|
||||
@Operation(summary = "获取当前默认的文件上传存储平台")
|
||||
@GetMapping("/getDefaultUpload")
|
||||
public Result<String> getDefault(){
|
||||
String defaultPlatform = fileStorageService.getProperties().getDefaultPlatform();
|
||||
return Res.ok(defaultPlatform);
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
package cn.bootx.platform.starter.file.controller;
|
||||
|
||||
import cn.bootx.platform.starter.file.param.FileUploadRequestParams;
|
||||
import cn.bootx.platform.starter.file.result.FileUploadParamsResult;
|
||||
import cn.bootx.platform.starter.file.service.OssService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@Tag(name = "对象存储")
|
||||
@RequestMapping("/oss")
|
||||
@AllArgsConstructor
|
||||
public class OssController {
|
||||
|
||||
|
||||
private final OssService ossService;
|
||||
|
||||
@Operation(summary = "获取上传参数")
|
||||
@PostMapping("/getUploadParams")
|
||||
public FileUploadParamsResult getUploadParams(@RequestBody @Valid FileUploadRequestParams params) {
|
||||
return ossService.getUploadParams(params);
|
||||
}
|
||||
|
||||
@Operation(summary = "文件预览/文件下载")
|
||||
@GetMapping("/download/{attachName}")
|
||||
public void download(HttpServletResponse httpServletResponse, @Schema(description = "附件名") @PathVariable("attachName") String attachName) {
|
||||
ossService.download(httpServletResponse, attachName);
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package cn.bootx.platform.starter.file.convert;
|
||||
|
||||
import cn.bootx.platform.starter.file.entity.UploadFileInfo;
|
||||
import cn.bootx.platform.starter.file.param.UploadFileInfoParam;
|
||||
import cn.bootx.platform.starter.file.result.UploadFileResult;
|
||||
import org.dromara.x.file.storage.core.FileInfo;
|
||||
import org.mapstruct.Mapper;
|
||||
@@ -19,6 +20,8 @@ public interface FileConvert {
|
||||
|
||||
UploadFileInfo convert(FileInfo in);
|
||||
|
||||
UploadFileInfo convert(UploadFileInfoParam in);
|
||||
|
||||
FileInfo toFileInfo(UploadFileInfo in);
|
||||
|
||||
UploadFileResult toResult(FileInfo in);
|
||||
|
@@ -6,13 +6,23 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
*
|
||||
* 支付平台配置
|
||||
* @author xxm
|
||||
* @since 2024/8/12
|
||||
*/
|
||||
@Slf4j
|
||||
@Deprecated
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class FilePlatformManager extends BaseManager<FilePlatformMapper, FilePlatform> {
|
||||
|
||||
/**
|
||||
* 根据平台类型查询
|
||||
*/
|
||||
public Optional<FilePlatform> findByType(String platform) {
|
||||
return findByField(FilePlatform::getType, platform);
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,8 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 文件存储平台
|
||||
* @author xxm
|
||||
@@ -41,12 +43,20 @@ public class FilePlatform extends MpRealDelEntity implements ToResult<FilePlatfo
|
||||
private boolean defaultPlatform;
|
||||
|
||||
/** 访问地址 */
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String url;
|
||||
|
||||
/** 前端直传 */
|
||||
private Boolean frontendUpload;
|
||||
|
||||
public String getUrl() {
|
||||
return StrUtil.removeSuffix(url, "/");
|
||||
}
|
||||
|
||||
public Boolean getFrontendUpload() {
|
||||
return Objects.equals(frontendUpload,true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换
|
||||
*/
|
||||
|
@@ -27,7 +27,7 @@ import java.time.LocalDateTime;
|
||||
public class UploadFileInfo extends MpIdEntity implements ToResult<UploadFileResult> {
|
||||
|
||||
/**
|
||||
* 文件访问地址
|
||||
* 文件访问地址(存储在S3中的唯一名称)
|
||||
*/
|
||||
private String url;
|
||||
|
||||
|
@@ -8,8 +8,6 @@ import org.dromara.x.file.storage.core.FileInfo;
|
||||
import org.dromara.x.file.storage.core.recorder.DefaultFileRecorder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* x.file.storage 文件上传信息储存
|
||||
* @author xxm
|
||||
@@ -27,7 +25,6 @@ public class FileDetailRecordHandler extends DefaultFileRecorder {
|
||||
@Override
|
||||
public boolean save(FileInfo fileInfo) {
|
||||
UploadFileInfo uploadFileInfo = UploadFileInfo.init(fileInfo);
|
||||
uploadFileInfo.setCreateTime(LocalDateTime.now());
|
||||
uploadFileManager.save(uploadFileInfo);
|
||||
fileInfo.setId(String.valueOf(uploadFileInfo.getId()));
|
||||
return true;
|
||||
|
@@ -20,8 +20,11 @@ public class FilePlatformParam {
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "平台地址不得为空")
|
||||
@Schema(description = "平台地址")
|
||||
private String url;
|
||||
|
||||
/** 前端直传 */
|
||||
@Schema(description = "前端直传")
|
||||
private Boolean frontendUpload;
|
||||
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ public class FileUploadRequestParams {
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "媒体类型 MEDIA_TYPE")
|
||||
@NotBlank(message = "媒体类型不可为")
|
||||
@NotBlank(message = "媒体类型不可为为空")
|
||||
private String mediaType;
|
||||
|
||||
@Schema(description = "文件大小")
|
||||
|
@@ -0,0 +1,106 @@
|
||||
package cn.bootx.platform.starter.file.param;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 上传文件信息
|
||||
* @author xxm
|
||||
* @since 2025/5/15
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "上传文件信息")
|
||||
public class UploadFileInfoParam {
|
||||
|
||||
/**
|
||||
* 文件访问地址(名称)
|
||||
*/
|
||||
@Schema(description = "文件访问地址")
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 文件大小,单位字节
|
||||
*/
|
||||
@Schema(description = "文件大小,单位字节")
|
||||
private Long size;
|
||||
|
||||
/**
|
||||
* 文件名称
|
||||
*/
|
||||
@Schema(description = "文件名称")
|
||||
private String filename;
|
||||
|
||||
/**
|
||||
* 原始文件名
|
||||
*/
|
||||
@Schema(description = "原始文件名")
|
||||
private String originalFilename;
|
||||
|
||||
/**
|
||||
* 基础存储路径
|
||||
*/
|
||||
@Schema(description = "基础存储路径")
|
||||
private String basePath;
|
||||
|
||||
/**
|
||||
* 存储路径
|
||||
*/
|
||||
@Schema(description = "存储路径")
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 文件扩展名
|
||||
*/
|
||||
@Schema(description = "文件扩展名")
|
||||
private String ext;
|
||||
|
||||
/**
|
||||
* MIME 类型
|
||||
*/
|
||||
@Schema(description = "MIME 类型")
|
||||
private String contentType;
|
||||
|
||||
/**
|
||||
* 存储平台
|
||||
*/
|
||||
@Schema(description = "存储平台")
|
||||
private String platform;
|
||||
|
||||
/**
|
||||
* 缩略图访问路径
|
||||
*/
|
||||
@Schema(description = "缩略图访问路径")
|
||||
private String thUrl;
|
||||
|
||||
/**
|
||||
* 缩略图名称
|
||||
*/
|
||||
@Schema(description = "缩略图名称")
|
||||
private String thFilename;
|
||||
|
||||
/**
|
||||
* 缩略图大小,单位字节
|
||||
*/
|
||||
@Schema(description = "缩略图大小,单位字节")
|
||||
private Long thSize;
|
||||
|
||||
/**
|
||||
* 缩略图 MIME 类型
|
||||
*/
|
||||
@Schema(description = "缩略图 MIME 类型")
|
||||
private String thContentType;
|
||||
|
||||
/**
|
||||
* 文件所属对象id
|
||||
*/
|
||||
@Schema(description = "文件所属对象id")
|
||||
private String objectId;
|
||||
|
||||
/**
|
||||
* 文件所属对象类型,例如用户头像,评价图片
|
||||
*/
|
||||
@Schema(description = "文件所属对象类型,例如用户头像,评价图片")
|
||||
private String objectType;
|
||||
}
|
@@ -36,4 +36,9 @@ public class FilePlatformResult extends BaseResult {
|
||||
/** 访问地址 */
|
||||
@Schema(description = "访问地址")
|
||||
private String url;
|
||||
|
||||
/** 前端直传 */
|
||||
@Schema(description = "前端直传")
|
||||
private Boolean frontendUpload;
|
||||
|
||||
}
|
||||
|
@@ -2,16 +2,24 @@ package cn.bootx.platform.starter.file.result;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文件上传参数
|
||||
*/
|
||||
@Schema(description = "文件上传参数")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class FileUploadParamsResult {
|
||||
|
||||
@Schema(description = "文件上传地址")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "上传后文件名称")
|
||||
private String attachName;
|
||||
|
||||
@Schema(description = "文件上传请求头,上传时放在请求头里")
|
||||
private Map<String,String> headers;
|
||||
|
||||
|
@@ -22,12 +22,12 @@ import java.util.List;
|
||||
* @since 2024/8/12
|
||||
*/
|
||||
@Slf4j
|
||||
@Deprecated
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FilePlatformService {
|
||||
private final FilePlatformManager filePlatformManager;
|
||||
|
||||
|
||||
/**
|
||||
* 获取全部存储平台
|
||||
*/
|
||||
@@ -62,12 +62,14 @@ public class FilePlatformService {
|
||||
.eq(FilePlatform::isDefaultPlatform, true)
|
||||
.set(FilePlatform::getLastModifiedTime, LocalDateTime.now())
|
||||
.set(MpRealDelEntity::getLastModifier, SecurityUtil.getUserIdOrDefaultId())
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
filePlatformManager.lambdaUpdate()
|
||||
.eq(FilePlatform::getId, id)
|
||||
.set(FilePlatform::getLastModifiedTime, LocalDateTime.now())
|
||||
.set(MpRealDelEntity::getLastModifier, SecurityUtil.getUserIdOrDefaultId())
|
||||
.set(FilePlatform::isDefaultPlatform, true)
|
||||
.setIncrBy(MpRealDelEntity::getVersion, 1)
|
||||
.update();
|
||||
}
|
||||
}
|
||||
|
@@ -1,39 +1,37 @@
|
||||
package cn.bootx.platform.starter.file.service;
|
||||
|
||||
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
|
||||
import cn.bootx.platform.core.exception.BizException;
|
||||
import cn.bootx.platform.core.exception.DataNotExistException;
|
||||
import cn.bootx.platform.core.rest.param.PageParam;
|
||||
import cn.bootx.platform.core.rest.result.PageResult;
|
||||
import cn.bootx.platform.starter.file.configuration.FileUploadProperties;
|
||||
import cn.bootx.platform.starter.file.convert.FileConvert;
|
||||
import cn.bootx.platform.starter.file.dao.UploadFileManager;
|
||||
import cn.bootx.platform.starter.file.entity.UploadFileInfo;
|
||||
import cn.bootx.platform.starter.file.param.FileUploadRequestParams;
|
||||
import cn.bootx.platform.starter.file.param.UploadFileInfoParam;
|
||||
import cn.bootx.platform.starter.file.param.UploadFileQuery;
|
||||
import cn.bootx.platform.starter.file.result.FileUploadParamsResult;
|
||||
import cn.bootx.platform.starter.file.result.UploadFileResult;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.x.file.storage.core.FileInfo;
|
||||
import org.dromara.x.file.storage.core.FileStorageService;
|
||||
import org.dromara.x.file.storage.core.upload.UploadPretreatment;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.dromara.x.file.storage.core.constant.Constant;
|
||||
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlResult;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文件上传管理类
|
||||
@@ -47,108 +45,134 @@ import java.time.LocalDateTime;
|
||||
public class FileUploadService {
|
||||
private final UploadFileManager uploadFileManager;
|
||||
private final FileStorageService fileStorageService;
|
||||
private final FileUploadProperties fileUploadProperties;
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*/
|
||||
public PageResult<UploadFileResult> page(PageParam pageParam, UploadFileQuery param) {
|
||||
return MpUtil.toPageResult(uploadFileManager.page(pageParam,param));
|
||||
return MpUtil.toPageResult(uploadFileManager.page(pageParam, param));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单条详情
|
||||
*/
|
||||
public UploadFileResult findById(Long id){
|
||||
public UploadFileResult findByUrl(Long id) {
|
||||
return uploadFileManager.findById(id)
|
||||
.map(UploadFileInfo::toResult)
|
||||
.orElseThrow(DataNotExistException::new);
|
||||
.orElseThrow(DataNotExistException::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据URL获取单条详情
|
||||
*/
|
||||
public UploadFileResult findById(String url){
|
||||
public UploadFileResult findByUrl(String url) {
|
||||
return uploadFileManager.findByUrl(url)
|
||||
.map(UploadFileInfo::toResult)
|
||||
.orElseThrow(DataNotExistException::new);
|
||||
.orElseThrow(DataNotExistException::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件删除
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id){
|
||||
public void delete(Long id) {
|
||||
UploadFileInfo uploadFileInfo = uploadFileManager.findById(id)
|
||||
.orElseThrow(DataNotExistException::new);
|
||||
fileStorageService.delete(FileConvert.CONVERT.toFileInfo(uploadFileInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
* @param file 文件
|
||||
* @param fileName 文件名称
|
||||
* 根据文件名称下载
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public UploadFileResult upload(@RequestPart MultipartFile file, String fileName) {
|
||||
UploadPretreatment uploadPretreatment = fileStorageService.of(file);
|
||||
if (StrUtil.isNotBlank(fileName)){
|
||||
uploadPretreatment.setOriginalFilename(fileName);
|
||||
public byte[] download(String attachName) {
|
||||
String ossFileUrl = this.getOssFileUrl(attachName);
|
||||
return HttpUtil.downloadBytes(ossFileUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据存储的文件对象下载
|
||||
*/
|
||||
public byte[] download(FileInfo fileInfo) {
|
||||
return HttpUtil.downloadBytes(this.getOssFileUrl(fileInfo.getUrl()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件下载, 首先判断文件是否存在, 用在严格的场景中
|
||||
*/
|
||||
public byte[] downloadAndCheck(String attachName) {
|
||||
FileInfo fileInfo = fileStorageService.getFileInfoByUrl(attachName);
|
||||
if (fileInfo == null){
|
||||
throw new DataNotExistException("文件不存在");
|
||||
}
|
||||
// 按年月日进行分目录
|
||||
uploadPretreatment.setPath(LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy/MM/dd/"));
|
||||
|
||||
FileInfo upload = uploadPretreatment.upload();
|
||||
return FileConvert.CONVERT.toResult(upload);
|
||||
return HttpUtil.downloadBytes(this.getOssFileUrl(attachName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件预览
|
||||
* TODO url需要使用URl
|
||||
* 获取直传上传参数
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void preview(Long id, HttpServletResponse response) {
|
||||
FileInfo info = fileStorageService.getFileInfoByUrl(String.valueOf(id));
|
||||
if (info == null){
|
||||
log.warn("文件不存在");
|
||||
return;
|
||||
public FileUploadParamsResult getUploadParams(FileUploadRequestParams params) {
|
||||
String fileExtension = FileUtil.extName(params.getFileName());
|
||||
String attachName = IdUtil.fastSimpleUUID() + (StringUtils.isNotBlank(fileExtension) ? "." + fileExtension : "");
|
||||
GeneratePresignedUrlResult uploadResult = fileStorageService
|
||||
.generatePresignedUrl()
|
||||
.setFilename(attachName) // 设置保存的文件名
|
||||
.setMethod(Constant.GeneratePresignedUrl.Method.PUT) // 签名方法
|
||||
.setExpiration(DateUtil.offsetMinute(new Date(), 10)) // 过期时间 10 分钟
|
||||
.putHeaders(Constant.Metadata.CONTENT_TYPE, params.getMediaType())
|
||||
.putHeaders(Constant.Metadata.CONTENT_LENGTH, String.valueOf(params.getFileSize()))
|
||||
.generatePresignedUrl();
|
||||
FileUploadParamsResult result = new FileUploadParamsResult();
|
||||
result.setUrl(uploadResult.getUrl())
|
||||
.setAttachName(attachName)
|
||||
.setHeaders(uploadResult.getHeaders());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前端直传的文件信息
|
||||
*/
|
||||
public void saveFileInfo(UploadFileInfoParam param) {
|
||||
UploadFileInfo uploadFileInfo = FileConvert.CONVERT.convert(param);
|
||||
// 扩展名
|
||||
String fileExtension = FileUtil.extName(uploadFileInfo.getOriginalFilename());
|
||||
uploadFileInfo.setExt(fileExtension);
|
||||
// 平台
|
||||
String defaultPlatform = fileStorageService.getProperties()
|
||||
.getDefaultPlatform();
|
||||
uploadFileInfo.setPlatform(defaultPlatform);
|
||||
uploadFileManager.save(uploadFileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取oss直传文件下载链接
|
||||
*/
|
||||
public String getOssFileUrl(String attachName) {
|
||||
// 系统中旧数据存储的地址是/开头, 为了兼容旧数据使用oss生成预签名链接的时候需要去掉
|
||||
// 新数据存储格式就没有/开头
|
||||
attachName = StrUtil.removePrefix(attachName, "/");
|
||||
GeneratePresignedUrlResult downloadResult = fileStorageService
|
||||
.generatePresignedUrl()
|
||||
.setFilename(attachName) // 文件名
|
||||
.setMethod(Constant.GeneratePresignedUrl.Method.GET) // 签名方法
|
||||
.setExpiration(DateUtil.offsetMinute(new Date(), 10)) // 过期时间 10 分钟
|
||||
.putResponseHeaders(
|
||||
Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=" + attachName)
|
||||
.generatePresignedUrl();
|
||||
return downloadResult.getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端直传文件下载/预览
|
||||
*/
|
||||
public void ossDownload(HttpServletResponse httpServletResponse, String attachName) {
|
||||
try {
|
||||
httpServletResponse.sendRedirect(this.getOssFileUrl(attachName));
|
||||
} catch (IOException e) {
|
||||
log.error("下载文件失败", e);
|
||||
httpServletResponse.setStatus(HttpStatus.NOT_FOUND.value());
|
||||
throw new BizException(e.getMessage());
|
||||
}
|
||||
byte[] bytes = fileStorageService.download(info).bytes();
|
||||
var is = new ByteArrayInputStream(bytes);
|
||||
// 获取响应输出流
|
||||
ServletOutputStream os = response.getOutputStream();
|
||||
IoUtil.copy(is, os);
|
||||
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, info.getContentType());
|
||||
IoUtil.close(is);
|
||||
IoUtil.close(os);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件下载
|
||||
*/
|
||||
@SneakyThrows
|
||||
public ResponseEntity<byte[]> download(Long id) {
|
||||
FileInfo fileInfo = fileStorageService.getFileInfoByUrl(String.valueOf(id));
|
||||
byte[] bytes = fileStorageService.download(fileInfo).bytes();
|
||||
// 设置header信息
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
String fileName = fileInfo.getOriginalFilename();
|
||||
headers.setContentDispositionFormData("attachment", URLEncoder.encode(fileName, StandardCharsets.UTF_8));
|
||||
return new ResponseEntity<>(bytes,headers,HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件访问转发地址(当前后端服务地址或被代理后的地址), 流量会经过后端服务的转发
|
||||
*/
|
||||
public String getServerFilePreviewUrlPrefix() {
|
||||
return this.getForwardServerUrl() + "/file/preview/";
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件访问转发地址(当前后端服务地址或被代理后的地址), 流量会经过后端服务的转发
|
||||
*/
|
||||
private String getForwardServerUrl() {
|
||||
return fileUploadProperties.getForwardServerUrl();
|
||||
}
|
||||
}
|
||||
|
@@ -1,88 +0,0 @@
|
||||
package cn.bootx.platform.starter.file.service;
|
||||
|
||||
import cn.bootx.platform.starter.file.configuration.OssProperties;
|
||||
import cn.bootx.platform.starter.file.param.FileUploadRequestParams;
|
||||
import cn.bootx.platform.starter.file.result.FileUploadParamsResult;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.x.file.storage.core.FileStorageService;
|
||||
import org.dromara.x.file.storage.core.constant.Constant;
|
||||
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlResult;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author yinxucun
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class OssService {
|
||||
|
||||
private final OssProperties ossProperties;
|
||||
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
private static final Instant expirationTime = new Date().toInstant().atZone(ZoneId.of("Asia/Shanghai"))
|
||||
.plusMinutes(10).toInstant();
|
||||
|
||||
public OssService(OssProperties ossProperties, FileStorageService fileStorageService) {
|
||||
this.ossProperties = ossProperties;
|
||||
this.fileStorageService = fileStorageService;
|
||||
log.info("初始化OSS配置:{}", ossProperties);
|
||||
}
|
||||
|
||||
|
||||
public FileUploadParamsResult getUploadParams(FileUploadRequestParams params) {
|
||||
return this.createSignedUrlForStringPut(params);
|
||||
}
|
||||
|
||||
public void download(HttpServletResponse httpServletResponse, String attachName) {
|
||||
GeneratePresignedUrlResult downloadResult = fileStorageService
|
||||
.generatePresignedUrl()
|
||||
.setPath(ossProperties.getFilePath()) // 文件路径
|
||||
.setFilename(attachName) // 文件名,也可以换成缩略图的文件名
|
||||
.setMethod(Constant.GeneratePresignedUrl.Method.GET) // 签名方法
|
||||
.setExpiration(Date.from(expirationTime)) // 过期时间 10 分钟
|
||||
.putResponseHeaders(
|
||||
Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=" + attachName)
|
||||
.generatePresignedUrl();
|
||||
try {
|
||||
httpServletResponse.sendRedirect(downloadResult.getUrl());
|
||||
} catch (IOException e) {
|
||||
log.error("下载文件失败", e);
|
||||
httpServletResponse.setStatus(HttpStatus.NOT_FOUND.value());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private FileUploadParamsResult createSignedUrlForStringPut(FileUploadRequestParams params) {
|
||||
String fileExtension = FileUtil.extName(params.getFileName());
|
||||
String attachName = IdUtil.fastSimpleUUID() + (StringUtils.isNotBlank(fileExtension) ? "." + fileExtension : "");
|
||||
Date expirationTime = new Date(System.currentTimeMillis() + 600 * 1000);
|
||||
GeneratePresignedUrlResult uploadResult = fileStorageService
|
||||
.generatePresignedUrl()
|
||||
.setPath(ossProperties.getFilePath()) // 设置路径
|
||||
.setFilename(attachName) // 设置保存的文件名
|
||||
.setMethod(Constant.GeneratePresignedUrl.Method.PUT) // 签名方法
|
||||
.setExpiration(expirationTime) // 设置过期时间 10 分钟
|
||||
.putHeaders(Constant.Metadata.CONTENT_TYPE, params.getMediaType())
|
||||
.putHeaders(Constant.Metadata.CONTENT_LENGTH, String.valueOf(params.getFileSize()))
|
||||
.generatePresignedUrl();
|
||||
log.info("expirationTime:{},attachName:{}", DateUtil.format(expirationTime, "yyyy-MM-dd HH:mm:ss"), attachName);
|
||||
log.info("生成上传预签名 URL 结果:{}", uploadResult);
|
||||
FileUploadParamsResult result = new FileUploadParamsResult();
|
||||
result.setUrl(uploadResult.getUrl());
|
||||
result.setHeaders(uploadResult.getHeaders());
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package org.dromara.x.file.storage.core.platform;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.client.builder.AwsClientBuilder;
|
||||
import com.amazonaws.services.s3.AmazonS3;
|
||||
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.dromara.x.file.storage.core.FileStorageProperties.AmazonS3Config;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 重写 Amazon S3 存储平台的 Client 工厂, 支持 withPathStyleAccessEnabled 配置
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class AmazonS3FileStorageClientFactory implements FileStorageClientFactory<AmazonS3> {
|
||||
private String platform;
|
||||
private String accessKey;
|
||||
private String secretKey;
|
||||
private String region;
|
||||
private String endPoint;
|
||||
private Map<String, Object> attr;
|
||||
private volatile AmazonS3 client;
|
||||
|
||||
public AmazonS3FileStorageClientFactory(AmazonS3Config config) {
|
||||
platform = config.getPlatform();
|
||||
accessKey = config.getAccessKey();
|
||||
secretKey = config.getSecretKey();
|
||||
region = config.getRegion();
|
||||
endPoint = config.getEndPoint();
|
||||
attr = config.getAttr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmazonS3 getClient() {
|
||||
if (client == null) {
|
||||
synchronized (this) {
|
||||
if (client == null) {
|
||||
AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard()
|
||||
.withCredentials(
|
||||
new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)));
|
||||
if (StrUtil.isNotBlank(endPoint)) {
|
||||
builder.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, region));
|
||||
} else if (StrUtil.isNotBlank(region)) {
|
||||
builder.withRegion(region);
|
||||
}
|
||||
// 使用路径访问样式
|
||||
Boolean forcePathStyle = MapUtil.getBool(attr, "forcePathStyle", false);
|
||||
builder.withPathStyleAccessEnabled(forcePathStyle);
|
||||
client = builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (client != null) {
|
||||
client.shutdown();
|
||||
client = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform-starter</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>starter-quartz</artifactId>
|
||||
|
@@ -7,13 +7,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.3</version>
|
||||
<version>3.5.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>cn.bootx.platform</groupId>
|
||||
<artifactId>bootx-platform</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<description>基础脚手架服务</description>
|
||||
|
||||
@@ -25,18 +25,18 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<bootx-platform.version>3.0.0.beta5</bootx-platform.version>
|
||||
<bootx-platform.version>3.0.0</bootx-platform.version>
|
||||
<!-- 再高的的新版本与会knife4j 4.5冲突, 目前使用三方的knife4j依赖 -->
|
||||
<springdoc.version>2.7.0</springdoc.version>
|
||||
<hutool.version>5.8.31</hutool.version>
|
||||
<hutool.version>5.8.39</hutool.version>
|
||||
<bouncycastle.version>1.78.1</bouncycastle.version>
|
||||
<knife4j.version>4.6.0</knife4j.version>
|
||||
<mybatis-plus.version>3.5.9</mybatis-plus.version>
|
||||
<mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
|
||||
<mybatis-plus.version>3.5.12</mybatis-plus.version>
|
||||
<mybatis-plus-join.version>1.5.4</mybatis-plus-join.version>
|
||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<x-file-storage.version>2.2.1</x-file-storage.version>
|
||||
<minio.version>8.5.4</minio.version>
|
||||
<x-file-storage.version>2.3.0</x-file-storage.version>
|
||||
<aws-s3.version>1.12.429</aws-s3.version>
|
||||
<qcloud.version>5.6.137</qcloud.version>
|
||||
<sa-token.version>1.39.0</sa-token.version>
|
||||
<justauth.version>1.16.6</justauth.version>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.daxpay</groupId>
|
||||
<artifactId>daxpay-open-channel</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>daxpay-open-channel-alipay</artifactId>
|
||||
|
@@ -2,6 +2,7 @@ package org.dromara.daxpay.channel.alipay.entity.allocation;
|
||||
|
||||
import cn.bootx.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.dromara.daxpay.channel.alipay.convert.AlipayAllocReceiverConvert;
|
||||
import org.dromara.daxpay.channel.alipay.result.allocation.AlipayAllocReceiverResult;
|
||||
import org.dromara.daxpay.core.enums.AllocReceiverTypeEnum;
|
||||
@@ -71,7 +72,7 @@ public class AlipayAllocReceiver implements ToResult<AlipayAllocReceiverResult>
|
||||
* 转换为通道接收方
|
||||
*/
|
||||
public static AlipayAllocReceiver convertChannel(AllocReceiver receiver) {
|
||||
var leshuaAllocReceiver = JsonUtil.toBean(receiver.getExt(), AlipayAllocReceiver.class);
|
||||
var leshuaAllocReceiver = JSONUtil.toBean(receiver.getExt(), AlipayAllocReceiver.class);
|
||||
leshuaAllocReceiver.setId(receiver.getId())
|
||||
.setReceiverNo(receiver.getReceiverNo())
|
||||
.setReceiverName(receiver.getReceiverName())
|
||||
|
@@ -2,6 +2,7 @@ package org.dromara.daxpay.channel.alipay.entity.config;
|
||||
|
||||
import cn.bootx.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.dromara.daxpay.channel.alipay.code.AlipayCode;
|
||||
import org.dromara.daxpay.channel.alipay.convert.AlipayConfigConvert;
|
||||
import org.dromara.daxpay.channel.alipay.result.config.AlipayConfigResult;
|
||||
@@ -97,7 +98,7 @@ public class AliPayConfig implements ToResult<AlipayConfigResult> {
|
||||
* 从通道配置转换为支付宝配置
|
||||
*/
|
||||
public static AliPayConfig convertConfig(ChannelConfig channelConfig) {
|
||||
var config = JsonUtil.toBean(channelConfig.getExt(), AliPayConfig.class);
|
||||
var config = JSONUtil.toBean(channelConfig.getExt(), AliPayConfig.class);
|
||||
config.setId(channelConfig.getId())
|
||||
.setAliAppId(channelConfig.getOutAppId())
|
||||
.setAppId(channelConfig.getAppId())
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.dromara.daxpay.channel.alipay.service.payment.notice;
|
||||
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.dromara.daxpay.channel.alipay.code.AlipayCode;
|
||||
import org.dromara.daxpay.channel.alipay.result.notice.AlipayOrderChangedResult;
|
||||
import org.dromara.daxpay.core.enums.CallbackStatusEnum;
|
||||
@@ -57,7 +58,7 @@ public class AlipayTransferNoticeService {
|
||||
// 通过 biz_content 获取值
|
||||
try {
|
||||
String bizContent = map.get("biz_content");
|
||||
var response = JsonUtil.toBean(bizContent, AlipayOrderChangedResult.class);
|
||||
var response = JSONUtil.toBean(bizContent, AlipayOrderChangedResult.class);
|
||||
callbackInfo.setCallbackData(BeanUtil.beanToMap(response));
|
||||
this.resolveData(response);
|
||||
return "success";
|
||||
|
@@ -2,6 +2,7 @@ package org.dromara.daxpay.channel.alipay.strategy.merchant;
|
||||
|
||||
import cn.bootx.platform.core.exception.ValidationFailedException;
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.dromara.daxpay.channel.alipay.entity.config.AliPayConfig;
|
||||
import org.dromara.daxpay.channel.alipay.param.pay.AlipayParam;
|
||||
import org.dromara.daxpay.channel.alipay.service.payment.config.AlipayConfigService;
|
||||
@@ -49,7 +50,7 @@ public class AliPayStrategy extends AbsPayStrategy {
|
||||
// 支付宝参数验证
|
||||
String channelParam = this.getPayParam().getExtraParam();
|
||||
if (StrUtil.isNotBlank(channelParam)) {
|
||||
this.aliPayParam = JsonUtil.toBean(channelParam, AlipayParam.class);
|
||||
this.aliPayParam = JSONUtil.toBean(channelParam, AlipayParam.class);
|
||||
}
|
||||
else {
|
||||
this.aliPayParam = new AlipayParam();
|
||||
|
@@ -2,6 +2,7 @@ package org.dromara.daxpay.channel.alipay.strategy.sub;
|
||||
|
||||
import cn.bootx.platform.core.exception.ValidationFailedException;
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.dromara.daxpay.channel.alipay.entity.config.AliPayConfig;
|
||||
import org.dromara.daxpay.channel.alipay.param.pay.AlipayParam;
|
||||
import org.dromara.daxpay.channel.alipay.service.payment.config.AlipayConfigService;
|
||||
@@ -49,7 +50,7 @@ public class AlipaySubPayStrategy extends AbsPayStrategy {
|
||||
// 支付宝参数验证
|
||||
String channelParam = this.getPayParam().getExtraParam();
|
||||
if (StrUtil.isNotBlank(channelParam)) {
|
||||
this.aliPayParam = JsonUtil.toBean(channelParam, AlipayParam.class);
|
||||
this.aliPayParam = JSONUtil.toBean(channelParam, AlipayParam.class);
|
||||
}
|
||||
else {
|
||||
this.aliPayParam = new AlipayParam();
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.daxpay</groupId>
|
||||
<artifactId>daxpay-open-channel</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>daxpay-open-channel-union</artifactId>
|
||||
|
@@ -3,6 +3,7 @@ package org.dromara.daxpay.channel.union.entity.config;
|
||||
import cn.bootx.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.bootx.platform.core.annotation.BigField;
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.dromara.daxpay.channel.union.code.UnionPayCode;
|
||||
import org.dromara.daxpay.channel.union.convert.UnionPayConfigConvert;
|
||||
import org.dromara.daxpay.channel.union.result.UnionPayConfigResult;
|
||||
@@ -101,7 +102,7 @@ public class UnionPayConfig implements ToResult<UnionPayConfigResult> {
|
||||
* 从通道配置转换为支付宝配置
|
||||
*/
|
||||
public static UnionPayConfig convertConfig(ChannelConfig channelConfig) {
|
||||
UnionPayConfig config = JsonUtil.toBean(channelConfig.getExt(), UnionPayConfig.class);
|
||||
UnionPayConfig config = JSONUtil.toBean(channelConfig.getExt(), UnionPayConfig.class);
|
||||
config.setId(channelConfig.getId())
|
||||
.setUnionMachId(channelConfig.getOutMchNo())
|
||||
.setEnable(channelConfig.isEnable());
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.daxpay</groupId>
|
||||
<artifactId>daxpay-open-channel</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>daxpay-open-channel-wechat</artifactId>
|
||||
|
@@ -1,505 +0,0 @@
|
||||
package com.github.binarywang.wxpay.config;
|
||||
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.util.HttpProxyUtils;
|
||||
import com.github.binarywang.wxpay.util.ResourcesUtils;
|
||||
import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder;
|
||||
import com.github.binarywang.wxpay.v3.auth.*;
|
||||
import com.github.binarywang.wxpay.v3.util.PemUtils;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.ssl.SSLContexts;
|
||||
import org.dromara.daxpay.channel.wechat.code.WechatPayCode;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 微信支付配置
|
||||
*
|
||||
* @author Binary Wang (<a href="https://github.com/binarywang">...</a>)
|
||||
*/
|
||||
@Data
|
||||
@Slf4j
|
||||
@ToString(exclude = "verifier")
|
||||
@EqualsAndHashCode(exclude = "verifier")
|
||||
public class WxPayConfig {
|
||||
private static final String DEFAULT_PAY_BASE_URL = "https://api.mch.weixin.qq.com";
|
||||
private static final String PROBLEM_MSG = "证书文件【%s】有问题,请核实!";
|
||||
private static final String NOT_FOUND_MSG = "证书文件【%s】不存在,请核实!";
|
||||
|
||||
/**
|
||||
* 接口版本, 使用v2还是v3接口
|
||||
* @see WechatPayCode#API_V2
|
||||
*/
|
||||
private String apiVersion;
|
||||
|
||||
/**
|
||||
* 微信支付接口请求地址域名部分.
|
||||
*/
|
||||
private String payBaseUrl = DEFAULT_PAY_BASE_URL;
|
||||
|
||||
/**
|
||||
* http请求连接超时时间.
|
||||
*/
|
||||
private int httpConnectionTimeout = 5000;
|
||||
|
||||
/**
|
||||
* http请求数据读取等待时间.
|
||||
*/
|
||||
private int httpTimeout = 10000;
|
||||
|
||||
/**
|
||||
* 公众号appid.
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 服务商模式下的子商户公众账号ID.
|
||||
*/
|
||||
private String subAppId;
|
||||
/**
|
||||
* 商户号.
|
||||
*/
|
||||
private String mchId;
|
||||
/**
|
||||
* 商户密钥.
|
||||
*/
|
||||
private String mchKey;
|
||||
/**
|
||||
* 企业支付密钥.
|
||||
*/
|
||||
private String entPayKey;
|
||||
/**
|
||||
* 服务商模式下的子商户号.
|
||||
*/
|
||||
private String subMchId;
|
||||
/**
|
||||
* 微信支付异步回掉地址,通知url必须为直接可访问的url,不能携带参数.
|
||||
*/
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 交易类型.
|
||||
* <pre>
|
||||
* JSAPI--公众号支付
|
||||
* NATIVE--原生扫码支付
|
||||
* APP--app支付
|
||||
* </pre>
|
||||
*/
|
||||
private String tradeType;
|
||||
/**
|
||||
* 签名方式.
|
||||
* 有两种HMAC_SHA256 和MD5
|
||||
*
|
||||
* @see com.github.binarywang.wxpay.constant.WxPayConstants.SignType
|
||||
*/
|
||||
private String signType;
|
||||
private SSLContext sslContext;
|
||||
/**
|
||||
* p12证书base64编码
|
||||
*/
|
||||
private String keyString;
|
||||
/**
|
||||
* p12证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String keyPath;
|
||||
|
||||
/**
|
||||
* apiclient_key.pem证书base64编码
|
||||
*/
|
||||
private String privateKeyString;
|
||||
/**
|
||||
* apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String privateKeyPath;
|
||||
|
||||
/**
|
||||
* apiclient_cert.pem证书base64编码
|
||||
*/
|
||||
private String privateCertString;
|
||||
/**
|
||||
* apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String privateCertPath;
|
||||
|
||||
/**
|
||||
* apiclient_key.pem证书文件内容的字节数组.
|
||||
*/
|
||||
private byte[] privateKeyContent;
|
||||
|
||||
/**
|
||||
* apiclient_cert.pem证书文件内容的字节数组.
|
||||
*/
|
||||
private byte[] privateCertContent;
|
||||
|
||||
/**
|
||||
* 公钥ID
|
||||
*/
|
||||
private String publicKeyId;
|
||||
|
||||
/**
|
||||
* pub_key.pem证书base64编码
|
||||
*/
|
||||
private String publicKeyString;
|
||||
|
||||
/**
|
||||
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String publicKeyPath;
|
||||
|
||||
/**
|
||||
* pub_key.pem证书文件内容的字节数组.
|
||||
*/
|
||||
private byte[] publicKeyContent;
|
||||
/**
|
||||
* apiV3 秘钥值.
|
||||
*/
|
||||
private String apiV3Key;
|
||||
|
||||
/**
|
||||
* apiV3 证书序列号值
|
||||
*/
|
||||
private String certSerialNo;
|
||||
/**
|
||||
* 微信支付分serviceId
|
||||
*/
|
||||
private String serviceId;
|
||||
|
||||
/**
|
||||
* 微信支付分回调地址
|
||||
*/
|
||||
private String payScoreNotifyUrl;
|
||||
|
||||
|
||||
/**
|
||||
* 微信支付分授权回调地址
|
||||
*/
|
||||
private String payScorePermissionNotifyUrl;
|
||||
|
||||
|
||||
private CloseableHttpClient apiV3HttpClient;
|
||||
/**
|
||||
* 支持扩展httpClientBuilder
|
||||
*/
|
||||
private HttpClientBuilderCustomizer httpClientBuilderCustomizer;
|
||||
private HttpClientBuilderCustomizer apiV3HttpClientBuilderCustomizer;
|
||||
/**
|
||||
* 私钥信息
|
||||
*/
|
||||
private PrivateKey privateKey;
|
||||
|
||||
/**
|
||||
* 证书自动更新时间差(分钟),默认一分钟
|
||||
*/
|
||||
private int certAutoUpdateTime = 60;
|
||||
|
||||
/**
|
||||
* p12证书文件内容的字节数组.
|
||||
*/
|
||||
private byte[] keyContent;
|
||||
/**
|
||||
* 微信支付是否使用仿真测试环境.
|
||||
* 默认不使用
|
||||
*/
|
||||
private boolean useSandboxEnv = false;
|
||||
|
||||
/**
|
||||
* 是否将接口请求日志信息保存到threadLocal中.
|
||||
* 默认不保存
|
||||
*/
|
||||
private boolean ifSaveApiData = false;
|
||||
|
||||
private String httpProxyHost;
|
||||
private Integer httpProxyPort;
|
||||
private String httpProxyUsername;
|
||||
private String httpProxyPassword;
|
||||
|
||||
/**
|
||||
* v3接口下证书检验对象,通过改对象可以获取到X509Certificate,进一步对敏感信息加密
|
||||
* <a href="https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/min-gan-xin-xi-jia-mi">文档</a>
|
||||
*/
|
||||
private Verifier verifier;
|
||||
|
||||
/**
|
||||
* 返回所设置的微信支付接口请求地址域名.
|
||||
*
|
||||
* @return 微信支付接口请求地址域名
|
||||
*/
|
||||
public String getPayBaseUrl() {
|
||||
if (StringUtils.isEmpty(this.payBaseUrl)) {
|
||||
return DEFAULT_PAY_BASE_URL;
|
||||
}
|
||||
|
||||
return this.payBaseUrl;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Verifier getVerifier() {
|
||||
if (verifier == null) {
|
||||
//当改对象为null时,初始化api v3的请求头
|
||||
initApiV3HttpClient();
|
||||
}
|
||||
return verifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化ssl.
|
||||
*
|
||||
* @return the ssl context
|
||||
* @throws WxPayException the wx pay exception
|
||||
*/
|
||||
public SSLContext initSSLContext() throws WxPayException {
|
||||
if (StringUtils.isBlank(this.getMchId())) {
|
||||
throw new WxPayException("请确保商户号mchId已设置");
|
||||
}
|
||||
|
||||
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
|
||||
this.keyContent, "p12证书")) {
|
||||
KeyStore keystore = KeyStore.getInstance("PKCS12");
|
||||
char[] partnerId2charArray = this.getMchId().toCharArray();
|
||||
keystore.load(inputStream, partnerId2charArray);
|
||||
this.sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
|
||||
return this.sslContext;
|
||||
} catch (Exception e) {
|
||||
throw new WxPayException("证书文件有问题,请核实!", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化api v3请求头 自动签名验签
|
||||
* 方法参照 <a href="https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient">微信支付官方api项目</a>
|
||||
*
|
||||
* @author doger.wang
|
||||
**/
|
||||
public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
|
||||
if (StringUtils.isBlank(this.getApiV3Key())) {
|
||||
throw new WxPayException("请确保apiV3Key值已设置");
|
||||
}
|
||||
|
||||
// 尝试从p12证书中加载私钥和证书
|
||||
PrivateKey merchantPrivateKey = null;
|
||||
X509Certificate certificate = null;
|
||||
Object[] objects = this.p12ToPem();
|
||||
if (objects != null) {
|
||||
merchantPrivateKey = (PrivateKey) objects[0];
|
||||
certificate = (X509Certificate) objects[1];
|
||||
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
|
||||
}
|
||||
try {
|
||||
if (merchantPrivateKey == null) {
|
||||
try (InputStream keyInputStream = this.loadConfigInputStream(this.getPrivateKeyString(), this.getPrivateKeyPath(),
|
||||
this.privateKeyContent, "privateKeyPath")) {
|
||||
merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream);
|
||||
}
|
||||
}
|
||||
if (certificate == null && StringUtils.isBlank(this.getCertSerialNo())) {
|
||||
try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(),
|
||||
this.privateCertContent, "privateCertPath")) {
|
||||
certificate = PemUtils.loadCertificate(certInputStream);
|
||||
}
|
||||
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
|
||||
}
|
||||
PublicKey publicKey = null;
|
||||
if (this.getPublicKeyString() != null || this.getPublicKeyPath() != null || this.publicKeyContent != null) {
|
||||
try (InputStream pubInputStream =
|
||||
this.loadConfigInputStream(this.getPublicKeyString(), this.getPublicKeyPath(),
|
||||
this.publicKeyContent, "publicKeyPath")) {
|
||||
publicKey = PemUtils.loadPublicKey(pubInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
//构造Http Proxy正向代理
|
||||
WxPayHttpProxy wxPayHttpProxy = getWxPayHttpProxy();
|
||||
|
||||
Verifier certificatesVerifier = getVerifier(merchantPrivateKey, wxPayHttpProxy, publicKey);
|
||||
|
||||
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
|
||||
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
|
||||
.withValidator(new WxPayValidator(certificatesVerifier));
|
||||
//初始化V3接口正向代理设置
|
||||
HttpProxyUtils.initHttpProxy(wxPayV3HttpClientBuilder, wxPayHttpProxy);
|
||||
|
||||
// 提供自定义wxPayV3HttpClientBuilder的能力
|
||||
Optional.ofNullable(apiV3HttpClientBuilderCustomizer).ifPresent(e -> {
|
||||
e.customize(wxPayV3HttpClientBuilder);
|
||||
});
|
||||
CloseableHttpClient httpClient = wxPayV3HttpClientBuilder.build();
|
||||
|
||||
this.apiV3HttpClient = httpClient;
|
||||
this.verifier = certificatesVerifier;
|
||||
this.privateKey = merchantPrivateKey;
|
||||
|
||||
return httpClient;
|
||||
} catch (WxPayException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new WxPayException("v3请求构造异常!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Verifier getVerifier(PrivateKey merchantPrivateKey, WxPayHttpProxy wxPayHttpProxy, PublicKey publicKey) {
|
||||
Verifier certificatesVerifier = null;
|
||||
// 如果配置了平台证书,则初始化验证器以备v2版本接口验签(公钥灰度实现)
|
||||
boolean pathB = this.getPrivateCertPath() != null && this.getPrivateKeyPath() != null;
|
||||
boolean pathC = this.getPrivateCertContent() != null && this.getPrivateKeyContent() != null;
|
||||
boolean pathS = this.getPrivateCertString() != null && this.getPrivateKeyString() != null;
|
||||
|
||||
if(WechatPayCode.API_V2.equals(this.getApiVersion())){
|
||||
if (pathB || pathC || pathS) {
|
||||
certificatesVerifier = new AutoUpdateCertificatesVerifier(
|
||||
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
|
||||
this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(),
|
||||
this.getPayBaseUrl(), wxPayHttpProxy);
|
||||
}
|
||||
}else{
|
||||
if (publicKey != null) {
|
||||
Verifier publicCertificatesVerifier = new PublicCertificateVerifier(publicKey, publicKeyId);
|
||||
publicCertificatesVerifier.setOtherVerifier(certificatesVerifier);
|
||||
certificatesVerifier = publicCertificatesVerifier;
|
||||
}
|
||||
}
|
||||
return certificatesVerifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化一个WxPayHttpProxy对象
|
||||
*
|
||||
* @return 返回封装的WxPayHttpProxy对象。如未指定代理主机和端口,则默认返回null
|
||||
*/
|
||||
private WxPayHttpProxy getWxPayHttpProxy() {
|
||||
if (StringUtils.isNotBlank(this.getHttpProxyHost()) && this.getHttpProxyPort() > 0) {
|
||||
return new WxPayHttpProxy(getHttpProxyHost(), getHttpProxyPort(), getHttpProxyUsername(), getHttpProxyPassword());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定参数加载输入流
|
||||
*
|
||||
* @param configString 证书内容进行Base64加密后的字符串
|
||||
* @param configPath 证书路径
|
||||
* @param configContent 证书内容的字节数组
|
||||
* @param certName 证书的标识
|
||||
* @return 输入流
|
||||
* @throws WxPayException 异常
|
||||
*/
|
||||
private InputStream loadConfigInputStream(String configString, String configPath, byte[] configContent,
|
||||
String certName) throws WxPayException {
|
||||
if (configContent != null) {
|
||||
return new ByteArrayInputStream(configContent);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(configString)) {
|
||||
configContent = Base64.getDecoder().decode(configString);
|
||||
return new ByteArrayInputStream(configContent);
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(configPath)) {
|
||||
throw new WxPayException(String.format("请确保【%s】的文件地址【%s】存在", certName, configPath));
|
||||
}
|
||||
|
||||
return this.loadConfigInputStream(configPath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从配置路径 加载配置 信息(支持 classpath、本地路径、网络url)
|
||||
*
|
||||
* @param configPath 配置路径
|
||||
* @return .
|
||||
* @throws WxPayException .
|
||||
*/
|
||||
private InputStream loadConfigInputStream(String configPath) throws WxPayException {
|
||||
String fileHasProblemMsg = String.format(PROBLEM_MSG, configPath);
|
||||
String fileNotFoundMsg = String.format(NOT_FOUND_MSG, configPath);
|
||||
|
||||
final String prefix = "classpath:";
|
||||
InputStream inputStream;
|
||||
if (configPath.startsWith(prefix)) {
|
||||
String path = RegExUtils.removeFirst(configPath, prefix);
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
|
||||
try {
|
||||
inputStream = ResourcesUtils.getResourceAsStream(path);
|
||||
if (inputStream == null) {
|
||||
throw new WxPayException(fileNotFoundMsg);
|
||||
}
|
||||
|
||||
return inputStream;
|
||||
} catch (Exception e) {
|
||||
throw new WxPayException(fileNotFoundMsg, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (configPath.startsWith("http://") || configPath.startsWith("https://")) {
|
||||
try {
|
||||
inputStream = new URL(configPath).openStream();
|
||||
if (inputStream == null) {
|
||||
throw new WxPayException(fileNotFoundMsg);
|
||||
}
|
||||
return inputStream;
|
||||
} catch (IOException e) {
|
||||
throw new WxPayException(fileNotFoundMsg, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
File file = new File(configPath);
|
||||
if (!file.exists()) {
|
||||
throw new WxPayException(fileNotFoundMsg);
|
||||
}
|
||||
//使用Files.newInputStream打开公私钥文件,会存在无法释放句柄的问题
|
||||
//return Files.newInputStream(file.toPath());
|
||||
return new FileInputStream(file);
|
||||
} catch (IOException e) {
|
||||
throw new WxPayException(fileHasProblemMsg, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分解p12证书文件
|
||||
*/
|
||||
private Object[] p12ToPem() {
|
||||
String key = getMchId();
|
||||
if (StringUtils.isBlank(key) ||
|
||||
(StringUtils.isBlank(this.getKeyPath()) && this.keyContent == null && StringUtils.isBlank(this.keyString))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 分解p12证书文件
|
||||
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
|
||||
this.keyContent, "p12证书")) {
|
||||
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
||||
keyStore.load(inputStream, key.toCharArray());
|
||||
|
||||
String alias = keyStore.aliases().nextElement();
|
||||
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, key.toCharArray());
|
||||
|
||||
Certificate certificate = keyStore.getCertificate(alias);
|
||||
X509Certificate x509Certificate = (X509Certificate) certificate;
|
||||
return new Object[]{privateKey, x509Certificate};
|
||||
} catch (Exception e) {
|
||||
log.error("加载p12证书时发生异常", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ package org.dromara.daxpay.channel.wechat.entity.allocation;
|
||||
|
||||
import cn.bootx.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.daxpay.channel.wechat.convert.WechatAllocReceiverConvert;
|
||||
@@ -66,7 +67,7 @@ public class WechatAllocReceiver implements ToResult<WechatAllocReceiverResult>
|
||||
* 转换为通道接收方
|
||||
*/
|
||||
public static WechatAllocReceiver convertChannel(AllocReceiver receiver) {
|
||||
var leshuaAllocReceiver = JsonUtil.toBean(receiver.getExt(), WechatAllocReceiver.class);
|
||||
var leshuaAllocReceiver = JSONUtil.toBean(receiver.getExt(), WechatAllocReceiver.class);
|
||||
leshuaAllocReceiver.setId(receiver.getId())
|
||||
.setReceiverNo(receiver.getReceiverNo())
|
||||
.setReceiverName(receiver.getReceiverName())
|
||||
|
@@ -3,6 +3,7 @@ package org.dromara.daxpay.channel.wechat.entity.config;
|
||||
import cn.bootx.platform.common.mybatisplus.function.ToResult;
|
||||
import cn.bootx.platform.core.util.JsonUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.daxpay.channel.wechat.code.WechatPayCode;
|
||||
@@ -114,7 +115,7 @@ public class WechatPayConfig implements ToResult<WechatPayConfigResult> {
|
||||
* 从通道配置转换为微信支付配置
|
||||
*/
|
||||
public static WechatPayConfig convertConfig(ChannelConfig channelConfig) {
|
||||
WechatPayConfig config = JsonUtil.toBean(channelConfig.getExt(), WechatPayConfig.class);
|
||||
WechatPayConfig config = JSONUtil.toBean(channelConfig.getExt(), WechatPayConfig.class);
|
||||
|
||||
config.setId(channelConfig.getId())
|
||||
.setWxAppId(channelConfig.getOutAppId())
|
||||
|
@@ -166,7 +166,7 @@ public class WechatPayConfigService {
|
||||
payConfig.setSubAppId(wechatPayConfig.getSubAppId());
|
||||
payConfig.setMchKey(wechatPayConfig.getApiKeyV2());
|
||||
payConfig.setApiV3Key(wechatPayConfig.getApiKeyV3());
|
||||
payConfig.setApiVersion(wechatPayConfig.getApiVersion());
|
||||
// payConfig.setApiVersion(wechatPayConfig.getApiVersion());
|
||||
// 注意不要使用base64的方式进行配置, 因为wxjava 是直接读取文本并不会进行解码, 会导致证书异常
|
||||
if (StrUtil.isNotBlank(wechatPayConfig.getPublicKey())){
|
||||
payConfig.setPublicKeyContent(Base64.decode(wechatPayConfig.getPublicKey()));
|
||||
|
@@ -6,13 +6,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.3</version>
|
||||
<version>3.5.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>org.dromara.daxpay</groupId>
|
||||
<artifactId>daxpay-open-channel</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<description>开源版支付通道功能实现</description>
|
||||
@@ -35,8 +35,8 @@
|
||||
<easypoi.version>4.5.0</easypoi.version>
|
||||
<wxjava.version>4.7.4.B</wxjava.version>
|
||||
|
||||
<bootx-platform.version>3.0.0.beta5</bootx-platform.version>
|
||||
<daxpay.version>3.0.0.beta5</daxpay.version>
|
||||
<bootx-platform.version>3.0.0</bootx-platform.version>
|
||||
<daxpay.version>3.0.0</daxpay.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.dromara.daxpay</groupId>
|
||||
<artifactId>daxpay-open-sdk</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!-- 项目信息 -->
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.3</version>
|
||||
<version>3.5.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<bootx-platform.version>3.0.0.beta5</bootx-platform.version>
|
||||
<daxpay.version>3.0.0.beta5</daxpay.version>
|
||||
<bootx-platform.version>3.0.0</bootx-platform.version>
|
||||
<daxpay.version>3.0.0</daxpay.version>
|
||||
<minio.version>8.5.2</minio.version>
|
||||
</properties>
|
||||
|
||||
@@ -46,16 +46,9 @@
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库驱动 MySQL -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.mysql</groupId>-->
|
||||
<!-- <artifactId>mysql-connector-j</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!--文件存储 (minio方式)-->
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>${minio.version}</version>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 支付通道 -->
|
||||
|
@@ -68,9 +68,6 @@ bootx-platform:
|
||||
- '/css/**'
|
||||
- '/error'
|
||||
- '/favicon.ico'
|
||||
file-upload:
|
||||
# 使用后端代理访问, 线上请使用 Nginx 配置或者直连方式,效率更高
|
||||
forward-server-url: http://127.0.0.1:9999
|
||||
dax-pay:
|
||||
env: DEV_
|
||||
machine-no: 70
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.daxpay</groupId>
|
||||
<artifactId>daxpay-open</artifactId>
|
||||
<version>3.0.0.beta5</version>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>daxpay-open-controller</artifactId>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user