mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2025-01-18 19:20:05 +08:00
by gateway:
1. 完善部分 order 单元测试 2. 增加支付订单的 no 生成逻辑
This commit is contained in:
parent
721adfbf60
commit
b54f7e9256
@ -12,7 +12,6 @@ import org.springframework.context.annotation.Bean;
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(PayProperties.class)
|
||||
public class YudaoPayAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
@ -14,4 +14,12 @@ public interface RedisKeyConstants {
|
||||
"pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder 类
|
||||
RedisKeyDefine.KeyTypeEnum.HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
|
||||
|
||||
/**
|
||||
* 支付序号的缓存
|
||||
*
|
||||
* KEY 格式:pay_no:{prefix}
|
||||
* VALUE 数据格式:编号自增
|
||||
*/
|
||||
String PAY_NO = "pay_no";
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.pay.dal.redis.no;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateUtil;import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 支付序号的 Redis DAO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Repository
|
||||
public class PayNoRedisDAO {
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 生成序号
|
||||
*
|
||||
* @param prefix 前缀
|
||||
* @return 序号
|
||||
*/
|
||||
public String generate(String prefix) {
|
||||
String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
|
||||
Long no = stringRedisTemplate.opsForValue().increment(noPrefix);
|
||||
return noPrefix + no;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package cn.iocoder.yudao.module.pay.framework.pay.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(PayProperties.class)
|
||||
public class PayConfiguration {
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.framework.pay.config;
|
||||
package cn.iocoder.yudao.module.pay.framework.pay.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
@ -12,6 +12,9 @@ import javax.validation.constraints.NotEmpty;
|
||||
@Data
|
||||
public class PayProperties {
|
||||
|
||||
private static final String ORDER_NO_PREFIX = "P";
|
||||
private static final String REFUND_NO_PREFIX = "R";
|
||||
|
||||
/**
|
||||
* 支付回调地址
|
||||
*
|
||||
@ -34,4 +37,16 @@ public class PayProperties {
|
||||
@URL(message = "支付回调地址的格式必须是 URL")
|
||||
private String refundNotifyUrl;
|
||||
|
||||
/**
|
||||
* 支付订单 no 的前缀
|
||||
*/
|
||||
@NotEmpty(message = "支付订单 no 的前缀不能为空")
|
||||
private String orderNoPrefix = ORDER_NO_PREFIX;
|
||||
|
||||
/**
|
||||
* 退款订单 no 的前缀
|
||||
*/
|
||||
@NotEmpty(message = "退款订单 no 的前缀不能为空")
|
||||
private String refundNoPrefix = REFUND_NO_PREFIX;
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 占位,无实际作用
|
||||
*/
|
||||
package cn.iocoder.yudao.module.pay.framework.pay.core;
|
@ -1,14 +1,11 @@
|
||||
package cn.iocoder.yudao.module.pay.service.order;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||
@ -27,8 +24,10 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
||||
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||
@ -40,7 +39,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@ -68,6 +66,8 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
private PayOrderMapper orderMapper;
|
||||
@Resource
|
||||
private PayOrderExtensionMapper orderExtensionMapper;
|
||||
@Resource
|
||||
private PayNoRedisDAO noRedisDAO;
|
||||
|
||||
@Resource
|
||||
private PayAppService appService;
|
||||
@ -136,8 +136,9 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
|
||||
// 2. 插入 PayOrderExtensionDO
|
||||
String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
|
||||
PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
|
||||
.setOrderId(order.getId()).setNo(generateOrderExtensionNo())
|
||||
.setOrderId(order.getId()).setNo(no)
|
||||
.setChannelId(channel.getId()).setChannelCode(channel.getCode())
|
||||
.setStatus(PayOrderStatusEnum.WAITING.getStatus());
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
@ -155,7 +156,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
|
||||
// 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
|
||||
if (unifiedOrderResp != null) {
|
||||
getSelf().notifyPayOrder(channel, unifiedOrderResp);
|
||||
getSelf().notifyOrder(channel, unifiedOrderResp);
|
||||
// 如有渠道错误码,则抛出业务异常,提示用户
|
||||
if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
|
||||
throw exception(ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
|
||||
@ -204,31 +205,12 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
return payProperties.getOrderNotifyUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
private String generateOrderExtensionNo() {
|
||||
// wx
|
||||
// 2014
|
||||
// 10
|
||||
// 27
|
||||
// 20
|
||||
// 09
|
||||
// 39
|
||||
// 5522657
|
||||
// a690389285100
|
||||
// 目前的算法
|
||||
// 时间序列,年月日时分秒 14 位
|
||||
// 纯随机,6 位 TODO 芋艿:此处估计是会有问题的,后续在调整
|
||||
return DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + // 时间序列
|
||||
RandomUtil.randomInt(100000, 999999) // 随机。为什么是这个范围,因为偷懒
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void notifyOrder(Long channelId, PayOrderRespDTO notify) {
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||
// 更新支付订单为已支付
|
||||
TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyPayOrder(channel, notify));
|
||||
TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,7 +220,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
* @param notify 通知
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class) // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效
|
||||
public void notifyPayOrder(PayChannelDO channel, PayOrderRespDTO notify) {
|
||||
public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) {
|
||||
// 情况一:支付成功的回调
|
||||
if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
|
||||
notifyOrderSuccess(channel, notify);
|
||||
@ -254,16 +236,16 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
|
||||
private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
|
||||
// 1. 更新 PayOrderExtensionDO 支付成功
|
||||
PayOrderExtensionDO orderExtension = updateOrderExtensionSuccess(notify);
|
||||
PayOrderExtensionDO orderExtension = updateOrderSuccess(notify);
|
||||
// 2. 更新 PayOrderDO 支付成功
|
||||
Pair<Boolean, PayOrderDO> order = updateOrderExtensionSuccess(channel, orderExtension, notify);
|
||||
if (order.getKey()) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
|
||||
Boolean paid = updateOrderSuccess(channel, orderExtension, notify);
|
||||
if (paid) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 插入支付通知记录
|
||||
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getValue().getId()).build());
|
||||
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(orderExtension.getOrderId()).build());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,7 +254,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
* @param notify 通知
|
||||
* @return PayOrderExtensionDO 对象
|
||||
*/
|
||||
private PayOrderExtensionDO updateOrderExtensionSuccess(PayOrderRespDTO notify) {
|
||||
private PayOrderExtensionDO updateOrderSuccess(PayOrderRespDTO notify) {
|
||||
// 1. 查询 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
|
||||
if (orderExtension == null) {
|
||||
@ -302,11 +284,10 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
* @param channel 支付渠道
|
||||
* @param orderExtension 支付拓展单
|
||||
* @param notify 通知回调
|
||||
* @return key:是否之前已经成功回调
|
||||
* value:PayOrderDO 对象
|
||||
* @return 是否之前已经成功回调
|
||||
*/
|
||||
private Pair<Boolean, PayOrderDO> updateOrderExtensionSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
|
||||
PayOrderRespDTO notify) {
|
||||
private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
|
||||
PayOrderRespDTO notify) {
|
||||
// 1. 判断 PayOrderDO 是否处于待支付
|
||||
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
|
||||
if (order == null) {
|
||||
@ -315,7 +296,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新
|
||||
&& Objects.equals(order.getExtensionId(), orderExtension.getId())) {
|
||||
log.info("[updateOrderExtensionSuccess][支付订单({}) 已经是已支付,无需更新]", order.getId());
|
||||
return Pair.of(true, order);
|
||||
return true;
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(ORDER_STATUS_IS_NOT_WAITING);
|
||||
@ -333,7 +314,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
throw exception(ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[updateOrderExtensionSuccess][支付订单({}) 更新为已支付]", order.getId());
|
||||
return Pair.of(false, order);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) {
|
||||
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.service.refund;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||
@ -24,6 +23,7 @@ import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
||||
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||
|
@ -1,27 +1,37 @@
|
||||
package cn.iocoder.yudao.module.pay.service.order;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
|
||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
|
||||
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
|
||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
||||
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@ -30,6 +40,7 @@ import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
@ -37,19 +48,18 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
|
||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link PayOrderServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋艿
|
||||
*/
|
||||
@Import({PayOrderServiceImpl.class})
|
||||
public class PayOrderServiceTest extends BaseDbUnitTest {
|
||||
@Import({PayOrderServiceImpl.class, PayNoRedisDAO.class})
|
||||
public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
||||
|
||||
@Resource
|
||||
private PayOrderServiceImpl orderService;
|
||||
@ -70,6 +80,11 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
|
||||
@MockBean
|
||||
private PayNotifyService notifyService;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
when(properties.getOrderNotifyUrl()).thenReturn("http://127.0.0.1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrderPage() {
|
||||
// mock 数据
|
||||
@ -237,7 +252,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
|
||||
public void testSubmitOrder_channelNotFound() {
|
||||
// mock 数据(order)
|
||||
PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
||||
.setAppId(1L));
|
||||
.setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
|
||||
orderMapper.insert(order);
|
||||
// 准备参数
|
||||
PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
|
||||
@ -257,54 +272,295 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
|
||||
|
||||
@Test // 调用 unifiedOrder 接口,返回存在渠道错误
|
||||
public void testSubmitOrder_channelError() {
|
||||
// mock 数据(order)
|
||||
PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
||||
.setAppId(1L));
|
||||
orderMapper.insert(order);
|
||||
PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
|
||||
.thenReturn(payOrderServiceImpl);
|
||||
|
||||
// mock 数据(order)
|
||||
PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
||||
.setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
|
||||
orderMapper.insert(order);
|
||||
// 准备参数
|
||||
PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
|
||||
.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
String userIp = randomString();
|
||||
// mock 方法(app)
|
||||
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
|
||||
when(appService.validPayApp(eq(1L))).thenReturn(app);
|
||||
// mock 方法(channel)
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
|
||||
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
|
||||
.thenReturn(channel);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法()
|
||||
PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o ->
|
||||
o.setChannelErrorCode("001").setChannelErrorMsg("模拟异常"));
|
||||
when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
|
||||
assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo());
|
||||
assertThat(payOrderUnifiedReqDTO)
|
||||
.extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime")
|
||||
.containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10",
|
||||
reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime());
|
||||
return true;
|
||||
}))).thenReturn(unifiedOrderResp);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> orderService.submitOrder(reqVO, userIp),
|
||||
ORDER_SUBMIT_CHANNEL_ERROR, "001", "模拟异常");
|
||||
// 断言,数据记录(PayOrderExtensionDO)
|
||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
|
||||
assertNotNull(orderExtension);
|
||||
assertThat(orderExtension).extracting("no", "orderId").isNotNull();
|
||||
assertThat(orderExtension)
|
||||
.extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
|
||||
"channelErrorCode", "channelErrorMsg", "channelNotifyData")
|
||||
.containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
|
||||
PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
|
||||
null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubmitOrder_success() {
|
||||
PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
|
||||
.thenReturn(payOrderServiceImpl);
|
||||
|
||||
// mock 数据(order)
|
||||
PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
||||
.setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
|
||||
orderMapper.insert(order);
|
||||
// 准备参数
|
||||
PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
|
||||
.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
String userIp = randomString();
|
||||
// mock 方法(app)
|
||||
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
|
||||
when(appService.validPayApp(eq(1L))).thenReturn(app);
|
||||
// mock 方法(channel)
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
|
||||
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
|
||||
.thenReturn(channel);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
|
||||
// mock 方法()
|
||||
PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null)
|
||||
.setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou"));
|
||||
when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
|
||||
assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo());
|
||||
assertThat(payOrderUnifiedReqDTO)
|
||||
.extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime")
|
||||
.containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10",
|
||||
reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime());
|
||||
return true;
|
||||
}))).thenReturn(unifiedOrderResp);
|
||||
|
||||
// 调用
|
||||
PayOrderSubmitRespVO result = orderService.submitOrder(reqVO, userIp);
|
||||
// 断言,数据记录(PayOrderExtensionDO)
|
||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
|
||||
assertNotNull(orderExtension);
|
||||
assertThat(orderExtension).extracting("no", "orderId").isNotNull();
|
||||
assertThat(orderExtension)
|
||||
.extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
|
||||
"channelErrorCode", "channelErrorMsg", "channelNotifyData")
|
||||
.containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
|
||||
PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
|
||||
null, null, null);
|
||||
// 断言,返回(PayOrderSubmitRespVO)
|
||||
assertThat(result)
|
||||
.extracting("status", "displayMode", "displayContent")
|
||||
.containsExactly(PayOrderStatusEnum.WAITING.getStatus(), PayOrderDisplayModeEnum.URL.getMode(), "tudou");
|
||||
// 断言,调用
|
||||
verify(payOrderServiceImpl).notifyOrder(same(channel), same(unifiedOrderResp));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyOrder_channelId() {
|
||||
PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
|
||||
.thenReturn(payOrderServiceImpl);
|
||||
// 准备参数
|
||||
Long channelId = 10L;
|
||||
PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class);
|
||||
// mock 方法(channel)
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
|
||||
when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
|
||||
|
||||
// 调用
|
||||
orderService.notifyOrder(channelId, notify);
|
||||
// 断言
|
||||
verify(payOrderServiceImpl).notifyOrder(same(channel), same(notify));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyOrderSuccess_orderExtension_notFound() {
|
||||
// 准备参数
|
||||
PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
|
||||
.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
String userIp = randomString();
|
||||
// mock 方法(app)
|
||||
PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
|
||||
when(appService.validPayApp(eq(1L))).thenReturn(app);
|
||||
// mock 方法(channel)
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
|
||||
.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
|
||||
when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
|
||||
.thenReturn(channel);
|
||||
// mock 方法(client)
|
||||
PayClient client = mock(PayClient.class);
|
||||
when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
|
||||
when(client.unifiedOrder(any(PayOrderUnifiedReqDTO.class))).thenThrow(new NullPointerException());
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
|
||||
PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
|
||||
o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()));
|
||||
|
||||
// 调用,并断言异常
|
||||
assertThrows(NullPointerException.class, () -> orderService.submitOrder(reqVO, userIp));
|
||||
// 断言,数据记录(PayOrderExtensionDO)
|
||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
|
||||
assertNotNull(orderExtension);
|
||||
assertThat(orderExtension).extracting("no", "orderId").isNotNull();
|
||||
assertThat(orderExtension)
|
||||
.extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
|
||||
"channelErrorCode", "channelErrorMsg", "channelNotifyData")
|
||||
.containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
|
||||
PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
|
||||
null, null, null);
|
||||
assertServiceException(() -> orderService.notifyOrder(channel, notify),
|
||||
ORDER_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test // 【成功】支付结果为等待中
|
||||
public void testSubmitOrder_waitingResult() {
|
||||
@Test
|
||||
public void testNotifyOrderSuccess_orderExtension_closed() {
|
||||
// mock 数据(PayOrderExtensionDO)
|
||||
PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
|
||||
.setNo("P110"));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// 准备参数
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
|
||||
PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
|
||||
o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
|
||||
.setOutTradeNo("P110"));
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> orderService.notifyOrder(channel, notify),
|
||||
ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
@Test // 【成功】支付结果为已完成
|
||||
public void testSubmitOrder_successResult() {
|
||||
@Test
|
||||
public void testNotifyOrderSuccess_order_notFound() {
|
||||
// mock 数据(PayOrderExtensionDO)
|
||||
PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.setNo("P110"));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// 准备参数
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
|
||||
PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
|
||||
o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
|
||||
.setOutTradeNo("P110"));
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> orderService.notifyOrder(channel, notify),
|
||||
ORDER_NOT_FOUND);
|
||||
// 断言 PayOrderExtensionDO :数据更新被回滚
|
||||
assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
|
||||
}
|
||||
|
||||
@Test // 【成功】支付结果为已关闭
|
||||
public void testSubmitOrder_closedResult() {
|
||||
@Test
|
||||
public void testNotifyOrderSuccess_order_closed() {
|
||||
testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.CLOSED.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyOrderSuccess_order_refund() {
|
||||
testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.REFUND.getStatus());
|
||||
}
|
||||
|
||||
private void testNotifyOrderSuccess_order_closedOrRefund(Integer status) {
|
||||
// mock 数据(PayOrderDO)
|
||||
PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status));
|
||||
orderMapper.insert(order);
|
||||
// mock 数据(PayOrderExtensionDO)
|
||||
PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.setNo("P110")
|
||||
.setOrderId(order.getId()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// 准备参数
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
|
||||
PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
|
||||
o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
|
||||
.setOutTradeNo("P110"));
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> orderService.notifyOrder(channel, notify),
|
||||
ORDER_STATUS_IS_NOT_WAITING);
|
||||
// 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
|
||||
assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyOrderSuccess_order_paid() {
|
||||
// mock 数据(PayOrderDO)
|
||||
PayOrderDO order = randomPojo(PayOrderDO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
|
||||
orderMapper.insert(order);
|
||||
// mock 数据(PayOrderExtensionDO)
|
||||
PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.setNo("P110")
|
||||
.setOrderId(order.getId()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// 重要:需要将 order 的 extensionId 更新下
|
||||
order.setExtensionId(orderExtension.getId());
|
||||
orderMapper.updateById(order);
|
||||
// 准备参数
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
|
||||
PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
|
||||
o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
|
||||
.setOutTradeNo("P110"));
|
||||
|
||||
// 调用,并断言异常
|
||||
orderService.notifyOrder(channel, notify);
|
||||
// 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
|
||||
assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
|
||||
// 断言 PayOrderDO :数据未更新,因为它是 SUCCESS
|
||||
assertPojoEquals(order, orderMapper.selectOne(null));
|
||||
// 断言,调用
|
||||
verify(notifyService, never()).createPayNotifyTask(any(PayNotifyTaskCreateReqDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyOrderSuccess_order_waiting() {
|
||||
// mock 数据(PayOrderDO)
|
||||
PayOrderDO order = randomPojo(PayOrderDO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
||||
.setPrice(10));
|
||||
orderMapper.insert(order);
|
||||
// mock 数据(PayOrderExtensionDO)
|
||||
PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
|
||||
o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
||||
.setNo("P110")
|
||||
.setOrderId(order.getId()));
|
||||
orderExtensionMapper.insert(orderExtension);
|
||||
// 准备参数
|
||||
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
|
||||
.setFeeRate(0.1D));
|
||||
PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
|
||||
o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
|
||||
.setOutTradeNo("P110"));
|
||||
|
||||
// 调用,并断言异常
|
||||
orderService.notifyOrder(channel, notify);
|
||||
// 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
|
||||
orderExtension.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.setChannelNotifyData(toJsonString(notify));
|
||||
assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null),
|
||||
"updateTime", "updater");
|
||||
// 断言 PayOrderDO :数据未更新,因为它是 SUCCESS
|
||||
order.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
|
||||
.setChannelId(10L).setChannelCode(channel.getCode())
|
||||
.setSuccessTime(notify.getSuccessTime()).setExtensionId(orderExtension.getId()).setNo(orderExtension.getNo())
|
||||
.setChannelOrderNo(notify.getChannelOrderNo()).setChannelUserId(notify.getChannelUserId())
|
||||
.setChannelFeeRate(0.1D).setChannelFeePrice(1);
|
||||
assertPojoEquals(order, orderMapper.selectOne(null),
|
||||
"updateTime", "updater");
|
||||
// 断言,调用
|
||||
verify(notifyService).createPayNotifyTask(argThat(new ArgumentMatcher<PayNotifyTaskCreateReqDTO>() {
|
||||
@Override
|
||||
public boolean matches(PayNotifyTaskCreateReqDTO reqDTO) {
|
||||
assertEquals(reqDTO.getType(), PayNotifyTypeEnum.ORDER.getType());
|
||||
assertEquals(reqDTO.getDataId(), orderExtension.getOrderId());
|
||||
return true;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package cn.iocoder.yudao.module.pay.service.refund;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
|
||||
@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
|
||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
||||
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||
@ -29,7 +30,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Import(PayRefundServiceImpl.class)
|
||||
public class PayRefundServiceTest extends BaseDbUnitTest {
|
||||
public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
||||
|
||||
@Resource
|
||||
private PayRefundServiceImpl refundService;
|
||||
|
@ -1,4 +1,5 @@
|
||||
DELETE FROM pay_app;
|
||||
DELETE FROM pay_channel;
|
||||
DELETE FROM pay_order;
|
||||
DELETE FROM pay_order_extension;
|
||||
DELETE FROM pay_refund;
|
||||
|
@ -44,7 +44,7 @@ CREATE TABLE IF NOT EXISTS `pay_order` (
|
||||
`channel_fee_price` bigint(20) DEFAULT 0,
|
||||
`status` tinyint(4) NOT NULL,
|
||||
`user_ip` varchar(50) NOT NULL,
|
||||
`expire_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`success_time` datetime(0) DEFAULT CURRENT_TIMESTAMP,
|
||||
`notify_time` datetime(0) DEFAULT CURRENT_TIMESTAMP,
|
||||
`extension_id` bigint(20) DEFAULT NULL,
|
||||
@ -71,7 +71,7 @@ CREATE TABLE IF NOT EXISTS `pay_order_extension` (
|
||||
`channel_extras` varchar(1024) NULL DEFAULT NULL,
|
||||
`channel_error_code` varchar(64) NULL,
|
||||
`channel_error_msg` varchar(64) NULL,
|
||||
`channel_notify_data` varchar(64) NULL,
|
||||
`channel_notify_data` varchar(1024) NULL,
|
||||
`creator` varchar(64) NULL DEFAULT '',
|
||||
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updater` varchar(64) NULL DEFAULT '',
|
||||
|
Loading…
Reference in New Issue
Block a user