Merge branch 'master-jdk17' into feature/db

# Conflicts:
#	sql/tools/README.md
This commit is contained in:
dhb52 2024-06-17 23:58:36 +08:00
commit 27e2996c5d
18 changed files with 628 additions and 139 deletions

View File

@ -67,8 +67,8 @@ exit
① 下载人大金仓 Docker 镜像: ① 下载人大金仓 Docker 镜像:
> x86_64版本: https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/x86_64/kdb_x86_64_V009R001C001B0025.tar > x86_64 版本: https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/x86_64/kdb_x86_64_V009R001C001B0025.tar
> aarch64版本https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/aarch64/kdb_aarch64_V009R001C001B0025.tar > aarch64 版本https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/aarch64/kdb_aarch64_V009R001C001B0025.tar
② 加载镜像文件,在镜像 tar 文件所在目录运行: ② 加载镜像文件,在镜像 tar 文件所在目录运行:
@ -80,11 +80,11 @@ docker load -i x86_64/kdb_x86_64_V009R001C001B0025.tar
```Bash ```Bash
docker compose up -d kingbase docker compose up -d kingbase
# 注意:启动完 dm 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本 # 注意:启动完 kingbase 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本
docker compose exec kingbase bash -c 'ksql -U $DB_USER -d test -f /tmp/schema.sql' docker compose exec kingbase bash -c "exec ksql -Uroot -d test -f /tmp/schema.sql"
``` ```
**注意**: MyBatis, MyBatis Plus 目前不兼容人大金仓推荐直接使用PostgreSQL JDBC 驱动,已经 url 配置方式连接数据库。 **注意**: MyBatisMyBatis Plus 目前不兼容人大金仓,推荐直接使用 PostgreSQL JDBC 驱动,已经 url 配置方式连接数据库。
### 1.7 华为 OpenGauss ### 1.7 华为 OpenGauss

View File

@ -247,15 +247,15 @@ id,name,type,parentId
246,英属印度洋领地,1,0 246,英属印度洋领地,1,0
247,东萨摩亚,1,0 247,东萨摩亚,1,0
248,诺福克岛,1,0 248,诺福克岛,1,0
110000,北京,2,1 110000,北京,2,1
120000,天津,2,1 120000,天津,2,1
130000,河北省,2,1 130000,河北省,2,1
140000,山西省,2,1 140000,山西省,2,1
150000,内蒙古自治区,2,1 150000,内蒙古自治区,2,1
210000,辽宁省,2,1 210000,辽宁省,2,1
220000,吉林省,2,1 220000,吉林省,2,1
230000,黑龙江省,2,1 230000,黑龙江省,2,1
310000,上海,2,1 310000,上海,2,1
320000,江苏省,2,1 320000,江苏省,2,1
330000,浙江省,2,1 330000,浙江省,2,1
340000,安徽省,2,1 340000,安徽省,2,1
@ -268,7 +268,7 @@ id,name,type,parentId
440000,广东省,2,1 440000,广东省,2,1
450000,广西壮族自治区,2,1 450000,广西壮族自治区,2,1
460000,海南省,2,1 460000,海南省,2,1
500000,重庆,2,1 500000,重庆,2,1
510000,四川省,2,1 510000,四川省,2,1
520000,贵州省,2,1 520000,贵州省,2,1
530000,云南省,2,1 530000,云南省,2,1

1 id name type parentId
247 246 英属印度洋领地 1 0
248 247 东萨摩亚 1 0
249 248 诺福克岛 1 0
250 110000 北京 北京市 2 1
251 120000 天津 天津市 2 1
252 130000 河北省 2 1
253 140000 山西省 2 1
254 150000 内蒙古自治区 2 1
255 210000 辽宁省 2 1
256 220000 吉林省 2 1
257 230000 黑龙江省 2 1
258 310000 上海 上海市 2 1
259 320000 江苏省 2 1
260 330000 浙江省 2 1
261 340000 安徽省 2 1
268 440000 广东省 2 1
269 450000 广西壮族自治区 2 1
270 460000 海南省 2 1
271 500000 重庆 重庆市 2 1
272 510000 四川省 2 1
273 520000 贵州省 2 1
274 530000 云南省 2 1

View File

