1. 增加 XXL-Job starter
2. 迁移 pay 服务的 Job 逻辑
This commit is contained in:
parent
04f53da686
commit
efaeb5b39d
66
README.md
66
README.md
@ -34,6 +34,12 @@
|
||||
>
|
||||
> 迫切希望,有前端能力不错的小伙伴,加入我们,一起来完善「芋道商城」。
|
||||
|
||||
## 管理后台
|
||||
|
||||
体验传送门:<http://dashboard.shop.iocoder.cn>
|
||||
|
||||
![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-admin-min.gif)
|
||||
|
||||
## H5 商城
|
||||
|
||||
体验传送门:<http://h5.shop.iocoder.cn>
|
||||
@ -42,14 +48,6 @@
|
||||
|
||||
![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-h5-min.gif)
|
||||
|
||||
## 管理后台
|
||||
|
||||
体验传送门:<http://dashboard.shop.iocoder.cn>
|
||||
|
||||
*2M 带宽小水管,访问略微有点慢*
|
||||
|
||||
![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-admin-min.gif)
|
||||
|
||||
## 其它演示
|
||||
|
||||
下面,我们会提供目前用到的中间件的管理平台。
|
||||
@ -81,10 +79,10 @@
|
||||
|
||||
**XXL-Job Console**
|
||||
|
||||
* 地址:<http://job.shop.iocoder.cn>
|
||||
* 管理员账号:admin / 233666
|
||||
* 地址:<http://xxl-job.shop.iocoder.cn>
|
||||
* 管理员账号:admin / 123456
|
||||
|
||||
> 教程:[《芋道 RocketMQ 安装部署》](http://www.iocoder.cn/XXL-JOB/install/?onemall)
|
||||
> 教程:[《芋道 XXL-Job 安装部署》](http://www.iocoder.cn/XXL-JOB/install/?onemall)
|
||||
|
||||
**Sentinel Console**
|
||||
|
||||
@ -107,33 +105,35 @@ TODO 此处应有一个架构图的装逼 JPG 图。
|
||||
|
||||
| 模块 | 名称 | 端口 | |
|
||||
| --- | --- | --- | --- |
|
||||
| `admin-web` | 【前端】管理后台 | HTTP 8080 | |
|
||||
| `mobile-web` | 【前端】商城 H5 | HTTP 8000 | |
|
||||
| `system-application` | 管理员 HTTP 服务 | HTTP 18083 | [接口文档](http://api.shop.iocoder.cn/admin-api/doc.html) |
|
||||
| `user-application` | 用户 HTTP 服务 | HTTP 18082 | [接口文档](http://api.shop.iocoder.cn/user-api/doc.html) |
|
||||
| `product-application` | 商品 HTTP 服务 | HTTP 18081 | [接口文档](http://api.shop.iocoder.cn/product-api/doc.html) |
|
||||
| `pay-application` | 支付 HTTP 服务 | HTTP 18084 | [接口文档](http://api.shop.iocoder.cn/pay-api/doc.html) |
|
||||
| `promotion-application` | 促销 HTTP 服务 | HTTP 18085 | [接口文档](http://api.shop.iocoder.cn/promotion-api/doc.html) |
|
||||
| `search-application` | 搜索 HTTP 服务 | HTTP 18086 | [接口文档](http://api.shop.iocoder.cn/search-api/doc.html) |
|
||||
| `order-application` | 订单 HTTP 服务 | HTTP 18088 | [接口文档](http://api.shop.iocoder.cn/order-api/doc.html) |
|
||||
| [`admin-dashboard-vue`](https://github.com/YunaiV/onemall-web/tree/master/admin-dashboard-vue) | 【前端】管理后台 | HTTP 9527 | |
|
||||
| [`user-dashboard-vue`](https://github.com/YunaiV/onemall-web/tree/master/user-h5-vue) | 【前端】商城平台 | HTTP 8080 | |
|
||||
| | | |
|
||||
| | | |
|
||||
| `management-web-app` | 【后端】管理平台 HTTP 服务 | HTTP 18083 | [接口文档](http://api-dashboard.shop.iocoder.cn/management-api/doc.html) |
|
||||
| `shop-web-app` | 【后端】商城平台 HTTP 服务 | HTTP 18084 | [接口文档](http://api-h5.shop.iocoder.cn/shop-api/doc.html) |
|
||||
| | | |
|
||||
| | | |
|
||||
| `system-service-project` | 系统 RPC 服务 | 随机 |
|
||||
| `user-service-project` | 用户 RPC 服务 | 随机 | |
|
||||
| `promotion-service-project` | 营销 RPC 服务 | 随机 | |
|
||||
| `pay-service-project` | 支付 RPC 服务 | 随机 | |
|
||||
| `trade-service-project` | 交易 RPC 服务 | 随机 | |
|
||||
| `product-service-project` | 商品 RPC 服务 | 随机 | |
|
||||
| `search-service-project` | 搜索å RPC 服务 | 随机 | |
|
||||
|
||||
-------
|
||||
|
||||
后端项目,目前的项目结构如下:
|
||||
|
||||
```Java
|
||||
[-] xxx
|
||||
├──[-] xxx-application // 提供对外 HTTP API 。
|
||||
├──[-] xxx-service-api // 提供 Dubbo 服务 API 。
|
||||
├──[-] xxx-service-impl // 提供 Dubbo 服务 Service 实现。
|
||||
[-] xxx-web-app // 提供对外 HTTP API。
|
||||
|
||||
[-] xxx-service-project
|
||||
├──[-] xxx-service-api // 提供对内 RPC API 。
|
||||
├──[-] xxx-service-app // 提供对内 RPC 实现。
|
||||
├──[-] xxx-service-integration-test // 集成测试。
|
||||
```
|
||||
|
||||
考虑到大多数公司,无需拆分的特别细,并且过多 JVM 带来的服务器成本。所以目前的设定是:
|
||||
|
||||
* `xxx-service-impl` 内嵌在 `xxx-application` 中运行。
|
||||
* MQ 消费者、定时器执行器,内嵌在 `xxx-service-impl` 中运行。
|
||||
|
||||
也就是说,一个 `xxx-application` 启动后,该模块就完整启动了。
|
||||
|
||||
## 技术栈
|
||||
|
||||
@ -165,8 +165,6 @@ TODO 此处应有一个架构图的装逼 JPG 图。
|
||||
|
||||
### 前端
|
||||
|
||||
商城 H5 和管理后台,分别采用了 Vue 和 React ,基于其适合的场景考虑。具体的,可以看看 [《为什么 React 比 Vue 更适合大型应用?》](https://www.zhihu.com/question/314761485/answer/615318460) 的讨论。
|
||||
|
||||
**商城 H5**
|
||||
|
||||
| 框架 | 说明 | 版本 |
|
||||
@ -178,8 +176,8 @@ TODO 此处应有一个架构图的装逼 JPG 图。
|
||||
|
||||
| 框架 | 说明 | 版本 |
|
||||
| --- | --- | --- |
|
||||
| [React](https://reactjs.org/) | JavaScript 框架 | 16.7.0 |
|
||||
| [Ant Design](https://ant.design/docs/react/introduce-cn) | React UI 组件库 | 3.13.0 |
|
||||
| [Vue](https://cn.vuejs.org/index.html) | JavaScript 框架 | 2.5.17 |
|
||||
| [Vue Element Admin](https://ant.design/docs/react/introduce-cn) | 后台前端解决方案 | - |
|
||||
|
||||
### 监控
|
||||
|
||||
|
34
common/mall-spring-boot-starter-xxl-job/pom.xml
Normal file
34
common/mall-spring-boot-starter-xxl-job/pom.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>common</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mall-spring-boot-starter-xxl-job</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,56 @@
|
||||
package cn.iocoder.mall.xxljob.config;
|
||||
|
||||
import com.xxl.job.core.executor.XxlJobExecutor;
|
||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* XXL-Job 自动配置类
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(XxlJobSpringExecutor.class)
|
||||
@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
@EnableConfigurationProperties({XxlJobProperties.class})
|
||||
public class XxlJobAutoConfiguration {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(XxlJobAutoConfiguration.class);
|
||||
|
||||
private final XxlJobProperties properties;
|
||||
|
||||
public XxlJobAutoConfiguration(XxlJobProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public XxlJobExecutor xxlJobExecutor() {
|
||||
LOGGER.info("初始化 XXL-Job 执行器的配置");
|
||||
|
||||
// 参数校验
|
||||
XxlJobProperties.AdminProperties admin = this.properties.getAdmin();
|
||||
XxlJobProperties.ExecutorProperties executor = this.properties.getExecutor();
|
||||
Objects.requireNonNull(admin, "xxl job admin properties must not be null.");
|
||||
Objects.requireNonNull(executor, "xxl job executor properties must not be null.");
|
||||
|
||||
// 初始化执行器
|
||||
XxlJobExecutor xxlJobExecutor = new XxlJobSpringExecutor();
|
||||
xxlJobExecutor.setIp(executor.getIp());
|
||||
xxlJobExecutor.setPort(executor.getPort());
|
||||
xxlJobExecutor.setAppname(executor.getAppName());
|
||||
xxlJobExecutor.setLogPath(executor.getLogPath());
|
||||
xxlJobExecutor.setLogRetentionDays(executor.getLogRetentionDays());
|
||||
xxlJobExecutor.setAdminAddresses(admin.getAddresses());
|
||||
xxlJobExecutor.setAccessToken(this.properties.getAccessToken());
|
||||
return xxlJobExecutor;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
package cn.iocoder.mall.xxljob.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* XXL-Job 配置类
|
||||
*/
|
||||
@ConfigurationProperties("xxl.job")
|
||||
public class XxlJobProperties {
|
||||
|
||||
/**
|
||||
* 是否开启,默认为 true 关闭
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String accessToken;
|
||||
/**
|
||||
* 控制器配置
|
||||
*/
|
||||
private AdminProperties admin;
|
||||
/**
|
||||
* 执行器配置
|
||||
*/
|
||||
private ExecutorProperties executor;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
if (enabled != null) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
if (accessToken != null && accessToken.trim().length() > 0) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
public AdminProperties getAdmin() {
|
||||
return admin;
|
||||
}
|
||||
|
||||
public void setAdmin(AdminProperties admin) {
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
public ExecutorProperties getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
public void setExecutor(ExecutorProperties executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* XXL-Job 调度器配置类
|
||||
*/
|
||||
public static class AdminProperties {
|
||||
|
||||
/**
|
||||
* 调度器地址
|
||||
*/
|
||||
private String addresses;
|
||||
|
||||
public String getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public void setAddresses(String addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AdminProperties{" +
|
||||
"addresses='" + addresses + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* XXL-Job 执行器配置类
|
||||
*/
|
||||
public static class ExecutorProperties {
|
||||
|
||||
/**
|
||||
* 默认端口
|
||||
*
|
||||
* 这里使用 -1 表示随机
|
||||
*/
|
||||
private static final Integer PORT_DEFAULT = -1;
|
||||
|
||||
/**
|
||||
* 默认日志保留天数
|
||||
*
|
||||
* 默认为 -1,不清理,永久保留
|
||||
*/
|
||||
private static final Integer LOG_RETENTION_DAYS_DEFAULT = -1;
|
||||
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
private String appName;
|
||||
/**
|
||||
* 执行器的 IP
|
||||
*/
|
||||
private String ip;
|
||||
/**
|
||||
* 执行器的 Port
|
||||
*/
|
||||
private Integer port = PORT_DEFAULT;
|
||||
/**
|
||||
* 日志地址
|
||||
*/
|
||||
private String logPath;
|
||||
/**
|
||||
* 日志保留天数
|
||||
*/
|
||||
private Integer logRetentionDays = LOG_RETENTION_DAYS_DEFAULT;
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public void setAppName(String appName) {
|
||||
this.appName = appName;
|
||||
}
|
||||
|
||||
public String getLogPath() {
|
||||
return logPath;
|
||||
}
|
||||
|
||||
public void setLogPath(String logPath) {
|
||||
this.logPath = logPath;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public Integer getLogRetentionDays() {
|
||||
return logRetentionDays;
|
||||
}
|
||||
|
||||
public void setLogRetentionDays(Integer logRetentionDays) {
|
||||
this.logRetentionDays = logRetentionDays;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.xxljob.config.XxlJobAutoConfiguration
|
@ -24,6 +24,7 @@
|
||||
<module>mall-spring-boot-starter-dubbo</module>
|
||||
<module>mall-spring-boot-starter-system-error-code</module>
|
||||
<module>mall-spring-boot-starter-rocketmq</module>
|
||||
<module>mall-spring-boot-starter-xxl-job</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -43,9 +43,9 @@
|
||||
<!-- RPC 相关 -->
|
||||
<dubbo.version>2.7.7</dubbo.version>
|
||||
<!-- MQ 相关 -->
|
||||
<rocketmq-spring-boot-starter.version>2.1.0</rocketmq-spring-boot-starter.version>
|
||||
<rocketmq-spring-boot-starter.version>2.1.1</rocketmq-spring-boot-starter.version>
|
||||
<!-- Job 相关 -->
|
||||
<xxl-job.version>2.0.1</xxl-job.version>
|
||||
<xxl-job.version>2.2.0</xxl-job.version>
|
||||
<!-- Transaction 相关 -->
|
||||
<seata.version>1.1.0</seata.version>
|
||||
<!-- 云服务相关 -->
|
||||
@ -249,6 +249,12 @@
|
||||
<version>${xxl-job.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-xxl-job</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba.nacos</groupId>-->
|
||||
|
@ -1,53 +0,0 @@
|
||||
package cn.iocoder.mall.order.biz.dao.order;
|
||||
|
||||
import cn.iocoder.mall.order.biz.dataobject.OrderDO;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* 订单 mapper
|
||||
*
|
||||
* @author Sin
|
||||
* @time 2019-03-16 15:09
|
||||
*/
|
||||
@Repository
|
||||
public interface OrderMapper extends BaseMapper<OrderDO> {
|
||||
|
||||
// /**
|
||||
// * 更新 - 根据 id 更新
|
||||
// *
|
||||
// * @param orderDO
|
||||
// * @return
|
||||
// */
|
||||
// int updateById(OrderDO orderDO);
|
||||
//
|
||||
// int updateByIdAndStatus(@Param("id") Integer id,
|
||||
// @Param("status") Integer status,
|
||||
// @Param("updateObj") OrderDO updateObj);
|
||||
//
|
||||
// /**
|
||||
// * 查询 - 根据id 查询
|
||||
// *
|
||||
// * @param id
|
||||
// * @return
|
||||
// */
|
||||
// OrderDO selectById(
|
||||
// @Param("id") Integer id
|
||||
// );
|
||||
//
|
||||
// /**
|
||||
// * 查询 - 后台分页page
|
||||
// *
|
||||
// * @param orderQueryDTO
|
||||
// * @return
|
||||
// */
|
||||
// int selectPageCount(OrderQueryDTO orderQueryDTO);
|
||||
//
|
||||
// /**
|
||||
// * 查询 - 后台分页page
|
||||
// *
|
||||
// * @param orderQueryDTO
|
||||
// * @return
|
||||
// */
|
||||
// List<OrderDO> selectPage(OrderQueryDTO orderQueryDTO);
|
||||
}
|
@ -17,28 +17,6 @@ import java.util.List;
|
||||
@Repository
|
||||
public interface OrderMapper extends BaseMapper<OrderDO> {
|
||||
|
||||
/**
|
||||
* 更新 - 根据 id 更新
|
||||
*
|
||||
* @param orderDO
|
||||
* @return
|
||||
*/
|
||||
int updateById(OrderDO orderDO);
|
||||
|
||||
int updateByIdAndStatus(@Param("id") Integer id,
|
||||
@Param("status") Integer status,
|
||||
@Param("updateObj") OrderDO updateObj);
|
||||
|
||||
/**
|
||||
* 查询 - 根据id 查询
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
OrderDO selectById(
|
||||
@Param("id") Integer id
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询 - 后台分页page
|
||||
*
|
||||
|
@ -326,31 +326,6 @@ public class OrderServiceImpl implements OrderService {
|
||||
return CommonResult.success(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String updatePaySuccess(String orderId, Integer payAmount) {
|
||||
OrderDO order = orderMapper.selectById(Integer.valueOf(orderId));
|
||||
if (order == null) { // 订单不存在
|
||||
return ServiceExceptionUtil.error(OrderErrorCodeEnum.ORDER_NOT_EXISTENT.getCode()).getMessage();
|
||||
}
|
||||
if (!order.getStatus().equals(OrderStatusEnum.WAITING_PAYMENT.getValue())) { // 状态不处于等待支付
|
||||
return ServiceExceptionUtil.error(OrderErrorCodeEnum.ORDER_STATUS_NOT_WAITING_PAYMENT.getCode()).getMessage();
|
||||
}
|
||||
if (!order.getPresentPrice().equals(payAmount)) { // 支付金额不正确
|
||||
return ServiceExceptionUtil.error(OrderErrorCodeEnum.ORDER_PAY_AMOUNT_ERROR.getCode()).getMessage();
|
||||
}
|
||||
// 更新 OrderDO 状态为已支付,等待发货
|
||||
OrderDO updateOrderObj = new OrderDO()
|
||||
.setStatus(OrderStatusEnum.WAIT_SHIPMENT.getValue())
|
||||
.setPayAmount(payAmount)
|
||||
.setPaymentTime(new Date());
|
||||
int updateCount = orderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), updateOrderObj);
|
||||
if (updateCount <= 0) {
|
||||
return ServiceExceptionUtil.error(OrderErrorCodeEnum.ORDER_STATUS_NOT_WAITING_PAYMENT.getCode()).getMessage();
|
||||
}
|
||||
// TODO FROM 芋艿 to 小范,把更新 OrderItem 给补全。
|
||||
return "success";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult listenerConfirmGoods() {
|
||||
return null;
|
||||
|
@ -2,100 +2,6 @@
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.mall.order.biz.dao.order.OrderMapper">
|
||||
|
||||
<sql id="FIELDS">
|
||||
id, user_id, order_no, buy_price, discount_price, logistics_price, present_price, pay_amount,
|
||||
payment_time, delivery_time, receiver_time, closing_time,
|
||||
has_return_exchange,
|
||||
status, remark, create_time, update_time, `deleted`
|
||||
</sql>
|
||||
|
||||
<sql id="updateFieldSql" >
|
||||
<set>
|
||||
<if test="orderNo != null">
|
||||
, order_no = #{orderNo}
|
||||
</if>
|
||||
<if test="buyPrice != null">
|
||||
, buy_price = #{buyPrice}
|
||||
</if>
|
||||
<if test="discountPrice != null">
|
||||
, discount_price = #{discountPrice}
|
||||
</if>
|
||||
<if test="logisticsPrice != null">
|
||||
, logistics_price = #{logisticsPrice}
|
||||
</if>
|
||||
<if test="logisticsPrice != null">
|
||||
, logistics_price = #{logisticsPrice}
|
||||
</if>
|
||||
<if test="presentPrice != null">
|
||||
, present_price = #{presentPrice}
|
||||
</if>
|
||||
<if test="payAmount != null">
|
||||
, pay_amount = #{payAmount}
|
||||
</if>
|
||||
<if test="deliveryTime != null">
|
||||
, delivery_time = #{deliveryTime}
|
||||
</if>
|
||||
<if test="paymentTime != null">
|
||||
, payment_time = #{paymentTime}
|
||||
</if>
|
||||
<if test="receiverTime != null">
|
||||
, receiver_time = #{receiverTime}
|
||||
</if>
|
||||
<if test="closingTime != null">
|
||||
, closing_time = #{closingTime}
|
||||
</if>
|
||||
<if test="hasReturnExchange != null">
|
||||
, has_return_exchange = #{hasReturnExchange}
|
||||
</if>
|
||||
|
||||
<if test="status != null">
|
||||
, status = #{status}
|
||||
</if>
|
||||
<if test="remark != null">
|
||||
, remark = #{remark}
|
||||
</if>
|
||||
<if test="deleted != null">
|
||||
, `deleted` = #{deleted}
|
||||
</if>
|
||||
<if test="createTime != null">
|
||||
, create_time = #{createTime}
|
||||
</if>
|
||||
<if test="updateTime != null">
|
||||
, update_time = #{updateTime}
|
||||
</if>
|
||||
</set>
|
||||
</sql>
|
||||
|
||||
<update id="updateById" parameterType="OrderDO">
|
||||
UPDATE `orders`
|
||||
<include refid="updateFieldSql" />
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updateByIdAndStatus">
|
||||
UPDATE `orders`
|
||||
<set>
|
||||
<if test="updateObj.payAmount != null">
|
||||
, pay_amount = #{updateObj.payAmount}
|
||||
</if>
|
||||
<if test="updateObj.paymentTime != null">
|
||||
, payment_time = #{updateObj.paymentTime}
|
||||
</if>
|
||||
<if test="updateObj.status != null">
|
||||
, status = #{updateObj.status}
|
||||
</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
AND status = #{status}
|
||||
</update>
|
||||
|
||||
<select id="selectById" resultType="cn.iocoder.mall.order.biz.dataobject.OrderDO">
|
||||
SELECT
|
||||
<include refid="FIELDS" />
|
||||
FROM `orders`
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<sql id="selectWhere">
|
||||
<if test="status != null">
|
||||
AND `status` = #{status}
|
||||
|
@ -1,47 +0,0 @@
|
||||
package cn.iocoder.mall.pay.biz.config;
|
||||
|
||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
@Configuration
|
||||
@Profile("dev")
|
||||
public class XxlJobConfiguration {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(XxlJobConfiguration.class);
|
||||
|
||||
@Value("${xxl.job.admin.addresses}")
|
||||
private String adminAddresses;
|
||||
@Value("${xxl.job.executor.appname}")
|
||||
private String appName;
|
||||
@Value("${xxl.job.executor.ip}")
|
||||
private String ip;
|
||||
@Value("${xxl.job.executor.port}")
|
||||
private int port;
|
||||
@Value("${xxl.job.accessToken}")
|
||||
private String accessToken;
|
||||
@Value("${xxl.job.executor.logpath}")
|
||||
private String logPath;
|
||||
@Value("${xxl.job.executor.logretentiondays}")
|
||||
private int logRetentionDays;
|
||||
|
||||
@Bean(initMethod = "start", destroyMethod = "destroy")
|
||||
public XxlJobSpringExecutor xxlJobExecutor() {
|
||||
logger.info(">>>>>>>>>>> xxl-job config init.");
|
||||
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
|
||||
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
|
||||
xxlJobSpringExecutor.setAppName(appName);
|
||||
xxlJobSpringExecutor.setIp(ip);
|
||||
xxlJobSpringExecutor.setPort(port);
|
||||
xxlJobSpringExecutor.setAccessToken(accessToken);
|
||||
xxlJobSpringExecutor.setLogPath(logPath);
|
||||
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
|
||||
|
||||
return xxlJobSpringExecutor;
|
||||
}
|
||||
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package cn.iocoder.mall.pay.biz.job;
|
||||
|
||||
import cn.iocoder.mall.pay.biz.dao.PayNotifyTaskMapper;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.pay.biz.service.PayNotifyServiceImpl;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.handler.IJobHandler;
|
||||
import com.xxl.job.core.handler.annotation.JobHandler;
|
||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 支付通知重试 Job
|
||||
*/
|
||||
@Component
|
||||
@JobHandler(value = "payTransactionNotifyJob")
|
||||
public class PayNotifyJob extends IJobHandler {
|
||||
|
||||
@Autowired
|
||||
private PayNotifyTaskMapper payTransactionNotifyTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private PayNotifyServiceImpl payNotifyService;
|
||||
|
||||
@Resource
|
||||
private RocketMQTemplate rocketMQTemplate;
|
||||
|
||||
@Override
|
||||
public ReturnT<String> execute(String param) {
|
||||
// 获得需要通知的任务
|
||||
List<PayNotifyTaskDO> notifyTasks = payTransactionNotifyTaskMapper.selectByNotify();
|
||||
// 循环任务,发送通知
|
||||
for (PayNotifyTaskDO notifyTask : notifyTasks) {
|
||||
// 发送 MQ
|
||||
payNotifyService.sendNotifyMessage(notifyTask);
|
||||
// 更新最后通知时间
|
||||
// 1. 这样操作,虽然可能会出现 MQ 消费快于下面 PayTransactionNotifyTaskDO 的更新语句。但是,因为更新字段不同,所以不会有问题。
|
||||
// 2. 换个视角,如果先更新 PayTransactionNotifyTaskDO ,再发送 MQ 消息。如果 MQ 消息发送失败,则 PayTransactionNotifyTaskDO 再也不会被轮询到了。
|
||||
// 3. 当然,最最最完美的话,就是做事务消息,不过这样又过于复杂~
|
||||
PayNotifyTaskDO updateNotifyTask = new PayNotifyTaskDO()
|
||||
.setId(notifyTask.getId()).setLastExecuteTime(new Date());
|
||||
payTransactionNotifyTaskMapper.update(updateNotifyTask);
|
||||
}
|
||||
return new ReturnT<>("执行通知数:" + notifyTasks.size());
|
||||
}
|
||||
|
||||
}
|
@ -80,7 +80,4 @@ public class PayTransactionServiceImpl implements PayTransactionService {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
# xxl-job
|
||||
xxl:
|
||||
job:
|
||||
admin:
|
||||
addresses: http://s1.iocoder.cn:18079/
|
||||
executor:
|
||||
appname: pay-job-executor
|
||||
ip:
|
||||
port: 0
|
||||
logpath: /Users/yunai/logs/xxl-job/
|
||||
logretentiondays: 1
|
||||
accessToken:
|
@ -1,32 +0,0 @@
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.ReferenceConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.rpc.service.GenericService;
|
||||
|
||||
public class DubboGenericInvokerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationConfig application = new ApplicationConfig();
|
||||
application.setName("api-generic-consumer");
|
||||
|
||||
RegistryConfig registry = new RegistryConfig();
|
||||
registry.setAddress("zookeeper://127.0.0.1:2181");
|
||||
|
||||
application.setRegistry(registry);
|
||||
|
||||
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
|
||||
// 弱类型接口名
|
||||
reference.setInterface("cn.iocoder.mall.order.api.OrderService");
|
||||
// 声明为泛化接口
|
||||
reference.setGeneric(true);
|
||||
|
||||
reference.setApplication(application);
|
||||
|
||||
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
|
||||
GenericService genericService = reference.get();
|
||||
|
||||
String name = (String) genericService.$invoke("updatePaySuccess", new String[]{String.class.getName()}, new Object[]{"1"});
|
||||
System.out.println(name);
|
||||
}
|
||||
|
||||
}
|
@ -37,6 +37,12 @@
|
||||
<artifactId>mall-spring-boot-starter-rocketmq</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-xxl-job</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Registry 和 Config 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
|
@ -46,9 +46,15 @@ public class DubboReferencePool {
|
||||
@Value("${dubbo.application.name}")
|
||||
private String dubboApplicationName;
|
||||
|
||||
public ReferenceMeta getReferenceMeta(String notifyUrl) {
|
||||
DubboReferencePool.ReferenceMeta referenceMeta = referenceMetaCache.getUnchecked(notifyUrl);
|
||||
Assert.notNull(referenceMeta, String.format("notifyUrl(%s) 不存在对应的 ReferenceMeta 对象", notifyUrl));
|
||||
return referenceMeta;
|
||||
}
|
||||
|
||||
private ReferenceMeta createGenericService(String notifyUrl) {
|
||||
// 使用 # 号分隔,格式为 服务名#方法名#版本号
|
||||
List<String> notifyUrlParts = StringUtils.split(notifyUrl, "#");
|
||||
List<String> notifyUrlParts = this.parseNotifyUrl(notifyUrl);
|
||||
// 创建 ApplicationConfig 对象
|
||||
ApplicationConfig application = new ApplicationConfig();
|
||||
application.setName(dubboApplicationName);
|
||||
@ -69,10 +75,9 @@ public class DubboReferencePool {
|
||||
return new ReferenceMeta(reference, genericService, notifyUrlParts.get(1));
|
||||
}
|
||||
|
||||
public ReferenceMeta getReferenceMeta(String notifyUrl) {
|
||||
DubboReferencePool.ReferenceMeta referenceMeta = referenceMetaCache.getUnchecked(notifyUrl);
|
||||
Assert.notNull(referenceMeta, String.format("notifyUrl(%s) 不存在对应的 ReferenceMeta 对象", notifyUrl));
|
||||
return referenceMeta;
|
||||
// TODO 芋艿,后续重构成一个对象
|
||||
private List<String> parseNotifyUrl(String notifyUrl) {
|
||||
return StringUtils.split(notifyUrl, "#");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package cn.iocoder.mall.payservice.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
/**
|
||||
* Spring Aop 配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
|
||||
public class AopConfiguration {
|
||||
}
|
@ -4,6 +4,8 @@ import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.PayRefundSuccessMessage;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.PayTransactionSuccessMessage;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
@ -11,8 +13,18 @@ public interface PayNotifyConvert {
|
||||
|
||||
PayNotifyConvert INSTANCE = Mappers.getMapper(PayNotifyConvert.class);
|
||||
|
||||
PayTransactionSuccessMessage convertTransaction(PayNotifyTaskDO payTransactionNotifyTaskDO);
|
||||
@Mappings({
|
||||
@Mapping(source = "transaction.transactionId", target = "transactionId"),
|
||||
@Mapping(source = "transaction.orderId", target = "orderId"),
|
||||
})
|
||||
PayTransactionSuccessMessage convertTransaction(PayNotifyTaskDO entity);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "refund.transactionId", target = "transactionId"),
|
||||
@Mapping(source = "refund.orderId", target = "orderId"),
|
||||
@Mapping(source = "refund.refundId", target = "refundId"),
|
||||
})
|
||||
PayRefundSuccessMessage convertRefund(PayNotifyTaskDO entity);
|
||||
|
||||
PayRefundSuccessMessage convertRefund(PayNotifyTaskDO payTransactionNotifyTaskDO);
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactio
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyType;
|
||||
import cn.iocoder.mall.payservice.service.transaction.PayTransactionService;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
|
||||
@ -56,25 +55,18 @@ public class PayNotifyTaskDO extends DeletableDO {
|
||||
* 外键 {@link PayNotifyStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 是否激活中,即处于正在 MQ 异步通知中
|
||||
*
|
||||
* @see cn.iocoder.mall.payservice.job.notify.PayNotifyRetryJob
|
||||
*/
|
||||
private Boolean active;
|
||||
/**
|
||||
* 下一次通知时间
|
||||
*/
|
||||
private Date nextNotifyTime;
|
||||
/**
|
||||
* 最后一次执行时间
|
||||
*
|
||||
* 这个字段,需要结合 {@link #nextNotifyTime} 一起使用。
|
||||
*
|
||||
* 1. 初始时,{@link PayTransactionService#updateTransactionPaySuccess(Integer, String)}
|
||||
* nextNotifyTime 为当前时间 + 15 秒
|
||||
* lastExecuteTime 为空
|
||||
* 并发送给 MQ ,执行执行
|
||||
*
|
||||
* 2. MQ 消费时,更新 lastExecuteTime 为当时时间
|
||||
*
|
||||
* 3. 定时任务,扫描 nextNotifyTime < lastExecuteTime 的任务
|
||||
* nextNotifyTime 为当前时间 + N 秒。具体的 N ,由第几次通知决定
|
||||
* lastExecuteTime 为当前时间
|
||||
*/
|
||||
private Date lastExecuteTime;
|
||||
/**
|
||||
|
@ -16,16 +16,21 @@ public interface PayNotifyTaskMapper extends BaseMapper<PayNotifyTaskDO> {
|
||||
*
|
||||
* 1. status 非成功
|
||||
* 2. nextNotifyTime 小于当前时间
|
||||
* 3. lastExecuteTime > nextNotifyTime
|
||||
* 3. active 为 false 并未正在执行中
|
||||
*
|
||||
* @return PayTransactionNotifyTaskDO 数组
|
||||
*/
|
||||
default List<PayNotifyTaskDO> selectListByNotify() {
|
||||
return selectList(new QueryWrapper<PayNotifyTaskDO>()
|
||||
.in("status", PayNotifyStatusEnum.WAITING.getName(), PayNotifyStatusEnum.REQUEST_SUCCESS.getName(),
|
||||
PayNotifyStatusEnum.REQUEST_FAILURE.getName())
|
||||
.in("status", PayNotifyStatusEnum.WAITING.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(),
|
||||
PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
|
||||
.le("next_notify_time", "NOW()")
|
||||
.gt("last_execute_time", "next_notify_time"));
|
||||
.eq("active", Boolean.FALSE));
|
||||
}
|
||||
|
||||
default int update(PayNotifyTaskDO update, Integer whereNotifyTimes) {
|
||||
return update(update, new QueryWrapper<PayNotifyTaskDO>()
|
||||
.eq("id", update.getId()).eq("notify_times", whereNotifyTimes));
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -0,0 +1,51 @@
|
||||
package cn.iocoder.mall.payservice.job.notify;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.notify.PayNotifyTaskMapper;
|
||||
import cn.iocoder.mall.payservice.service.notify.PayNotifyService;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.handler.IJobHandler;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 支付通知重试 Job
|
||||
*
|
||||
* 由于 RocketMQ 不支持指定时间的延迟消息,所以我们需要通过 Job 扫描到达 {@link PayNotifyTaskDO#getNextNotifyTime()} 时间的任务。
|
||||
* 扫描到后,通过发送 MQ 去异步通知,提高通知效率。
|
||||
*
|
||||
* 考虑到 MQ 执行可能存在延迟的情况,导致一个 {@link PayNotifyTaskDO} 同时触发多个通知,通过 {@link PayNotifyTaskDO#getActive()} 标记解决。
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class PayNotifyRetryJob extends IJobHandler {
|
||||
|
||||
@Autowired
|
||||
private PayNotifyTaskMapper payNotifyTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private PayNotifyService payNotifyService;
|
||||
|
||||
@Override
|
||||
@XxlJob("payNotifyRetryJob")
|
||||
public ReturnT<String> execute(String param) {
|
||||
// 获得需要通知的任务
|
||||
List<PayNotifyTaskDO> notifyTasks = payNotifyTaskMapper.selectListByNotify();
|
||||
|
||||
// 循环任务,发送通知
|
||||
for (PayNotifyTaskDO notifyTask : notifyTasks) {
|
||||
// 发送 MQ
|
||||
payNotifyService.sendNotifyMessage(notifyTask);
|
||||
|
||||
// 标记任务执行中。考虑到 MQ 可能会存在先于该操作执行完,所以更新时,增加一个 notifyTimes 作为额外条件,避免覆盖更新的问题。
|
||||
PayNotifyTaskDO updateNotifyTask = new PayNotifyTaskDO().setId(notifyTask.getId()).setActive(true);
|
||||
payNotifyTaskMapper.update(updateNotifyTask, notifyTask.getNotifyTimes());
|
||||
}
|
||||
return new ReturnT<>("执行通知数:" + notifyTasks.size());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
package cn.iocoder.mall.payservice.job;
|
@ -2,6 +2,7 @@ package cn.iocoder.mall.payservice.mq.consumer;
|
||||
|
||||
import cn.iocoder.common.framework.util.DateUtil;
|
||||
import cn.iocoder.common.framework.util.ExceptionUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.payservice.common.dubbo.DubboReferencePool;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyLogDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
@ -10,14 +11,15 @@ import cn.iocoder.mall.payservice.dal.mysql.mapper.notify.PayNotifyTaskMapper;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.AbstractPayNotifySuccessMessage;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractPayNotifySuccessMQConsumer<T extends AbstractPayNotifySuccessMessage> implements RocketMQListener<T> {
|
||||
public abstract class AbstractPayNotifySuccessMQConsumer<T extends AbstractPayNotifySuccessMessage> {
|
||||
// implements RocketMQListener<T> TODO 芋艿,理论来说,可以实现 RocketMQListener 接口,然后 execute 作为 onMessage 的具体实现。但是新版本貌似不行,后续在排查下;
|
||||
|
||||
@Autowired
|
||||
private DubboReferencePool dubboReferencePool;
|
||||
@ -27,21 +29,22 @@ public abstract class AbstractPayNotifySuccessMQConsumer<T extends AbstractPayNo
|
||||
@Autowired
|
||||
private PayNotifyLogMapper payTransactionNotifyLogMapper;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void onMessage(T message) {
|
||||
// 获得 ReferenceMeta 对象
|
||||
DubboReferencePool.ReferenceMeta referenceMeta = dubboReferencePool.getReferenceMeta(message.getNotifyUrl());
|
||||
public void execute(T message) {
|
||||
// 发起调用
|
||||
String response = null; // RPC / HTTP 调用的响应
|
||||
CommonResult<Boolean> invokeResult = null; // RPC / HTTP 调用的响应
|
||||
Throwable invokeException = null; //
|
||||
PayNotifyTaskDO updateTask = new PayNotifyTaskDO() // 更新 PayTransactionNotifyTaskDO 对象
|
||||
.setId(message.getId())
|
||||
.setActive(false) // 标记本地通知已经完成
|
||||
.setLastExecuteTime(new Date())
|
||||
.setNotifyTimes(message.getNotifyTimes() + 1);
|
||||
try {
|
||||
// 获得 ReferenceMeta 对象
|
||||
DubboReferencePool.ReferenceMeta referenceMeta = dubboReferencePool.getReferenceMeta(message.getNotifyUrl());
|
||||
// TODO 芋艿,这里要优化下,不要在事务里,进行 RPC 调用
|
||||
response = invoke(message, referenceMeta);
|
||||
if ("success".equals(response)) { // 情况一,请求成功且返回成功
|
||||
invokeResult = invoke(message, referenceMeta);
|
||||
if (invokeResult.isSuccess()) { // 情况一,请求成功且返回成功
|
||||
// 更新通知成功
|
||||
updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
@ -53,8 +56,8 @@ public abstract class AbstractPayNotifySuccessMQConsumer<T extends AbstractPayNo
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
}
|
||||
} catch (Throwable e) { // 请求失败
|
||||
invokeException = e;
|
||||
// 更新通知请求失败
|
||||
response = ExceptionUtil.getRootCauseMessage(e);
|
||||
handleFailure(updateTask, PayNotifyStatusEnum.REQUEST_FAILURE.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
// 抛出异常,回滚事务
|
||||
@ -63,7 +66,9 @@ public abstract class AbstractPayNotifySuccessMQConsumer<T extends AbstractPayNo
|
||||
} finally {
|
||||
// 插入 PayTransactionNotifyLogDO 日志
|
||||
PayNotifyLogDO notifyLog = new PayNotifyLogDO().setNotifyId(message.getId())
|
||||
.setRequest(JSON.toJSONString(message)).setResponse(response).setStatus(updateTask.getStatus());
|
||||
.setStatus(updateTask.getStatus())
|
||||
.setRequest(JSON.toJSONString(message))
|
||||
.setResponse(invokeResult != null ? JSON.toJSONString(invokeResult) : ExceptionUtil.getRootCauseMessage(invokeException));
|
||||
payTransactionNotifyLogMapper.insert(notifyLog);
|
||||
}
|
||||
}
|
||||
@ -77,8 +82,26 @@ public abstract class AbstractPayNotifySuccessMQConsumer<T extends AbstractPayNo
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract String invoke(T message, DubboReferencePool.ReferenceMeta referenceMeta);
|
||||
protected abstract CommonResult<Boolean> invoke(T message, DubboReferencePool.ReferenceMeta referenceMeta);
|
||||
|
||||
protected abstract void afterInvokeSuccess(T message);
|
||||
|
||||
/**
|
||||
* 将 Dubbo 泛化调用的结果,解析成 CommonResult
|
||||
*
|
||||
* 目前,约定 Dubbo 返回的结果为 CommonResult<Boolean>
|
||||
*
|
||||
* @param dubboResult Dubbo 调用结果
|
||||
* @return CommonResult 结果
|
||||
*/
|
||||
protected static CommonResult<Boolean> parseDubboGenericResult(Object dubboResult) {
|
||||
// TODO 芋艿,目前暂时这么实现,未来找下更合适的
|
||||
Map<String, Object> dubboResultMap = (Map<String, Object>) dubboResult;
|
||||
CommonResult<Boolean> commonResult = new CommonResult<>();
|
||||
commonResult.setCode((Integer) dubboResultMap.get("code"));
|
||||
commonResult.setMessage((String) dubboResultMap.get("message"));
|
||||
commonResult.setData((Boolean) dubboResultMap.get("data"));
|
||||
return commonResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.mall.payservice.mq.consumer;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.payservice.common.dubbo.DubboReferencePool;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.refund.PayRefundMapper;
|
||||
@ -25,15 +26,22 @@ public class PayRefundSuccessMQConsumer extends AbstractPayNotifySuccessMQConsum
|
||||
private PayRefundMapper payRefundMapper;
|
||||
|
||||
@Override
|
||||
protected String invoke(PayRefundSuccessMessage message, DubboReferencePool.ReferenceMeta referenceMeta) {
|
||||
public void onMessage(PayRefundSuccessMessage message) {
|
||||
super.execute(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommonResult<Boolean> invoke(PayRefundSuccessMessage message, DubboReferencePool.ReferenceMeta referenceMeta) {
|
||||
// 查询支付交易
|
||||
PayRefundDO refund = payRefundMapper.selectById(message.getRefundId());
|
||||
Assert.notNull(refund, String.format("回调消息(%s) 退款单不能为空", message.toString()));
|
||||
// 执行调用
|
||||
GenericService genericService = referenceMeta.getService();
|
||||
String methodName = referenceMeta.getMethodName();
|
||||
return (String) genericService.$invoke(methodName, new String[]{String.class.getName(), Integer.class.getName()},
|
||||
Object dubboResult = genericService.$invoke(methodName,
|
||||
new String[]{String.class.getName(), Integer.class.getName()},
|
||||
new Object[]{message.getOrderId(), refund.getPrice()});
|
||||
return parseDubboGenericResult(dubboResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.mall.payservice.mq.consumer;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.payservice.common.dubbo.DubboReferencePool;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.transaction.PayTransactionMapper;
|
||||
@ -25,15 +26,22 @@ public class PayTransactionSuccessMQConsumer extends AbstractPayNotifySuccessMQC
|
||||
private PayTransactionMapper payTransactionMapper;
|
||||
|
||||
@Override
|
||||
protected String invoke(PayTransactionSuccessMessage message, DubboReferencePool.ReferenceMeta referenceMeta) {
|
||||
public void onMessage(PayTransactionSuccessMessage message) {
|
||||
super.execute(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommonResult<Boolean> invoke(PayTransactionSuccessMessage message, DubboReferencePool.ReferenceMeta referenceMeta) {
|
||||
// 查询支付交易
|
||||
PayTransactionDO transaction = payTransactionMapper.selectById(message.getTransactionId());
|
||||
Assert.notNull(transaction, String.format("回调消息(%s) 订单交易不能为空", message.toString()));
|
||||
// 执行调用
|
||||
GenericService genericService = referenceMeta.getService();
|
||||
String methodName = referenceMeta.getMethodName();
|
||||
return (String) genericService.$invoke(methodName, new String[]{String.class.getName(), Integer.class.getName()},
|
||||
Object dubboResult = genericService.$invoke(methodName,
|
||||
new String[]{String.class.getName(), Integer.class.getName()},
|
||||
new Object[]{message.getOrderId(), transaction.getPrice()});
|
||||
return parseDubboGenericResult(dubboResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -17,8 +17,7 @@ public class PayMQProducer {
|
||||
@Autowired
|
||||
private RocketMQTemplate template;
|
||||
|
||||
public void sendPayRefundNotifyTaskMessage(PayRefundSuccessMessage message, Integer refundId, Integer transactionId, String orderId) {
|
||||
message.setRefundId(refundId).setTransactionId(transactionId).setOrderId(orderId);
|
||||
public void sendPayRefundNotifyTaskMessage(PayRefundSuccessMessage message) {
|
||||
try {
|
||||
SendResult sendResult = template.syncSend(PayTransactionSuccessMessage.TOPIC, message);
|
||||
if (!SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
|
||||
@ -29,8 +28,7 @@ public class PayMQProducer {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPayTransactionNotifyTaskMessage(PayTransactionSuccessMessage message, Integer transactionId, String orderId) {
|
||||
message.setTransactionId(transactionId).setOrderId(orderId);
|
||||
public void sendPayTransactionNotifyTaskMessage(PayTransactionSuccessMessage message) {
|
||||
try {
|
||||
SendResult sendResult = template.syncSend(PayTransactionSuccessMessage.TOPIC, message);
|
||||
if (!SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.mall.payservice.service.notify;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
@ -15,4 +16,7 @@ public interface PayNotifyService {
|
||||
// TODO 芋艿:后续优化下,不要暴露 entity 出来
|
||||
void addPayTransactionNotifyTask(PayTransactionDO transaction, PayTransactionExtensionDO extension);
|
||||
|
||||
// TODO 芋艿:后续优化下,不要暴露 entity 出来
|
||||
void sendNotifyMessage(PayNotifyTaskDO notifyTask);
|
||||
|
||||
}
|
||||
|
@ -39,8 +39,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
payNotifyTaskMapper.insert(payNotifyTaskDO);
|
||||
|
||||
// 发送 MQ 消息
|
||||
payMQProducer.sendPayRefundNotifyTaskMessage(PayNotifyConvert.INSTANCE.convertRefund(payNotifyTaskDO),
|
||||
refund.getId(), refund.getTransactionId(), refund.getOrderId());
|
||||
sendNotifyMessage(payNotifyTaskDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -54,14 +53,24 @@ public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
payNotifyTaskMapper.insert(payNotifyTaskDO);
|
||||
|
||||
// 发送 MQ 消息
|
||||
payMQProducer.sendPayTransactionNotifyTaskMessage(PayNotifyConvert.INSTANCE.convertTransaction(payNotifyTaskDO),
|
||||
transaction.getId(), transaction.getOrderId());
|
||||
sendNotifyMessage(payNotifyTaskDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendNotifyMessage(PayNotifyTaskDO notifyTask) {
|
||||
if (PayNotifyType.TRANSACTION.getType().equals(notifyTask.getType())) {
|
||||
payMQProducer.sendPayTransactionNotifyTaskMessage(PayNotifyConvert.INSTANCE.convertTransaction(notifyTask));
|
||||
} else if (PayNotifyType.REFUND.getType().equals(notifyTask.getType())) {
|
||||
payMQProducer.sendPayRefundNotifyTaskMessage(PayNotifyConvert.INSTANCE.convertRefund(notifyTask));
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("通知任务(%s) 无法发送通知消息", notifyTask.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private PayNotifyTaskDO createBasePayNotifyTaskDO(String appId, String notifyUrl) {
|
||||
return new PayNotifyTaskDO()
|
||||
.setAppId(appId)
|
||||
.setStatus(PayNotifyStatusEnum.WAITING.getStatus())
|
||||
.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setActive(true)
|
||||
.setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1)
|
||||
.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[0]))
|
||||
.setNotifyUrl(notifyUrl);
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_pay?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_pay?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
@ -19,3 +19,13 @@ dubbo:
|
||||
registry:
|
||||
# address: spring-cloud://400-infra.server.iocoder.cn:8848 # 指定 Dubbo 服务注册中心的地址
|
||||
address: nacos://400-infra.server.iocoder.cn:8848?namespace=dev # 指定 Dubbo 服务注册中心的地址
|
||||
|
||||
# XXL-Job 配置项
|
||||
xxl:
|
||||
job:
|
||||
admin:
|
||||
addresses: http://127.0.0.1:9099/
|
||||
executor:
|
||||
appname: ${spring.application.name}
|
||||
logpath: /data/applogs/xxl-job/
|
||||
accessToken:
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_pay?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_pay?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
@ -22,3 +22,19 @@ dubbo:
|
||||
# Dubbo 服务提供者的配置
|
||||
provider:
|
||||
tag: ${DUBBO_TAG} # Dubbo 路由分组
|
||||
|
||||
# XXL-Job 配置项
|
||||
xxl:
|
||||
job:
|
||||
enabled: false # 本地开发时,关闭 XXL-Job
|
||||
admin:
|
||||
addresses: http://400-infra.server.iocoder.cn:9099
|
||||
executor:
|
||||
appname: ${spring.application.name}
|
||||
accessToken:
|
||||
|
||||
|
||||
# MyBatis Plus 配置
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 本地开发环境下,多打印 SQL 到控制台
|
||||
|
@ -0,0 +1,49 @@
|
||||
package cn.iocoder.mall.payservice.common.dubbo;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.ReferenceConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.rpc.service.GenericService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DubboGenericInvokerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationConfig application = new ApplicationConfig();
|
||||
application.setName("api-generic-consumer");
|
||||
|
||||
RegistryConfig registry = new RegistryConfig();
|
||||
registry.setAddress("nacos://400-infra.server.iocoder.cn:8848?namespace=dev");
|
||||
|
||||
application.setRegistry(registry);
|
||||
|
||||
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
|
||||
// 弱类型接口名
|
||||
reference.setInterface("cn.iocoder.mall.tradeservice.rpc.order.TradeOrderRpc");
|
||||
reference.setVersion("1.0.0");
|
||||
// 声明为泛化接口
|
||||
reference.setGeneric(true);
|
||||
|
||||
reference.setApplication(application);
|
||||
|
||||
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
|
||||
GenericService genericService = reference.get();
|
||||
|
||||
Object result = genericService.$invoke("updateTradeOrderPaySuccess",
|
||||
new String[]{String.class.getName(), Integer.class.getName()},
|
||||
new Object[]{"1", 100});
|
||||
CommonResult<Boolean> commonResult = parseCommonResult((Map<String, Object>) result);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
private static CommonResult<Boolean> parseCommonResult(Map<String, Object> dubboResult) {
|
||||
CommonResult<Boolean> commonResult = new CommonResult<>();
|
||||
commonResult.setCode((Integer) dubboResult.get("code"));
|
||||
commonResult.setMessage((String) dubboResult.get("message"));
|
||||
commonResult.setData((Boolean) dubboResult.get("data"));
|
||||
return commonResult;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
package cn.iocoder.mall.payservice.common;
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_product?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_product?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_product?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_product?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_promotion?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_promotion?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_promotion?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_promotion?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_system?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_system?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_system?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_system?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
@ -38,6 +38,17 @@ public interface TradeOrderRpc {
|
||||
*/
|
||||
CommonResult<PageResult<TradeOrderRespDTO>> pageTradeOrder(TradeOrderPageReqDTO pageDTO);
|
||||
|
||||
// TODO 芋艿:需要重构成入参是 DTO,方便后续升级;返回是 CommonResult,用于返回失败的原因
|
||||
|
||||
/**
|
||||
* 更新交易订单支付成功
|
||||
*
|
||||
* 目前用于对接 pay-service 支付服务,回调该交易订单在三方支付平台,支付成功
|
||||
*
|
||||
* @param tradeOrderId 交易订单编号
|
||||
* @param payAmount 支付金额
|
||||
* @return 成功
|
||||
*/
|
||||
CommonResult<Boolean> updateTradeOrderPaySuccess(String tradeOrderId, Integer payAmount);
|
||||
|
||||
}
|
||||
|
@ -20,4 +20,9 @@ public interface TradeOrderItemMapper extends BaseMapper<TradeOrderItemDO> {
|
||||
return selectList(new QueryWrapper<TradeOrderItemDO>().in("order_id", orderIds));
|
||||
}
|
||||
|
||||
default int updateListByOrderId(TradeOrderItemDO update, Integer orderId, Integer whereStatus) {
|
||||
return update(update, new QueryWrapper<TradeOrderItemDO>().eq("order_id", orderId)
|
||||
.eq("status", whereStatus));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import cn.iocoder.mall.mybatis.core.query.QueryWrapperX;
|
||||
import cn.iocoder.mall.mybatis.core.util.PageUtil;
|
||||
import cn.iocoder.mall.tradeservice.dal.mysql.dataobject.order.TradeOrderDO;
|
||||
import cn.iocoder.mall.tradeservice.rpc.order.dto.TradeOrderPageReqDTO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@ -17,4 +18,9 @@ public interface TradeOrderMapper extends BaseMapper<TradeOrderDO> {
|
||||
.eqIfPresent("status", pageReqDTO.getOrderStatus()));
|
||||
}
|
||||
|
||||
default int update(TradeOrderDO update, Integer whereOrderStatus) {
|
||||
return update(update, new QueryWrapper<TradeOrderDO>()
|
||||
.eq("id", update.getId()).eq("order_status", whereOrderStatus));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,4 +37,10 @@ public class TradeOrderRpcImpl implements TradeOrderRpc {
|
||||
return success(tradeOrderService.pageTradeOrder(pageDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> updateTradeOrderPaySuccess(String tradeOrderId, Integer payAmount) {
|
||||
tradeOrderService.updateTradeOrderPaySuccess(Integer.valueOf(tradeOrderId), payAmount);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,4 +37,12 @@ public interface TradeOrderService {
|
||||
*/
|
||||
PageResult<TradeOrderRespDTO> pageTradeOrder(TradeOrderPageReqDTO pageReqDTO);
|
||||
|
||||
/**
|
||||
* 更新交易订单支付成功
|
||||
*
|
||||
* @param tradeOrderId 交易订单编号
|
||||
* @param payAmount 支付金额
|
||||
*/
|
||||
void updateTradeOrderPaySuccess(Integer tradeOrderId, Integer payAmount);
|
||||
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.common.framework.util.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.mall.tradeservice.enums.OrderErrorCodeConstants.ORDER_GET_GOODS_INFO_INCORRECT;
|
||||
import static cn.iocoder.mall.tradeservice.enums.OrderErrorCodeConstants.*;
|
||||
import static cn.iocoder.mall.userservice.enums.UserErrorCodeConstants.USER_ADDRESS_NOT_FOUND;
|
||||
|
||||
/**
|
||||
@ -240,4 +240,40 @@ public class TradeOrderServiceImpl implements TradeOrderService {
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateTradeOrderPaySuccess(Integer tradeOrderId, Integer payAmount) {
|
||||
// if (true) {
|
||||
// throw new IllegalArgumentException("测试失败的情况");
|
||||
// }
|
||||
// 校验交易订单,是否可以
|
||||
TradeOrderDO tradeOrderDO = tradeOrderMapper.selectById(tradeOrderId);
|
||||
if (tradeOrderDO == null) { // 订单不存在
|
||||
throw ServiceExceptionUtil.exception(ORDER_NOT_EXISTENT);
|
||||
}
|
||||
if (!tradeOrderDO.getOrderStatus().equals(TradeOrderStatusEnum.WAITING_PAYMENT.getValue())) { // 状态不处于等待支付
|
||||
throw ServiceExceptionUtil.exception(ORDER_STATUS_NOT_WAITING_PAYMENT);
|
||||
}
|
||||
if (!tradeOrderDO.getPresentPrice().equals(payAmount)) { // 支付金额不正确
|
||||
throw ServiceExceptionUtil.exception(ORDER_PAY_AMOUNT_ERROR);
|
||||
}
|
||||
|
||||
// 更新 TradeOrderDO 状态为已支付,等待发货
|
||||
TradeOrderDO updateOrderObj = new TradeOrderDO().setId(tradeOrderId)
|
||||
.setOrderStatus(TradeOrderStatusEnum.WAIT_SHIPMENT.getValue())
|
||||
.setPayPrice(payAmount)
|
||||
.setPayTime(new Date());
|
||||
int updateCount = tradeOrderMapper.update(updateOrderObj, TradeOrderStatusEnum.WAITING_PAYMENT.getValue());
|
||||
if (updateCount <= 0) {
|
||||
throw ServiceExceptionUtil.exception(ORDER_STATUS_NOT_WAITING_PAYMENT);
|
||||
}
|
||||
|
||||
// 更新 TradeOrderItemDO 状态为已支付,等待发货
|
||||
TradeOrderItemDO updateOrderItemObj = new TradeOrderItemDO()
|
||||
.setStatus(TradeOrderStatusEnum.WAIT_SHIPMENT.getValue());
|
||||
tradeOrderItemMapper.updateListByOrderId(updateOrderItemObj, tradeOrderId,
|
||||
TradeOrderStatusEnum.WAITING_PAYMENT.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_trade?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_trade?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_trade?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_trade?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
@ -25,3 +25,8 @@ dubbo:
|
||||
# Dubbo 服务提供者的配置
|
||||
provider:
|
||||
tag: ${DUBBO_TAG} # Dubbo 路由分组
|
||||
|
||||
# MyBatis Plus 配置
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 本地开发环境下,多打印 SQL 到控制台
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 3WLiVUBEwTbvAfsh
|
||||
|
Loading…
Reference in New Issue
Block a user