7.12 简单的交易下单功能实现

This commit is contained in:
zhangsan 2025-07-12 15:31:52 +08:00
parent 95318d5686
commit cf575464ea
33 changed files with 525 additions and 80 deletions

View File

@ -31,9 +31,9 @@ spring:
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource
# MyBatis 配置【如需使用记得打开】 # MyBatis 配置【如需使用记得打开】
#mybatis: mybatis:
# mapper-locations: classpath:/mybatis/mapper/*.xml mapper-locations: classpath:/mybatis/mapper/*.xml
# config-location: classpath:/mybatis/config/mybatis-config.xml config-location: classpath:/mybatis/config/mybatis-config.xml
# 微信公众号对接 # 微信公众号对接
weixin: weixin:

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="edu.whut.infrastructure.persistent.dao.Xxx">
<resultMap id="CaseMap" type="edu.whut.infrastructure.persistent.po.A">
<id column="id" property="id"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<insert id="insert" parameterType="edu.whut.infrastructure.persistent.po.A">
INSERT INTO table(a,b,c) VALUES(#{a}, #{b}, #{c})
</insert>
<update id="update" parameterType="edu.whut.infrastructure.persistent.po.A">
UPDATE table SET a = #{a} WHERE b = #{b}
</update>
<select id="queryEmployeeByEmployNumber" parameterType="java.lang.String" resultMap="CaseMap">
SELECT a, b, c
FROM table
WHERE a = #{a}
</select>
</mapper>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="edu.whut.infrastructure.dao.IOrderDao">
<resultMap id="dataMap" type="edu.whut.infrastructure.dao.po.PayOrder">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="product_id" property="productId"/>
<result column="product_name" property="productName"/>
<result column="order_id" property="orderId"/>
<result column="order_time" property="orderTime"/>
<result column="total_amount" property="totalAmount"/>
<result column="status" property="status"/>
<result column="pay_url" property="payUrl"/>
<result column="pay_time" property="payTime"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<insert id="insert" parameterType="edu.whut.infrastructure.dao.po.PayOrder">
insert into pay_order(user_id, product_id, product_name, order_id, order_time,
total_amount, status, create_time, update_time)
values(#{userId}, #{productId}, #{productName}, #{orderId}, #{orderTime},
#{totalAmount}, #{status}, now(), now())
</insert>
<select id="queryUnPayOrder" parameterType="edu.whut.infrastructure.dao.po.PayOrder" resultMap="dataMap">
select product_id, product_name, order_id, order_time, total_amount, status, pay_url
from pay_order
where user_id = #{userId} and product_id = #{productId}
order by id desc
limit 1
</select>
</mapper>

View File

@ -0,0 +1,33 @@
package edu.whut.test.domain;
import com.alibaba.fastjson.JSON;
import edu.whut.domain.order.model.entity.PayOrderEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
import edu.whut.domain.order.service.IOrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@RequiredArgsConstructor
public class OrderServiceTest {
private final IOrderService orderService;
@Test
public void test_createOrder() throws Exception {
ShopCartEntity shopCartEntity = new ShopCartEntity();
shopCartEntity.setUserId("smile01");
shopCartEntity.setProductId("10001");
PayOrderEntity payOrderEntity = orderService.createOrder(shopCartEntity);
log.info("请求参数:{}", JSON.toJSONString(shopCartEntity));
log.info("测试结果:{}", JSON.toJSONString(payOrderEntity));
}
}

View File

@ -1,6 +1,7 @@
package edu.whut.domain.auth.service; package edu.whut.domain.auth.service;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import edu.whut.domain.auth.adapter.port.ILoginPort; import edu.whut.domain.auth.adapter.port.ILoginPort;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -9,12 +10,12 @@ import java.io.IOException;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor
public class WeixinLoginService implements ILoginService { public class WeixinLoginService implements ILoginService {
@Resource private final ILoginPort loginPort;
private ILoginPort loginPort;
@Resource private final Cache<String, String> openidToken;
private Cache<String, String> openidToken;
/** /**
* 生成登录二维码的 ticket * 生成登录二维码的 ticket

View File

@ -0,0 +1,9 @@
package edu.whut.domain.order.adapter.port;
import edu.whut.domain.order.model.entity.ProductEntity;
public interface IProductPort {
ProductEntity queryProductByProductId(String productId);
}

View File

@ -0,0 +1,12 @@
package edu.whut.domain.order.adapter.repository;
import edu.whut.domain.order.model.aggregate.CreateOrderAggregate;
import edu.whut.domain.order.model.entity.OrderEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
public interface IOrderRepository {
void doSaveOrder(CreateOrderAggregate orderAggregate);
OrderEntity queryUnPayOrder(ShopCartEntity shopCartEntity);
}

View File

@ -0,0 +1,35 @@
package edu.whut.domain.order.model.aggregate;
import edu.whut.domain.order.model.entity.OrderEntity;
import edu.whut.domain.order.model.entity.ProductEntity;
import edu.whut.domain.order.model.valobj.OrderStatusVO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.RandomStringUtils;
import java.util.Date;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CreateOrderAggregate {
private String userId;
private ProductEntity productEntity;
private OrderEntity orderEntity;
public static OrderEntity buildOrderEntity(String productId, String productName){
return OrderEntity.builder()
.productId(productId)
.productName(productName)
.orderId(RandomStringUtils.randomNumeric(14))
.orderTime(new Date())
.orderStatusVO(OrderStatusVO.CREATE)
.build();
}
}

View File

@ -0,0 +1,25 @@
package edu.whut.domain.order.model.entity;
import edu.whut.domain.order.model.valobj.OrderStatusVO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OrderEntity {
private String productId;
private String productName;
private String orderId;
private Date orderTime;
private BigDecimal totalAmount;
private OrderStatusVO orderStatusVO;
private String payUrl;
}

View File

@ -0,0 +1,19 @@
package edu.whut.domain.order.model.entity;
import edu.whut.domain.order.model.valobj.OrderStatusVO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PayOrderEntity {
private String userId;
private String orderId;
private String payUrl;
private OrderStatusVO orderStatus;
}

View File

@ -0,0 +1,25 @@
package edu.whut.domain.order.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProductEntity {
/** 商品ID */
private String productId;
/** 商品名称 */
private String productName;
/** 商品描述 */
private String productDesc;
/** 商品价格 */
private BigDecimal price;
}

View File

@ -0,0 +1,18 @@
package edu.whut.domain.order.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ShopCartEntity {
private String userId;
private String productId;
}

View File

@ -0,0 +1,20 @@
package edu.whut.domain.order.model.valobj;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum OrderStatusVO {
CREATE("CREATE", "创建完成 - 如果调单了,也会从创建记录重新发起创建支付单"),
PAY_WAIT("PAY_WAIT", "等待支付 - 订单创建完成后,创建支付单"),
PAY_SUCCESS("PAY_SUCCESS", "支付成功 - 接收到支付回调消息"),
DEAL_DONE("DEAL_DONE", "交易完成 - 商品发货完成"),
CLOSE("CLOSE", "超时关单 - 超市未支付"),
;
private final String code;
private final String desc;
}

View File

@ -0,0 +1,72 @@
package edu.whut.domain.order.service;
import edu.whut.domain.order.adapter.port.IProductPort;
import edu.whut.domain.order.adapter.repository.IOrderRepository;
import edu.whut.domain.order.model.aggregate.CreateOrderAggregate;
import edu.whut.domain.order.model.entity.OrderEntity;
import edu.whut.domain.order.model.entity.PayOrderEntity;
import edu.whut.domain.order.model.entity.ProductEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
import edu.whut.domain.order.model.valobj.OrderStatusVO;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class AbstractOrderService implements IOrderService {
protected final IOrderRepository repository;
protected final IProductPort port;
public AbstractOrderService(IOrderRepository repository, IProductPort port) {
this.repository = repository;
this.port = port;
}
/**
* 创建订单
*/
@Override
public PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception {
// 1. 查询当前用户是否存在掉单和未支付订单
OrderEntity unpaidOrderEntity = repository.queryUnPayOrder(shopCartEntity);
// 如果已有订单正在等待支付直接复用
if (null != unpaidOrderEntity && OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatusVO())) {
log.info("创建订单-存在已存在未支付订单。userId:{} productId:{} orderId:{}", shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId());
return PayOrderEntity.builder()
.orderId(unpaidOrderEntity.getOrderId())
.payUrl(unpaidOrderEntity.getPayUrl())
.build();
// TODO: 如果存在已创建未支付状态
} else if (null != unpaidOrderEntity && OrderStatusVO.CREATE.equals(unpaidOrderEntity.getOrderStatusVO())) {
}
// 2. 调用产品服务查询商品详细信息
ProductEntity productEntity = port.queryProductByProductId(shopCartEntity.getProductId());
// 3. 构建基础 OrderEntity orderId时间等
OrderEntity orderEntity = CreateOrderAggregate.buildOrderEntity(productEntity.getProductId(), productEntity.getProductName());
// 4. 组装聚合根聚合中包含用户商品和待持久化的订单实体
CreateOrderAggregate orderAggregate = CreateOrderAggregate.builder()
.userId(shopCartEntity.getUserId())
.productEntity(productEntity)
.orderEntity(orderEntity)
.build();
// 5. 交由子类实现保存订单聚合
this.doSaveOrder(orderAggregate);
// 6. 返回支付实体
return PayOrderEntity.builder()
.orderId(orderEntity.getOrderId())
.payUrl("暂无")
.build();
}
/**
* 保存订单
*/
protected abstract void doSaveOrder(CreateOrderAggregate orderAggregate);
}

View File

@ -0,0 +1,11 @@
package edu.whut.domain.order.service;
import edu.whut.domain.order.model.entity.PayOrderEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
public interface IOrderService {
PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception;
}

View File

@ -0,0 +1,21 @@
package edu.whut.domain.order.service;
import edu.whut.domain.order.adapter.port.IProductPort;
import edu.whut.domain.order.adapter.repository.IOrderRepository;
import edu.whut.domain.order.model.aggregate.CreateOrderAggregate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderService extends AbstractOrderService{
public OrderService(IOrderRepository repository, IProductPort port) {
super(repository, port);
}
@Override
protected void doSaveOrder(CreateOrderAggregate orderAggregate) {
repository.doSaveOrder(orderAggregate);
}
}

View File

@ -1,4 +0,0 @@
/**
* 外部接口适配器层当需要调用外部接口时则创建出这一层并定义接口之后由基础设施层的 adapter 层具体实现
*/
package edu.whut.domain.xxx.adapter;

View File

@ -1,4 +0,0 @@
/**
* 外部接口适配器层当需要调用外部接口时则创建出这一层并定义接口之后由基础设施层的 adapter 层具体实现
*/
package edu.whut.domain.xxx.adapter.port;

View File

@ -1,5 +0,0 @@
/**
* 仓储服务
* 1. 定义仓储接口之后由基础设施层做具体实现
*/
package edu.whut.domain.xxx.adapter.repository;

View File

@ -1,7 +0,0 @@
/**
* 聚合对象
* 1. 聚合实体和值对象
* 2. 聚合是聚合的对象和提供基础处理对象的方法但不建议在聚合中引入仓储和接口来做过大的逻辑而这些复杂的操作应该放到service中处理
* 3. 对象名称 XxxAggregate
*/
package edu.whut.domain.xxx.model.aggregate;

View File

@ -1,7 +0,0 @@
/**
* 实体对象
* 1. 一般和数据库持久化对象1v1的关系但因各自开发系统的不同也有1vn的可能
* 2. 如果是老系统改造那么旧的库表冗余了太多的字段可能会有nv1的情况
* 3. 对象名称 XxxEntity
*/
package edu.whut.domain.xxx.model.entity;

View File

@ -1,6 +0,0 @@
/**
* 值对象
* 1. 用于描述对象属性的值如一个库表中有json后者一个字段多个属性信息的枚举对象
* 2. 对象名称如XxxVO
*/
package edu.whut.domain.xxx.model.valobj;

View File

@ -1 +0,0 @@
package edu.whut.domain.xxx.service;

View File

@ -6,6 +6,7 @@ import edu.whut.infrastructure.gateway.dto.WeixinQrCodeRequestDTO;
import edu.whut.infrastructure.gateway.dto.WeixinQrCodeResponseDTO; import edu.whut.infrastructure.gateway.dto.WeixinQrCodeResponseDTO;
import edu.whut.infrastructure.gateway.dto.WeixinTemplateMessageDTO; import edu.whut.infrastructure.gateway.dto.WeixinTemplateMessageDTO;
import edu.whut.infrastructure.gateway.dto.WeixinTokenResponseDTO; import edu.whut.infrastructure.gateway.dto.WeixinTokenResponseDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import retrofit2.Call; import retrofit2.Call;
@ -16,6 +17,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Service @Service
@RequiredArgsConstructor
public class LoginPort implements ILoginPort { public class LoginPort implements ILoginPort {
@Value("${weixin.config.app-id}") @Value("${weixin.config.app-id}")
@ -25,11 +27,9 @@ public class LoginPort implements ILoginPort {
@Value("${weixin.config.template_id}") @Value("${weixin.config.template_id}")
private String template_id; private String template_id;
@Resource private final Cache<String, String> weixinAccessToken;
private Cache<String, String> weixinAccessToken;
@Resource private final IWeixinApiService weixinApiService;
private IWeixinApiService weixinApiService;
/** /**
* 生成二维码登录凭证 ticket * 生成二维码登录凭证 ticket

View File

@ -0,0 +1,29 @@
package edu.whut.infrastructure.adapter.port;
import edu.whut.domain.order.adapter.port.IProductPort;
import edu.whut.domain.order.model.entity.ProductEntity;
import edu.whut.infrastructure.gateway.ProductRPC;
import edu.whut.infrastructure.gateway.dto.ProductDTO;
import org.springframework.stereotype.Component;
@Component
public class ProductPort implements IProductPort {
private final ProductRPC productRPC;
public ProductPort(ProductRPC productRPC) {
this.productRPC = productRPC;
}
@Override
public ProductEntity queryProductByProductId(String productId) {
ProductDTO productDTO = productRPC.queryProductByProductId(productId);
return ProductEntity.builder()
.productId(productDTO.getProductId())
.productName(productDTO.getProductName())
.productDesc(productDTO.getProductDesc())
.price(productDTO.getPrice())
.build();
}
}

View File

@ -0,0 +1,62 @@
package edu.whut.infrastructure.adapter.repository;
import edu.whut.domain.order.adapter.repository.IOrderRepository;
import edu.whut.domain.order.model.aggregate.CreateOrderAggregate;
import edu.whut.domain.order.model.entity.OrderEntity;
import edu.whut.domain.order.model.entity.ProductEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
import edu.whut.domain.order.model.valobj.OrderStatusVO;
import edu.whut.infrastructure.dao.IOrderDao;
import edu.whut.infrastructure.dao.po.PayOrder;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository
@RequiredArgsConstructor
public class OrderRepository implements IOrderRepository {
private final IOrderDao orderDao;
@Override
public void doSaveOrder(CreateOrderAggregate orderAggregate) {
String userId = orderAggregate.getUserId();
ProductEntity productEntity = orderAggregate.getProductEntity();
OrderEntity orderEntity = orderAggregate.getOrderEntity();
PayOrder order = new PayOrder();
order.setUserId(userId);
order.setProductId(productEntity.getProductId());
order.setProductName(productEntity.getProductName());
order.setOrderId(orderEntity.getOrderId());
order.setOrderTime(orderEntity.getOrderTime());
order.setTotalAmount(productEntity.getPrice());
order.setStatus(orderEntity.getOrderStatusVO().getCode());
orderDao.insert(order);
}
@Override
public OrderEntity queryUnPayOrder(ShopCartEntity shopCartEntity) {
// 1. 封装参数
PayOrder orderReq = new PayOrder();
orderReq.setUserId(shopCartEntity.getUserId());
orderReq.setProductId(shopCartEntity.getProductId());
// 2. 查询到订单
PayOrder order = orderDao.queryUnPayOrder(orderReq);
if (null == order) return null;
// 3. 返回结果
return OrderEntity.builder()
.productId(order.getProductId())
.productName(order.getProductName())
.orderId(order.getOrderId())
.orderStatusVO(OrderStatusVO.valueOf(order.getStatus()))
.orderTime(order.getOrderTime())
.totalAmount(order.getTotalAmount())
.payUrl(order.getPayUrl())
.build();
}
}

View File

@ -0,0 +1,12 @@
package edu.whut.infrastructure.dao;
import edu.whut.infrastructure.dao.po.PayOrder;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IOrderDao {
void insert(PayOrder payOrder);
PayOrder queryUnPayOrder(PayOrder payOrder);
}

View File

@ -1,4 +0,0 @@
/**
* DAO 接口IXxxDao
*/
package edu.whut.infrastructure.dao;

View File

@ -0,0 +1,30 @@
package edu.whut.infrastructure.dao.po;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.Date;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PayOrder {
private Long id;
private String userId;
private String productId;
private String productName;
private String orderId;
private Date orderTime;
private BigDecimal totalAmount;
private String status;
private String payUrl;
private Date payTime;
private Date createTime;
private Date updateTime;
}

View File

@ -1,4 +0,0 @@
/**
* 持久化对象XxxPO 最后的 PO 是大写UserPO
*/
package edu.whut.infrastructure.dao.po;

View File

@ -0,0 +1,23 @@
package edu.whut.infrastructure.gateway;
import edu.whut.infrastructure.gateway.dto.ProductDTO;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class ProductRPC {
/**
* 模拟调用外部商城获取商品信息
*/
public ProductDTO queryProductByProductId(String productId){
ProductDTO productVO = new ProductDTO();
productVO.setProductId(productId);
productVO.setProductName("测试商品");
productVO.setProductDesc("这是一个测试商品");
productVO.setPrice(new BigDecimal("1.68"));
return productVO;
}
}

View File

@ -0,0 +1,19 @@
package edu.whut.infrastructure.gateway.dto;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ProductDTO {
/** 商品ID */
private String productId;
/** 商品名称 */
private String productName;
/** 商品描述 */
private String productDesc;
/** 商品价格 */
private BigDecimal price;
}

View File

@ -3,6 +3,7 @@ import edu.whut.api.IAuthService;
import edu.whut.api.response.Response; import edu.whut.api.response.Response;
import edu.whut.domain.auth.service.ILoginService; import edu.whut.domain.auth.service.ILoginService;
import edu.whut.types.common.Constants; import edu.whut.types.common.Constants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -13,10 +14,10 @@ import javax.annotation.Resource;
@RestController() @RestController()
@CrossOrigin("*") @CrossOrigin("*")
@RequestMapping("/api/v1/login") @RequestMapping("/api/v1/login")
@RequiredArgsConstructor
public class LoginController implements IAuthService { public class LoginController implements IAuthService {
@Resource private final ILoginService loginService;
private ILoginService loginService;
/** /**
* 生成并返回一个微信扫码登录的凭证ticket * 生成并返回一个微信扫码登录的凭证ticket