@ -34,20 +34,24 @@ public class CronUtils {
* @return 满足条件的执行时间 * @return 满足条件的执行时间
*/ */
public static List<LocalDateTime> getNextTimes(String cronExpression, int n) { public static List<LocalDateTime> getNextTimes(String cronExpression, int n) {
// 获得 CronExpression 对象 // 1. 获得 CronExpression 对象
CronExpression cron; CronExpression cron;
try { try {
cron = new CronExpression(cronExpression); cron = new CronExpression(cronExpression);
} catch (ParseException e) { } catch (ParseException e) {
throw new IllegalArgumentException(e.getMessage()); throw new IllegalArgumentException(e.getMessage());
} }
// 从当前开始计算n 个满足条件的 // 2. 从当前开始计算n 个满足条件的
Date now = new Date(); Date now = new Date();
List<LocalDateTime> nextTimes = new ArrayList<>(n); List<LocalDateTime> nextTimes = new ArrayList<>(n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
Date nextTime = cron.getNextValidTimeAfter(now); Date nextTime = cron.getNextValidTimeAfter(now);
// 2.1 如果 nextTime null说明没有更多的有效时间退出循环
if (nextTime == null) {
break;
}
nextTimes.add(LocalDateTimeUtil.of(nextTime)); nextTimes.add(LocalDateTimeUtil.of(nextTime));
// 切换现在为下一个触发时间 // 2.2 切换现在为下一个触发时间
now = nextTime; now = nextTime;
} }
return nextTimes; return nextTimes;

View File

@ -12,7 +12,7 @@
<packaging>jar</packaging> <packaging>jar</packaging>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<description>服务保证,提供分布式锁、幂等、限流、熔断等等功能</description> <description>服务保证,提供分布式锁、幂等、限流、熔断、API 签名等等功能</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url> <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies> <dependencies>
@ -35,6 +35,13 @@
<artifactId>lock4j-redisson-spring-boot-starter</artifactId> <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.framework.signature.config;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.framework.signature.core.aop.ApiSignatureAspect;
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* HTTP API 签名的自动配置类
*
* @author Zhougang
*/
@AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
public class YudaoApiSignatureAutoConfiguration {
@Bean
public ApiSignatureAspect signatureAspect(ApiSignatureRedisDAO signatureRedisDAO) {
return new ApiSignatureAspect(signatureRedisDAO);
}
@Bean
public ApiSignatureRedisDAO signatureRedisDAO(StringRedisTemplate stringRedisTemplate) {
return new ApiSignatureRedisDAO(stringRedisTemplate);
}
}

View File

@ -0,0 +1,59 @@
package cn.iocoder.yudao.framework.signature.core.annotation;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* HTTP API 签名注解
*
* @author Zhougang
*/
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiSignature {
/**
* 同一个请求多长时间内有效 默认 60
*/
int timeout() default 60;
/**
* 时间单位默认为 SECONDS
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
// ========================== 签名参数 ==========================
/**
* 提示信息签名失败的提示
*
* @see GlobalErrorCodeConstants#BAD_REQUEST
*/
String message() default "签名不正确"; // 为空时使用 BAD_REQUEST 错误提示
/**
* 签名字段appId 应用ID
*/
String appId() default "appId";
/**
* 签名字段timestamp 时间戳
*/
String timestamp() default "timestamp";
/**
* 签名字段nonce 随机数10 位以上
*/
String nonce() default "nonce";
/**
* sign 客户端签名
*/
String sign() default "sign";
}

View File

@ -0,0 +1,169 @@
package cn.iocoder.yudao.framework.signature.core.aop;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
/**
* 拦截声明了 {@link ApiSignature} 注解的方法实现签名
*
* @author Zhougang
*/
@Aspect
@Slf4j
@AllArgsConstructor
public class ApiSignatureAspect {
private final ApiSignatureRedisDAO signatureRedisDAO;
@Before("@annotation(signature)")
public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) {
// 1. 验证通过直接结束
if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) {
return;
}
// 2. 验证不通过抛出异常
log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(),
joinPoint.getArgs());
throw new ServiceException(BAD_REQUEST.getCode(),
StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg()));
}
public boolean verifySignature(ApiSignature signature, HttpServletRequest request) {
// 1.1 校验 Header
if (!verifyHeaders(signature, request)) {
return false;
}
// 1.2 校验 appId 是否能获取到对应的 appSecret
String appId = request.getHeader(signature.appId());
String appSecret = signatureRedisDAO.getAppSecret(appId);
Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId);
// 2. 校验签名重要
String clientSignature = request.getHeader(signature.sign()); // 客户端签名
String serverSignatureString = buildSignatureString(signature, request, appSecret); // 服务端签名字符串
String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名
if (ObjUtil.notEqual(clientSignature, serverSignature)) {
return false;
}
// 3. nonce 记入缓存防止重复使用重点二此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2
String nonce = request.getHeader(signature.nonce());
signatureRedisDAO.setNonce(nonce, signature.timeout() * 2, signature.timeUnit());
return true;
}
/**
* 校验请求头加签参数
*
* 1. appId 是否为空
* 2. timestamp 是否为空请求是否已经超时默认 10 分钟
* 3. nonce 是否为空随机数是否 10 位以上是否在规定时间内已经访问过了
* 4. sign 是否为空
*
* @param signature signature
* @param request request
* @return 是否校验 Header 通过
*/
private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) {
// 1. 非空校验
String appId = request.getHeader(signature.appId());
if (StrUtil.isBlank(appId)) {
return false;
}
String timestamp = request.getHeader(signature.timestamp());
if (StrUtil.isBlank(timestamp)) {
return false;
}
String nonce = request.getHeader(signature.nonce());
if (StrUtil.length(nonce) < 10) {
return false;
}
String sign = request.getHeader(signature.sign());
if (StrUtil.isBlank(sign)) {
return false;
}
// 2. 检查 timestamp 是否超出允许的范围 重点一此处需要取绝对值
long expireTime = signature.timeUnit().toMillis(signature.timeout());
long requestTimestamp = Long.parseLong(timestamp);
long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp);
if (timestampDisparity > expireTime) {
return false;
}
// 3. 检查 nonce 是否存在有且仅能使用一次
return signatureRedisDAO.getNonce(nonce) == null;
}
/**
* 构建签名字符串
*
* 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
*
* @param signature signature
* @param request request
* @param appSecret appSecret
* @return 签名字符串
*/
private String buildSignatureString(ApiSignature signature, HttpServletRequest request, String appSecret) {
SortedMap<String, String> parameterMap = getRequestParameterMap(request); // 请求头
SortedMap<String, String> headerMap = getRequestHeaderMap(signature, request); // 请求参数
String requestBody = StrUtil.nullToDefault(ServletUtils.getBody(request), ""); // 请求体
return MapUtil.join(parameterMap, "&", "=")
+ requestBody
+ MapUtil.join(headerMap, "&", "=")
+ appSecret;
}
/**
* 获取请求头加签参数 Map
*
* @param request 请求
* @param signature 签名注解
* @return signature params
*/
private static SortedMap<String, String> getRequestHeaderMap(ApiSignature signature, HttpServletRequest request) {
SortedMap<String, String> sortedMap = new TreeMap<>();
sortedMap.put(signature.appId(), request.getHeader(signature.appId()));
sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp()));
sortedMap.put(signature.nonce(), request.getHeader(signature.nonce()));
return sortedMap;
}
/**
* 获取请求参数 Map
*
* @param request 请求
* @return queryParams
*/
private static SortedMap<String, String> getRequestParameterMap(HttpServletRequest request) {
SortedMap<String, String> sortedMap = new TreeMap<>();
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
sortedMap.put(entry.getKey(), entry.getValue()[0]);
}
return sortedMap;
}
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.framework.signature.core.redis;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
/**
* HTTP API 签名 Redis DAO
*
* @author Zhougang
*/
@AllArgsConstructor
public class ApiSignatureRedisDAO {
private final StringRedisTemplate stringRedisTemplate;
/**
* 验签随机数
*
* KEY 格式signature_nonce:%s // 参数为 随机数
* VALUE 格式String
* 过期时间不固定
*/
private static final String SIGNATURE_NONCE = "api_signature_nonce:%s";
/**
* 签名密钥
*
* HASH 结构
* KEY 格式%s // 参数为 appid
* VALUE 格式String
* 过期时间永不过期预加载到 Redis
*/
private static final String SIGNATURE_APPID = "api_signature_app";
// ========== 验签随机数 ==========
public String getNonce(String nonce) {
return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce));
}
public void setNonce(String nonce, int time, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), "", time, timeUnit);
}
private static String formatNonceKey(String key) {
return String.format(SIGNATURE_NONCE, key);
}
// ========== 签名密钥 ==========
public String getAppSecret(String appId) {
return (String) stringRedisTemplate.opsForHash().get(SIGNATURE_APPID, appId);
}
}

View File

@ -0,0 +1,6 @@
/**
* HTTP API 签名校验安全性
*
* @see <a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3>微信支付 —— 安全规范</a>
*/
package cn.iocoder.yudao.framework.signature;

View File

@ -1,3 +1,4 @@
cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration cn.iocoder.yudao.framework.idempotent.config.YudaoIdempotentConfiguration
cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration cn.iocoder.yudao.framework.lock4j.config.YudaoLock4jConfiguration
cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration cn.iocoder.yudao.framework.ratelimiter.config.YudaoRateLimiterConfiguration
cn.iocoder.yudao.framework.signature.config.YudaoApiSignatureAutoConfiguration

View File

@ -0,0 +1,75 @@
package cn.iocoder.yudao.framework.signature.core;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
import cn.iocoder.yudao.framework.signature.core.aop.ApiSignatureAspect;
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link ApiSignatureTest} 的单元测试
*/
@ExtendWith(MockitoExtension.class)
public class ApiSignatureTest {
@InjectMocks
private ApiSignatureAspect apiSignatureAspect;
@Mock
private ApiSignatureRedisDAO signatureRedisDAO;
@Test
public void testSignatureGet() throws IOException {
// 搞一个签名
Long timestamp = System.currentTimeMillis();
String nonce = IdUtil.randomUUID();
String appId = "xxxxxx";
String appSecret = "yyyyyy";
String signString = "k1=v1&v1=k1testappId=xxxxxx&nonce=" + nonce + "&timestamp=" + timestamp + "yyyyyy";
String sign = DigestUtil.sha256Hex(signString);
// 准备参数
ApiSignature apiSignature = mock(ApiSignature.class);
when(apiSignature.appId()).thenReturn("appId");
when(apiSignature.timestamp()).thenReturn("timestamp");
when(apiSignature.nonce()).thenReturn("nonce");
when(apiSignature.sign()).thenReturn("sign");
when(apiSignature.timeout()).thenReturn(60);
when(apiSignature.timeUnit()).thenReturn(TimeUnit.SECONDS);
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader(eq("appId"))).thenReturn(appId);
when(request.getHeader(eq("timestamp"))).thenReturn(String.valueOf(timestamp));
when(request.getHeader(eq("nonce"))).thenReturn(nonce);
when(request.getHeader(eq("sign"))).thenReturn(sign);
when(request.getParameterMap()).thenReturn(MapUtil.<String, String[]>builder()
.put("v1", new String[]{"k1"}).put("k1", new String[]{"v1"}).build());
when(request.getContentType()).thenReturn("application/json");
when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test")));
// mock 方法
when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret);
// 调用
boolean result = apiSignatureAspect.verifySignature(apiSignature, request);
// 断言结果
assertTrue(result);
// 断言调用
verify(signatureRedisDAO).setNonce(eq(nonce), eq(120), eq(TimeUnit.SECONDS));
}
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.business; package cn.iocoder.yudao.module.crm.dal.dataobject.business;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
@ -19,7 +20,7 @@ import lombok.*;
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class CrmBusinessStatusDO { public class CrmBusinessStatusDO extends BaseDO {
/** /**
* 主键 * 主键

View File

@ -28,8 +28,8 @@ public class FileContentDO extends BaseDO {
/** /**
* 编号数据库自增 * 编号数据库自增
*/ */
@TableId(type = IdType.INPUT) @TableId
private String id; private Long id;
/** /**
* 配置编号 * 配置编号
* *

View File

@ -25,7 +25,6 @@ import java.time.LocalDateTime;
@KeySequence(value = "infra_api_error_log_seq") @KeySequence(value = "infra_api_error_log_seq")
public class ApiErrorLogDO extends BaseDO { public class ApiErrorLogDO extends BaseDO {
/** /**
* {@link #requestParams} 的最大长度 * {@link #requestParams} 的最大长度
*/ */

View File

@ -342,7 +342,8 @@ public class CodegenEngine {
// className 相关 // className 相关
// 去掉指定前缀 TestDictType 转换成 DictType. 因为在 create 等方法后不需要带上 Test 前缀 // 去掉指定前缀 TestDictType 转换成 DictType. 因为在 create 等方法后不需要带上 Test 前缀
String simpleClassName = removePrefix(table.getClassName(), upperFirst(table.getModuleName())); String simpleClassName = equalsAnyIgnoreCase(table.getClassName(), table.getModuleName()) ? table.getClassName()
: removePrefix(table.getClassName(), upperFirst(table.getModuleName()));
bindingMap.put("simpleClassName", simpleClassName); bindingMap.put("simpleClassName", simpleClassName);
bindingMap.put("simpleClassName_underlineCase", toUnderlineCase(simpleClassName)); // DictType 转换成 dict_type bindingMap.put("simpleClassName_underlineCase", toUnderlineCase(simpleClassName)); // DictType 转换成 dict_type
bindingMap.put("classNameVar", lowerFirst(simpleClassName)); // DictType 转换成 dictType用于变量 bindingMap.put("classNameVar", lowerFirst(simpleClassName)); // DictType 转换成 dictType用于变量

View File

@ -1,33 +1,49 @@
package cn.iocoder.yudao.module.trade.service.order; package cn.iocoder.yudao.module.trade.service.order;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.express.DeliveryExpressCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.cart.CartServiceImpl;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressServiceImpl;
import cn.iocoder.yudao.module.trade.service.message.TradeMessageServiceImpl;
import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler;
import cn.iocoder.yudao.module.trade.service.price.TradePriceServiceImpl;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource;
import java.time.Duration; import java.time.Duration;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -38,7 +54,9 @@ import static org.mockito.Mockito.when;
* @since 2022-09-07 * @since 2022-09-07
*/ */
@Disabled // TODO 芋艿后续 fix 补充的单测 @Disabled // TODO 芋艿后续 fix 补充的单测
@Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class}) @Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class, CartServiceImpl.class, TradePriceServiceImpl.class,
DeliveryExpressServiceImpl.class, TradeMessageServiceImpl.class
})
public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
@Resource @Resource
@ -55,7 +73,9 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
private ProductSpuApi productSpuApi; private ProductSpuApi productSpuApi;
@MockBean @MockBean
private ProductSkuApi productSkuApi; private ProductSkuApi productSkuApi;
// @MockBean @MockBean
private ProductCommentApi productCommentApi;
// @MockBean
// private PriceApi priceApi; // private PriceApi priceApi;
@MockBean @MockBean
private PayOrderApi payOrderApi; private PayOrderApi payOrderApi;
@ -66,11 +86,22 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
@MockBean @MockBean
private TradeOrderProperties tradeOrderProperties; private TradeOrderProperties tradeOrderProperties;
@MockBean
private TradeNoRedisDAO tradeNoRedisDAO;
@MockBean
private TradeOrderHandler tradeOrderHandler;
@MockBean
private TradePriceCalculator tradePriceCalculator;
@MockBean
private NotifyMessageSendApi notifyMessageSendApi;
@MockBean
private DeliveryExpressService deliveryExpressService;
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
when(tradeOrderProperties.getAppId()).thenReturn(888L); when(tradeOrderProperties.getAppId()).thenReturn(888L);
when(tradeOrderProperties.getPayExpireTime()).thenReturn(Duration.ofDays(1)); when(tradeOrderProperties.getPayExpireTime()).thenReturn(Duration.ofDays(1));
when(tradeNoRedisDAO.generate(anyString())).thenReturn(IdUtil.randomUUID());
} }
// @Test // @Test
@ -259,11 +290,18 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> { TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> {
o.setId(1L).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()); o.setId(1L).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus());
o.setLogisticsId(null).setLogisticsNo(null).setDeliveryTime(null); o.setLogisticsId(null).setLogisticsNo(null).setDeliveryTime(null);
o.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
o.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType());
}); });
tradeOrderMapper.insert(order); tradeOrderMapper.insert(order);
DeliveryExpressCreateReqVO expressCreateReqVO = new DeliveryExpressCreateReqVO();
expressCreateReqVO.setCode("code").setName("Name").setLogo("logo").setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus());
Long deliveryExpressId = deliveryExpressService.createDeliveryExpress(expressCreateReqVO);
// 准备参数 // 准备参数
TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L) TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L)
.setLogisticsId(10L).setLogisticsNo("100"); .setLogisticsId(deliveryExpressId).setLogisticsNo("100");
// mock 方法支付单 // mock 方法支付单
// 调用 // 调用

View File

@ -1,128 +1,155 @@
CREATE TABLE IF NOT EXISTS "trade_order" ( CREATE TABLE IF NOT EXISTS "trade_order"
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, (
"no" varchar NOT NULL, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"type" int NOT NULL, "no" varchar NOT NULL,
"terminal" int NOT NULL, "type" int NOT NULL,
"user_id" bigint NOT NULL, "terminal" int NOT NULL,
"user_ip" varchar NOT NULL, "user_id" bigint NOT NULL,
"user_remark" varchar, "user_ip" varchar NOT NULL,
"status" int NOT NULL, "user_remark" varchar,
"product_count" int NOT NULL, "status" int NOT NULL,
"cancel_type" int, "product_count" int NOT NULL,
"remark" varchar, "cancel_type" int,
"pay_status" bit NOT NULL, "remark" varchar,
"pay_time" datetime, "comment_status" boolean,
"finish_time" datetime, "brokerage_user_id" bigint,
"cancel_time" datetime, "pay_status" bit NOT NULL,
"original_price" int NOT NULL, "pay_time" datetime,
"order_price" int NOT NULL, "finish_time" datetime,
"discount_price" int NOT NULL, "cancel_time" datetime,
"delivery_price" int NOT NULL, "total_price" int NULL,
"adjust_price" int NOT NULL, "order_price" int NULL,
"pay_price" int NOT NULL, "discount_price" int NOT NULL,
"pay_order_id" bigint, "delivery_price" int NOT NULL,
"pay_channel_code" varchar, "adjust_price" int NOT NULL,
"delivery_template_id" bigint, "pay_price" int NOT NULL,
"logistics_id" bigint, "delivery_type" int NOT NULL,
"logistics_no" varchar, "pay_order_id" bigint,
"delivery_time" datetime, "pay_channel_code" varchar,
"receive_time" datetime, "delivery_template_id" bigint,
"receiver_name" varchar NOT NULL, "logistics_id" bigint,
"receiver_mobile" varchar NOT NULL, "logistics_no" varchar,
"receiver_area_id" int NOT NULL, "delivery_time" datetime,
"receiver_post_code" int, "receive_time" datetime,
"receiver_detail_address" varchar NOT NULL, "receiver_name" varchar NOT NULL,
"after_sale_status" int NOT NULL, "receiver_mobile" varchar NOT NULL,
"refund_price" int NOT NULL, "receiver_area_id" int NOT NULL,
"coupon_id" bigint NOT NULL, "receiver_post_code" int,
"coupon_price" int NOT NULL, "receiver_detail_address" varchar NOT NULL,
"point_price" int NOT NULL, "pick_up_store_id" long NULL,
"creator" varchar DEFAULT '', "pick_up_verify_code" varchar NULL,
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, "refund_status" int NULL,
"updater" varchar DEFAULT '', "refund_price" int NULL,
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "after_sale_status" int NULL,
"deleted" bit NOT NULL DEFAULT FALSE, "coupon_id" bigint NOT NULL,
PRIMARY KEY ("id") "coupon_price" int NOT NULL,
"use_point" int NULL,
"point_price" int NOT NULL,
"give_point" int NULL,
"refund_point" int NULL,
"vip_price" int NULL,
"seckill_activity_id" long NULL,
"bargain_activity_id" long NULL,
"bargain_record_id" long NULL,
"combination_activity_id" long NULL,
"combination_head_id" long NULL,
"combination_record_id" long NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '交易订单表'; ) COMMENT '交易订单表';
CREATE TABLE IF NOT EXISTS "trade_order_item" ( CREATE TABLE IF NOT EXISTS "trade_order_item"
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, (
"user_id" bigint NOT NULL, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"order_id" bigint NOT NULL, "user_id" bigint NOT NULL,
"spu_id" bigint NOT NULL, "order_id" bigint NOT NULL,
"spu_name" varchar NOT NULL, "cart_id" int NULL,
"sku_id" bigint NOT NULL, "spu_id" bigint NOT NULL,
"properties" varchar, "spu_name" varchar NOT NULL,
"pic_url" varchar, "sku_id" bigint NOT NULL,
"count" int NOT NULL, "properties" varchar,
"original_price" int NOT NULL, "pic_url" varchar,
"original_unit_price" int NOT NULL, "count" int NOT NULL,
"discount_price" int NOT NULL, "comment_status" boolean NULL,
"pay_price" int NOT NULL, "price" int NOT NULL,
"order_part_price" int NOT NULL, "discount_price" int NOT NULL,
"order_divide_price" int NOT NULL, "delivery_price" int NULL,
"after_sale_status" int NOT NULL, "adjust_price" int NULL,
"creator" varchar DEFAULT '', "pay_price" int NOT NULL,
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, "coupon_price" int NULL,
"updater" varchar DEFAULT '', "point_price" int NULL,
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "use_point" int NULL,
"deleted" bit NOT NULL DEFAULT FALSE, "give_point" int NULL,
PRIMARY KEY ("id") "vip_price" int NULL,
"after_sale_id" long NULL,
"after_sale_status" int NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '交易订单明细表'; ) COMMENT '交易订单明细表';
CREATE TABLE IF NOT EXISTS "trade_after_sale" ( CREATE TABLE IF NOT EXISTS "trade_after_sale"
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, (
"no" varchar NOT NULL, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"status" int NOT NULL, "no" varchar NOT NULL,
"type" int NOT NULL, "status" int NOT NULL,
"way" int NOT NULL, "type" int NOT NULL,
"user_id" bigint NOT NULL, "way" int NOT NULL,
"apply_reason" varchar NOT NULL, "user_id" bigint NOT NULL,
"apply_reason" varchar NOT NULL,
"apply_description" varchar, "apply_description" varchar,
"apply_pic_urls" varchar, "apply_pic_urls" varchar,
"order_id" bigint NOT NULL, "order_id" bigint NOT NULL,
"order_no" varchar NOT NULL, "order_no" varchar NOT NULL,
"order_item_id" bigint NOT NULL, "order_item_id" bigint NOT NULL,
"spu_id" bigint NOT NULL, "spu_id" bigint NOT NULL,
"spu_name" varchar NOT NULL, "spu_name" varchar NOT NULL,
"sku_id" bigint NOT NULL, "sku_id" bigint NOT NULL,
"properties" varchar, "properties" varchar,
"pic_url" varchar, "pic_url" varchar,
"count" int NOT NULL, "count" int NOT NULL,
"audit_time" varchar, "audit_time" varchar,
"audit_user_id" bigint, "audit_user_id" bigint,
"audit_reason" varchar, "audit_reason" varchar,
"refund_price" int NOT NULL, "refund_price" int NOT NULL,
"pay_refund_id" bigint, "pay_refund_id" bigint,
"refund_time" varchar, "refund_time" varchar,
"logistics_id" bigint, "logistics_id" bigint,
"logistics_no" varchar, "logistics_no" varchar,
"delivery_time" varchar, "delivery_time" varchar,
"receive_time" varchar, "receive_time" varchar,
"receive_reason" varchar, "receive_reason" varchar,
"creator" varchar DEFAULT '', "creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '', "updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '交易售后表'; ) COMMENT '交易售后表';
CREATE TABLE IF NOT EXISTS "trade_after_sale_log" ( CREATE TABLE IF NOT EXISTS "trade_after_sale_log"
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, (
"user_id" bigint NOT NULL, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_type" int NOT NULL, "user_id" bigint NOT NULL,
"after_sale_id" bigint NOT NULL, "user_type" int NOT NULL,
"order_id" bigint NOT NULL, "after_sale_id" bigint NOT NULL,
"order_item_id" bigint NOT NULL, "order_id" bigint NOT NULL,
"order_item_id" bigint NOT NULL,
"before_status" int, "before_status" int,
"after_status" int NOT NULL, "after_status" int NOT NULL,
"content" varchar NOT NULL, "content" varchar NOT NULL,
"creator" varchar DEFAULT '', "creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '', "updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '交易售后日志'; ) COMMENT '交易售后日志';
@ -161,7 +188,7 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_record"
"updater" varchar DEFAULT '', "updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0', "tenant_id" bigint not null default '0',
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '佣金记录'; ) COMMENT '佣金记录';
CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw" CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw"
@ -186,6 +213,22 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw"
"updater" varchar DEFAULT '', "updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0', "tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '佣金提现';
CREATE TABLE IF NOT EXISTS "trade_delivery_express"
(
"id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"code" varchar NULL,
"name" varchar,
"logo" varchar NULL,
"sort" int NOT NULL,
"status" int NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '佣金提现'; ) COMMENT '佣金提现';

View File

@ -11,6 +11,7 @@ import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum SexEnum { public enum SexEnum {
/** 男 */ /** 男 */
MALE(1), MALE(1),
/** 女 */ /** 女 */