6.30 拼团锁单+价格试算整合
This commit is contained in:
parent
57e702e864
commit
a5991fe89d
244
docs/dev-ops/mysql/sql/0630group_buying_sys.sql
Normal file
244
docs/dev-ops/mysql/sql/0630group_buying_sys.sql
Normal file
@ -0,0 +1,244 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : group_buy
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 80042
|
||||
Source Host : localhost:13306
|
||||
Source Schema : group_buying_sys
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 80042
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 30/06/2025 14:21:35
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for crowd_tags
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `crowd_tags`;
|
||||
CREATE TABLE `crowd_tags` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`tag_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '人群ID',
|
||||
`tag_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '人群名称',
|
||||
`tag_desc` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '人群描述',
|
||||
`statistics` int NOT NULL COMMENT '人群标签统计量',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_tag_id`(`tag_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '人群标签' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of crowd_tags
|
||||
-- ----------------------------
|
||||
INSERT INTO `crowd_tags` VALUES (1, 'RQ_KJHKL98UU78H66554GFDV', '潜在消费用户', '潜在消费用户', 28, '2025-06-26 09:12:22', '2025-06-28 11:02:00');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for crowd_tags_detail
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `crowd_tags_detail`;
|
||||
CREATE TABLE `crowd_tags_detail` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`tag_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '人群ID',
|
||||
`user_id` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_tag_user`(`tag_id` ASC, `user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '人群标签明细' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of crowd_tags_detail
|
||||
-- ----------------------------
|
||||
INSERT INTO `crowd_tags_detail` VALUES (20, 'RQ_KJHKL98UU78H66554GFDV', 'zy123', '2025-06-28 10:53:23', '2025-06-28 10:53:23');
|
||||
INSERT INTO `crowd_tags_detail` VALUES (21, 'RQ_KJHKL98UU78H66554GFDV', 'smile', '2025-06-28 10:53:23', '2025-06-28 10:53:23');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for crowd_tags_job
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `crowd_tags_job`;
|
||||
CREATE TABLE `crowd_tags_job` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`tag_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '标签ID',
|
||||
`batch_id` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '批次ID',
|
||||
`tag_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '标签类型(参与量、消费金额)',
|
||||
`tag_rule` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '标签规则(限定类型 N次)',
|
||||
`stat_start_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '统计数据,开始时间',
|
||||
`stat_end_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '统计数据,结束时间',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态;0初始、1计划(进入执行阶段)、2重置、3完成',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_batch_id`(`batch_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '人群标签任务' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of crowd_tags_job
|
||||
-- ----------------------------
|
||||
INSERT INTO `crowd_tags_job` VALUES (1, 'RQ_KJHKL98UU78H66554GFDV', '10001', 0, '100', '2025-06-26 09:13:31', '2025-06-26 09:13:31', 0, '2025-06-26 09:13:31', '2025-06-26 09:13:31');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for group_buy_activity
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `group_buy_activity`;
|
||||
CREATE TABLE `group_buy_activity` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增',
|
||||
`activity_id` bigint NOT NULL COMMENT '活动ID',
|
||||
`activity_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '活动名称',
|
||||
`discount_id` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '折扣ID',
|
||||
`group_type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '拼团方式(0自动成团、1达成目标拼团)',
|
||||
`take_limit_count` int NOT NULL DEFAULT 1 COMMENT '拼团次数限制',
|
||||
`target` int NOT NULL DEFAULT 1 COMMENT '拼团目标',
|
||||
`valid_time` int NOT NULL DEFAULT 15 COMMENT '拼团时长(分钟)',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '活动状态(0创建、1生效、2过期、3废弃)',
|
||||
`start_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '活动开始时间',
|
||||
`end_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '活动结束时间',
|
||||
`tag_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '人群标签规则标识',
|
||||
`tag_scope` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '人群标签规则范围(多选;1可见限制、2参与限制)',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_activity_id`(`activity_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '拼团活动' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of group_buy_activity
|
||||
-- ----------------------------
|
||||
INSERT INTO `group_buy_activity` VALUES (1, 100123, '测试活动', '25120207', 0, 1, 3, 15, 1, '2025-06-19 10:19:40', '2025-06-19 10:19:40', 'RQ_KJHKL98UU78H66554GFDV', '1,2', '2025-06-19 10:19:40', '2025-06-30 14:11:05');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for group_buy_discount
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `group_buy_discount`;
|
||||
CREATE TABLE `group_buy_discount` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`discount_id` int NOT NULL COMMENT '折扣ID',
|
||||
`discount_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '折扣标题',
|
||||
`discount_desc` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '折扣描述',
|
||||
`discount_type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '折扣类型(0:base、1:tag)',
|
||||
`market_plan` varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'ZJ' COMMENT '营销优惠计划(ZJ:直减、MJ:满减、ZK:折扣、N元购)',
|
||||
`market_expr` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '营销优惠表达式',
|
||||
`tag_id` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '人群标签,特定优惠限定',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_discount_id`(`discount_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '折扣配置' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of group_buy_discount
|
||||
-- ----------------------------
|
||||
INSERT INTO `group_buy_discount` VALUES (1, 25120207, '直减优惠20元', '直减优惠20元', 0, 'ZJ', '20', NULL, '2025-06-25 14:02:13', '2025-06-25 14:02:13');
|
||||
INSERT INTO `group_buy_discount` VALUES (2, 25120208, '满减优惠100-10元', '满减优惠100-10元', 0, 'MJ', '100,10', NULL, '2025-06-25 14:02:13', '2025-06-25 14:02:13');
|
||||
INSERT INTO `group_buy_discount` VALUES (4, 25120209, '折扣优惠8折', '折扣优惠8折', 0, 'ZK', '0.8', NULL, '2025-06-25 14:02:13', '2025-06-25 14:02:13');
|
||||
INSERT INTO `group_buy_discount` VALUES (5, 25120210, 'N元购买优惠', 'N元购买优惠', 0, 'N', '1.99', NULL, '2025-06-25 14:02:13', '2025-06-25 14:02:13');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for group_buy_order
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `group_buy_order`;
|
||||
CREATE TABLE `group_buy_order` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`team_id` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '拼单组队ID',
|
||||
`activity_id` bigint NOT NULL COMMENT '活动ID',
|
||||
`source` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '渠道',
|
||||
`channel` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '来源',
|
||||
`original_price` decimal(8, 2) NOT NULL COMMENT '原始价格',
|
||||
`deduction_price` decimal(8, 2) NOT NULL COMMENT '折扣金额',
|
||||
`pay_price` decimal(8, 2) NOT NULL COMMENT '支付价格',
|
||||
`target_count` int NOT NULL COMMENT '目标数量',
|
||||
`complete_count` int NOT NULL COMMENT '完成数量',
|
||||
`lock_count` int NOT NULL COMMENT '锁单数量',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态(0-拼单中、1-完成、2-失败)',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_team_id`(`team_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '拼团订单表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of group_buy_order
|
||||
-- ----------------------------
|
||||
INSERT INTO `group_buy_order` VALUES (1, '41763306', 100123, 's01', 'c01', 100.00, 80.00, 80.00, 3, 0, 3, 0, '2025-06-30 14:15:54', '2025-06-30 14:16:34');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for group_buy_order_list
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `group_buy_order_list`;
|
||||
CREATE TABLE `group_buy_order_list` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户ID',
|
||||
`team_id` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '拼单组队ID',
|
||||
`order_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单ID',
|
||||
`activity_id` bigint NOT NULL COMMENT '活动ID',
|
||||
`start_time` datetime NOT NULL COMMENT '活动开始时间',
|
||||
`end_time` datetime NOT NULL COMMENT '活动结束时间',
|
||||
`goods_id` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品ID',
|
||||
`source` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '渠道',
|
||||
`channel` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '来源',
|
||||
`original_price` decimal(8, 2) NOT NULL COMMENT '原始价格',
|
||||
`deduction_price` decimal(8, 2) NOT NULL COMMENT '折扣金额',
|
||||
`status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态;0初始锁定、1消费完成',
|
||||
`out_trade_no` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '外部交易单号-确保外部调用唯一幂等',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_order_id`(`order_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '拼团订单明细表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of group_buy_order_list
|
||||
-- ----------------------------
|
||||
INSERT INTO `group_buy_order_list` VALUES (2, 'smile', '41763306', '489678402107', 100123, '2025-06-19 10:19:40', '2025-06-19 10:19:40', '9890001', 's01', 'c01', 100.00, 80.00, 0, '937537633579', '2025-06-30 14:15:54', '2025-06-30 14:15:54');
|
||||
INSERT INTO `group_buy_order_list` VALUES (3, 'smile', '41763306', '019791778751', 100123, '2025-06-19 10:19:40', '2025-06-19 10:19:40', '9890001', 's01', 'c01', 100.00, 80.00, 0, '568815897894', '2025-06-30 14:16:18', '2025-06-30 14:16:18');
|
||||
INSERT INTO `group_buy_order_list` VALUES (4, 'smile', '41763306', '640987287494', 100123, '2025-06-19 10:19:40', '2025-06-19 10:19:40', '9890001', 's01', 'c01', 100.00, 80.00, 0, '206038867574', '2025-06-30 14:16:34', '2025-06-30 14:16:34');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sc_sku_activity
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sc_sku_activity`;
|
||||
CREATE TABLE `sc_sku_activity` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`source` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '渠道',
|
||||
`channel` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '来源',
|
||||
`activity_id` bigint NOT NULL COMMENT '活动ID',
|
||||
`goods_id` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_sc_goodsid`(`source` ASC, `channel` ASC, `goods_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '渠道商品活动配置关联表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sc_sku_activity
|
||||
-- ----------------------------
|
||||
INSERT INTO `sc_sku_activity` VALUES (1, 's01', 'c01', 100123, '9890001', '2025-06-26 17:15:54', '2025-06-26 17:15:54');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sku
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sku`;
|
||||
CREATE TABLE `sku` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`source` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '渠道',
|
||||
`channel` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '来源',
|
||||
`goods_id` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品ID',
|
||||
`goods_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '商品名称',
|
||||
`original_price` decimal(10, 2) NOT NULL COMMENT '商品价格',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uq_goods_id`(`goods_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '商品信息' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sku
|
||||
-- ----------------------------
|
||||
INSERT INTO `sku` VALUES (1, 's01', 'c01', '9890001', '《手写MyBatis:渐进式源码实践》', 100.00, '2025-06-22 11:10:06', '2025-06-22 11:10:06');
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
@ -0,0 +1,19 @@
|
||||
package edu.whut.api;
|
||||
|
||||
import edu.whut.api.dto.LockMarketPayOrderRequestDTO;
|
||||
import edu.whut.api.dto.LockMarketPayOrderResponseDTO;
|
||||
import edu.whut.api.response.Response;
|
||||
|
||||
/**
|
||||
* 营销交易服务接口
|
||||
*/
|
||||
public interface IMarketTradeService {
|
||||
|
||||
/**
|
||||
* 拼团交易锁单
|
||||
* @param lockMarketPayOrderRequestDTO
|
||||
* @return
|
||||
*/
|
||||
Response<LockMarketPayOrderResponseDTO> lockMarketPayOrder(LockMarketPayOrderRequestDTO lockMarketPayOrderRequestDTO);
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package edu.whut.api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 营销支付锁单请求对象
|
||||
*/
|
||||
@Data
|
||||
public class LockMarketPayOrderRequestDTO {
|
||||
|
||||
// 用户ID
|
||||
private String userId;
|
||||
// 拼单组队ID - 可为空,为空则创建新组队ID
|
||||
private String teamId;
|
||||
// 活动ID
|
||||
private Long activityId;
|
||||
// 商品ID
|
||||
private String goodsId;
|
||||
// 渠道
|
||||
private String source;
|
||||
// 来源
|
||||
private String channel;
|
||||
// 外部交易单号
|
||||
private String outTradeNo;
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package edu.whut.api.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 营销支付锁单应答对象
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LockMarketPayOrderResponseDTO {
|
||||
|
||||
/** 预购订单ID */
|
||||
private String orderId;
|
||||
/** 折扣金额 */
|
||||
private BigDecimal deductionPrice;
|
||||
/** 交易订单状态 */
|
||||
private Integer tradeOrderStatus;
|
||||
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
/**
|
||||
* 数据传输对象 xxxRequestDTO xxxResponseDTO
|
||||
*/
|
||||
package edu.whut.api.dto;
|
@ -0,0 +1,44 @@
|
||||
<?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.IGroupBuyOrderListDao">
|
||||
|
||||
<resultMap id="dataMap" type="edu.whut.infrastructure.dao.po.GroupBuyOrderList">
|
||||
<id column="id" property="id"/>
|
||||
<result column="user_id" property="userId"/>
|
||||
<result column="team_id" property="teamId"/>
|
||||
<result column="order_id" property="orderId"/>
|
||||
<result column="activity_id" property="activityId"/>
|
||||
<result column="start_time" property="startTime"/>
|
||||
<result column="end_time" property="endTime"/>
|
||||
<result column="goods_id" property="goodsId"/>
|
||||
<result column="source" property="source"/>
|
||||
<result column="channel" property="channel"/>
|
||||
<result column="original_price" property="originalPrice"/>
|
||||
<result column="deduction_price" property="deductionPrice"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="out_trade_no" property="outTradeNo"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="edu.whut.infrastructure.dao.po.GroupBuyOrderList">
|
||||
insert into group_buy_order_list(
|
||||
user_id, team_id, order_id, activity_id, start_time,
|
||||
end_time, goods_id, source, channel, original_price,
|
||||
deduction_price, status, out_trade_no, create_time, update_time
|
||||
)
|
||||
values(
|
||||
#{userId}, #{teamId}, #{orderId}, #{activityId}, #{startTime},
|
||||
#{endTime}, #{goodsId}, #{source}, #{channel}, #{originalPrice},
|
||||
#{deductionPrice}, #{status}, #{outTradeNo}, now(), now()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="queryGroupBuyOrderRecordByOutTradeNo" parameterType="edu.whut.infrastructure.dao.po.GroupBuyOrderList" resultMap="dataMap">
|
||||
select user_id, team_id, order_id, activity_id, start_time,
|
||||
end_time, goods_id, source, channel, original_price, deduction_price, status
|
||||
from group_buy_order_list
|
||||
where out_trade_no = #{outTradeNo} and user_id = #{userId} and status = 0
|
||||
</select>
|
||||
|
||||
</mapper>
|
@ -0,0 +1,51 @@
|
||||
<?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.IGroupBuyOrderDao">
|
||||
|
||||
<resultMap id="dataMap" type="edu.whut.infrastructure.dao.po.GroupBuyOrder">
|
||||
<id column="id" property="id"/>
|
||||
<result column="team_id" property="teamId"/>
|
||||
<result column="activity_id" property="activityId"/>
|
||||
<result column="source" property="source"/>
|
||||
<result column="channel" property="channel"/>
|
||||
<result column="original_price" property="originalPrice"/>
|
||||
<result column="deduction_price" property="deductionPrice"/>
|
||||
<result column="pay_price" property="payPrice"/>
|
||||
<result column="target_count" property="targetCount"/>
|
||||
<result column="complete_count" property="completeCount"/>
|
||||
<result column="lock_count" property="lockCount"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="edu.whut.infrastructure.dao.po.GroupBuyOrder">
|
||||
insert into group_buy_order(
|
||||
team_id, activity_id, source, channel, original_price,
|
||||
deduction_price, pay_price, target_count, complete_count, lock_count, status, create_time, update_time
|
||||
) values(
|
||||
#{teamId}, #{activityId}, #{source}, #{channel}, #{originalPrice},
|
||||
#{deductionPrice}, #{payPrice}, #{targetCount}, #{completeCount}, #{lockCount}, 0, now(), now()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateAddLockCount" parameterType="java.lang.String">
|
||||
<![CDATA[
|
||||
update group_buy_order
|
||||
set lock_count = lock_count + 1, update_time= now()
|
||||
where team_id = #{teamId} and lock_count < target_count
|
||||
]]>
|
||||
</update>
|
||||
|
||||
<update id="updateSubtractionLockCount" parameterType="java.lang.String">
|
||||
update group_buy_order
|
||||
set lock_count = lock_count - 1, update_time= now()
|
||||
where team_id = #{teamId} and lock_count > 0
|
||||
</update>
|
||||
|
||||
<select id="queryGroupBuyProgress" parameterType="java.lang.String" resultMap="dataMap">
|
||||
select target_count, complete_count, lock_count from group_buy_order
|
||||
where team_id = #{teamId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
@ -15,7 +15,7 @@
|
||||
<select id="querySCSkuActivityBySCGoodsId" parameterType="edu.whut.infrastructure.dao.po.SCSkuActivity" resultMap="dataMap">
|
||||
select source, channel, activity_id, goods_id
|
||||
from sc_sku_activity
|
||||
where goods_id = #{goodsId}
|
||||
where goods_id = #{goodsId} and source = #{source} and channel = #{channel}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
@ -0,0 +1,86 @@
|
||||
package edu.whut.test.domain.trade;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import edu.whut.domain.activity.model.entity.MarketProductEntity;
|
||||
import edu.whut.domain.activity.model.entity.TrialBalanceEntity;
|
||||
import edu.whut.domain.activity.model.valobj.GroupBuyActivityDiscountVO;
|
||||
import edu.whut.domain.activity.service.IIndexGroupBuyMarketService;
|
||||
import edu.whut.domain.trade.model.entity.MarketPayOrderEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayActivityEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayDiscountEntity;
|
||||
import edu.whut.domain.trade.model.entity.UserEntity;
|
||||
import edu.whut.domain.trade.service.ITradeOrderService;
|
||||
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
|
||||
public class ITradeOrderServiceTest {
|
||||
|
||||
@Resource
|
||||
private IIndexGroupBuyMarketService indexGroupBuyMarketService;
|
||||
|
||||
@Resource
|
||||
private ITradeOrderService tradeOrderService;
|
||||
|
||||
@Test
|
||||
public void test_lockMarketPayOrder() throws Exception {
|
||||
// 入参信息
|
||||
Long activityId = 100123L;
|
||||
String userId = "xiaofuge";
|
||||
String goodsId = "9890001";
|
||||
String source = "s01";
|
||||
String channel = "c01";
|
||||
String outTradeNo = "909000098111";
|
||||
|
||||
// 1. 获取试算优惠,有【activityId】优先使用
|
||||
TrialBalanceEntity trialBalanceEntity = indexGroupBuyMarketService.indexMarketTrial(MarketProductEntity.builder()
|
||||
.userId(userId)
|
||||
.source(source)
|
||||
.channel(channel)
|
||||
.goodsId(goodsId)
|
||||
.activityId(activityId)
|
||||
.build());
|
||||
|
||||
GroupBuyActivityDiscountVO groupBuyActivityDiscountVO = trialBalanceEntity.getGroupBuyActivityDiscountVO();
|
||||
|
||||
// 查询 outTradeNo 是否已经存在交易记录
|
||||
MarketPayOrderEntity marketPayOrderEntityOld = tradeOrderService.queryNoPayMarketPayOrderByOutTradeNo(userId, outTradeNo);
|
||||
if (null != marketPayOrderEntityOld) {
|
||||
log.info("测试结果(Old):{}", JSON.toJSONString(marketPayOrderEntityOld));
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 锁定,营销预支付订单;商品下单前,预购锁定。
|
||||
MarketPayOrderEntity marketPayOrderEntityNew = tradeOrderService.lockMarketPayOrder(
|
||||
UserEntity.builder().userId(userId).build(),
|
||||
PayActivityEntity.builder()
|
||||
.teamId(null)
|
||||
.activityId(groupBuyActivityDiscountVO.getActivityId())
|
||||
.activityName(groupBuyActivityDiscountVO.getActivityName())
|
||||
.startTime(groupBuyActivityDiscountVO.getStartTime())
|
||||
.endTime(groupBuyActivityDiscountVO.getEndTime())
|
||||
.targetCount(groupBuyActivityDiscountVO.getTarget())
|
||||
.build(),
|
||||
PayDiscountEntity.builder()
|
||||
.source(source)
|
||||
.channel(channel)
|
||||
.goodsId(goodsId)
|
||||
.goodsName(trialBalanceEntity.getGoodsName())
|
||||
.originalPrice(trialBalanceEntity.getOriginalPrice())
|
||||
.deductionPrice(trialBalanceEntity.getDeductionPrice())
|
||||
.outTradeNo(outTradeNo)
|
||||
.build());
|
||||
|
||||
log.info("测试结果(New):{}",JSON.toJSONString(marketPayOrderEntityNew));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package edu.whut.test.trigger;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import edu.whut.api.IMarketTradeService;
|
||||
import edu.whut.api.dto.LockMarketPayOrderRequestDTO;
|
||||
import edu.whut.api.dto.LockMarketPayOrderResponseDTO;
|
||||
import edu.whut.api.response.Response;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
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
|
||||
public class MarketTradeControllerTest {
|
||||
|
||||
@Resource
|
||||
private IMarketTradeService marketTradeService;
|
||||
|
||||
@Test
|
||||
public void test_lockMarketPayOrder() {
|
||||
LockMarketPayOrderRequestDTO lockMarketPayOrderRequestDTO = new LockMarketPayOrderRequestDTO();
|
||||
lockMarketPayOrderRequestDTO.setUserId("smile");
|
||||
lockMarketPayOrderRequestDTO.setTeamId(null);
|
||||
lockMarketPayOrderRequestDTO.setActivityId(100123L);
|
||||
lockMarketPayOrderRequestDTO.setGoodsId("9890001");
|
||||
lockMarketPayOrderRequestDTO.setSource("s01");
|
||||
lockMarketPayOrderRequestDTO.setChannel("c01");
|
||||
lockMarketPayOrderRequestDTO.setOutTradeNo(RandomStringUtils.randomNumeric(12));
|
||||
|
||||
Response<LockMarketPayOrderResponseDTO> lockMarketPayOrderResponseDTOResponse = marketTradeService.lockMarketPayOrder(lockMarketPayOrderRequestDTO);
|
||||
|
||||
log.info("测试结果 req:{} res:{}", JSON.toJSONString(lockMarketPayOrderRequestDTO), JSON.toJSONString(lockMarketPayOrderResponseDTOResponse));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_lockMarketPayOrder_teamId_not_null() {
|
||||
LockMarketPayOrderRequestDTO lockMarketPayOrderRequestDTO = new LockMarketPayOrderRequestDTO();
|
||||
lockMarketPayOrderRequestDTO.setUserId("smile");
|
||||
lockMarketPayOrderRequestDTO.setTeamId("41763306");
|
||||
lockMarketPayOrderRequestDTO.setActivityId(100123L);
|
||||
lockMarketPayOrderRequestDTO.setGoodsId("9890001");
|
||||
lockMarketPayOrderRequestDTO.setSource("s01");
|
||||
lockMarketPayOrderRequestDTO.setChannel("c01");
|
||||
lockMarketPayOrderRequestDTO.setOutTradeNo(RandomStringUtils.randomNumeric(12));
|
||||
|
||||
Response<LockMarketPayOrderResponseDTO> lockMarketPayOrderResponseDTOResponse = marketTradeService.lockMarketPayOrder(lockMarketPayOrderRequestDTO);
|
||||
|
||||
log.info("测试结果 req:{} res:{}", JSON.toJSONString(lockMarketPayOrderRequestDTO), JSON.toJSONString(lockMarketPayOrderResponseDTOResponse));
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
/**
|
||||
* 外部接口适配器层;当需要调用外部接口时,则创建出这一层,并定义接口,之后由基础设施层的 adapter 层具体实现
|
||||
*/
|
||||
package edu.whut.domain.activity.adapter;
|
@ -1,5 +0,0 @@
|
||||
/**
|
||||
* 仓储服务
|
||||
* 1. 定义仓储接口,之后由基础设施层做具体实现
|
||||
*/
|
||||
package edu.whut.domain.activity.adapter.repository;
|
@ -13,7 +13,8 @@ import lombok.NoArgsConstructor;
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MarketProductEntity {
|
||||
|
||||
/** 活动ID */
|
||||
private Long activityId;
|
||||
/** 用户ID */
|
||||
private String userId;
|
||||
/** 商品ID */
|
||||
|
@ -1,5 +1,6 @@
|
||||
package edu.whut.domain.activity.model.entity;
|
||||
|
||||
import edu.whut.domain.activity.model.valobj.GroupBuyActivityDiscountVO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -35,5 +36,7 @@ public class TrialBalanceEntity {
|
||||
private Boolean isVisible;
|
||||
/** 是否可参与进团 */
|
||||
private Boolean isEnable;
|
||||
/** 活动配置信息 */
|
||||
private GroupBuyActivityDiscountVO groupBuyActivityDiscountVO;
|
||||
|
||||
}
|
||||
|
@ -20,11 +20,10 @@ public class IndexGroupBuyMarketServiceImpl implements IIndexGroupBuyMarketServi
|
||||
@Override
|
||||
public TrialBalanceEntity indexMarketTrial(MarketProductEntity marketProductEntity) throws Exception {
|
||||
|
||||
// 获取执行策略
|
||||
StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> strategyHandler = defaultActivityStrategyFactory.strategyHandler();
|
||||
|
||||
TrialBalanceEntity trialBalanceEntity = strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext());
|
||||
|
||||
return trialBalanceEntity;
|
||||
// 受理试算操作
|
||||
return strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
package edu.whut.domain.activity.service;
|
@ -40,6 +40,7 @@ public class EndNode extends AbstractGroupBuyMarketSupport<MarketProductEntity,
|
||||
.endTime(groupBuyActivityDiscountVO.getEndTime())
|
||||
.isVisible(dynamicContext.isVisible())
|
||||
.isEnable(dynamicContext.isEnable())
|
||||
.groupBuyActivityDiscountVO(groupBuyActivityDiscountVO)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntit
|
||||
@Override
|
||||
protected void multiThread(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {
|
||||
// 异步查询活动配置
|
||||
QueryGroupBuyActivityDiscountVOThreadTask queryGroupBuyActivityDiscountVOThreadTask = new QueryGroupBuyActivityDiscountVOThreadTask(requestParameter.getSource(), requestParameter.getChannel(),requestParameter.getGoodsId(), repository);
|
||||
QueryGroupBuyActivityDiscountVOThreadTask queryGroupBuyActivityDiscountVOThreadTask = new QueryGroupBuyActivityDiscountVOThreadTask(requestParameter.getActivityId(), requestParameter.getSource(), requestParameter.getChannel(), requestParameter.getGoodsId(), repository);
|
||||
FutureTask<GroupBuyActivityDiscountVO> groupBuyActivityDiscountVOFutureTask = new FutureTask<>(queryGroupBuyActivityDiscountVOThreadTask);
|
||||
threadPoolExecutor.execute(groupBuyActivityDiscountVOFutureTask);
|
||||
|
||||
|
@ -13,6 +13,11 @@ import java.util.concurrent.Callable;
|
||||
@RequiredArgsConstructor
|
||||
public class QueryGroupBuyActivityDiscountVOThreadTask implements Callable<GroupBuyActivityDiscountVO> {
|
||||
|
||||
/**
|
||||
* 活动ID
|
||||
*/
|
||||
private final Long activityId;
|
||||
|
||||
/**
|
||||
* 来源
|
||||
*/
|
||||
@ -37,11 +42,16 @@ public class QueryGroupBuyActivityDiscountVOThreadTask implements Callable<Group
|
||||
// 查询活动配置,查sc_sku_activity表
|
||||
@Override
|
||||
public GroupBuyActivityDiscountVO call() throws Exception {
|
||||
//根据商品id查询活动
|
||||
SCSkuActivityVO scSkuActivityVO = activityRepository.querySCSkuActivityBySCGoodsId(source, channel, goodsId);
|
||||
if (null == scSkuActivityVO) return null;
|
||||
//根据活动id查询活动配置
|
||||
return activityRepository.queryGroupBuyActivityDiscountVO(scSkuActivityVO.getActivityId());
|
||||
// 判断是否存在可用的活动ID
|
||||
Long availableActivityId = activityId;
|
||||
if (null == activityId){
|
||||
// 查询渠道商品活动配置关联配置
|
||||
SCSkuActivityVO scSkuActivityVO = activityRepository.querySCSkuActivityBySCGoodsId(source, channel, goodsId);
|
||||
if (null == scSkuActivityVO) return null;
|
||||
availableActivityId = scSkuActivityVO.getActivityId();
|
||||
}
|
||||
// 查询活动配置
|
||||
return activityRepository.queryGroupBuyActivityDiscountVO(availableActivityId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package edu.whut.domain.trade.adapter.repository;
|
||||
|
||||
import edu.whut.domain.trade.model.aggregate.GroupBuyOrderAggregate;
|
||||
import edu.whut.domain.trade.model.entity.MarketPayOrderEntity;
|
||||
import edu.whut.domain.trade.model.valobj.GroupBuyProgressVO;
|
||||
|
||||
/**
|
||||
* 交易仓储服务接口
|
||||
*/
|
||||
public interface ITradeRepository {
|
||||
|
||||
MarketPayOrderEntity queryMarketPayOrderEntityByOutTradeNo(String userId, String outTradeNo);
|
||||
|
||||
MarketPayOrderEntity lockMarketPayOrder(GroupBuyOrderAggregate groupBuyOrderAggregate);
|
||||
|
||||
GroupBuyProgressVO queryGroupBuyProgress(String teamId);
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package edu.whut.domain.trade.model.aggregate;
|
||||
import edu.whut.domain.trade.model.entity.PayActivityEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayDiscountEntity;
|
||||
import edu.whut.domain.trade.model.entity.UserEntity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 拼团订单聚合对象;聚合可以理解用各个四肢、身体、头等组装出来一个人
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class GroupBuyOrderAggregate {
|
||||
|
||||
/** 用户实体对象 */
|
||||
private UserEntity userEntity;
|
||||
/** 支付活动实体对象 */
|
||||
private PayActivityEntity payActivityEntity;
|
||||
/** 支付优惠实体对象 */
|
||||
private PayDiscountEntity payDiscountEntity;
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package edu.whut.domain.trade.model.entity;
|
||||
import edu.whut.domain.trade.model.valobj.TradeOrderStatusEnumVO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 拼团,预购订单营销实体对象
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MarketPayOrderEntity {
|
||||
|
||||
/** 预购订单ID */
|
||||
private String orderId;
|
||||
/** 折扣金额 */
|
||||
private BigDecimal deductionPrice;
|
||||
/** 交易订单状态枚举 */
|
||||
private TradeOrderStatusEnumVO tradeOrderStatusEnumVO;
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package edu.whut.domain.trade.model.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 拼团,支付活动实体对象
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PayActivityEntity {
|
||||
|
||||
/** 拼单组队ID */
|
||||
private String teamId;
|
||||
/** 活动ID */
|
||||
private Long activityId;
|
||||
/** 活动名称 */
|
||||
private String activityName;
|
||||
/** 拼团开始时间 */
|
||||
private Date startTime;
|
||||
/** 拼团结束时间 */
|
||||
private Date endTime;
|
||||
/** 目标数量 */
|
||||
private Integer targetCount;
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package edu.whut.domain.trade.model.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 拼团,支付优惠实体对象
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PayDiscountEntity {
|
||||
|
||||
/** 渠道 */
|
||||
private String source;
|
||||
/** 来源 */
|
||||
private String channel;
|
||||
/** 商品ID */
|
||||
private String goodsId;
|
||||
/** 商品名称 */
|
||||
private String goodsName;
|
||||
/** 原始价格 */
|
||||
private BigDecimal originalPrice;
|
||||
/** 折扣金额 */
|
||||
private BigDecimal deductionPrice;
|
||||
/** 外部交易单号-确保外部调用唯一幂等 */
|
||||
private String outTradeNo;
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package edu.whut.domain.trade.model.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 用户实体对象
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class UserEntity {
|
||||
|
||||
private String userId;
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package edu.whut.domain.trade.model.valobj;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 拼团进度值对象
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class GroupBuyProgressVO {
|
||||
|
||||
/** 目标数量 */
|
||||
private Integer targetCount;
|
||||
/** 完成数量 */
|
||||
private Integer completeCount;
|
||||
/** 锁单数量 */
|
||||
private Integer lockCount;
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package edu.whut.domain.trade.model.valobj;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 交易订单状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public enum TradeOrderStatusEnumVO {
|
||||
|
||||
CREATE(0, "初始创建"),
|
||||
COMPLETE(1, "消费完成"),
|
||||
CLOSE(2, "超时关单"),
|
||||
;
|
||||
|
||||
private Integer code;
|
||||
private String info;
|
||||
|
||||
public static TradeOrderStatusEnumVO valueOf(Integer code) {
|
||||
switch (code) {
|
||||
case 0:
|
||||
return CREATE;
|
||||
case 1:
|
||||
return COMPLETE;
|
||||
case 2:
|
||||
return CLOSE;
|
||||
}
|
||||
return CREATE;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package edu.whut.domain.trade.service;
|
||||
|
||||
import edu.whut.domain.trade.model.entity.MarketPayOrderEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayActivityEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayDiscountEntity;
|
||||
import edu.whut.domain.trade.model.entity.UserEntity;
|
||||
import edu.whut.domain.trade.model.valobj.GroupBuyProgressVO;
|
||||
|
||||
/**
|
||||
* 交易订单服务接口
|
||||
*/
|
||||
public interface ITradeOrderService {
|
||||
|
||||
/**
|
||||
* 查询,未被支付消费完成的营销优惠订单
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param outTradeNo 外部唯一单号
|
||||
* @return 拼团,预购订单营销实体对象
|
||||
*/
|
||||
MarketPayOrderEntity queryNoPayMarketPayOrderByOutTradeNo(String userId, String outTradeNo);
|
||||
|
||||
/**
|
||||
* 查询拼团进度
|
||||
*
|
||||
* @param teamId 拼团ID
|
||||
* @return 进度
|
||||
*/
|
||||
GroupBuyProgressVO queryGroupBuyProgress(String teamId);
|
||||
|
||||
/**
|
||||
* 锁定,营销预支付订单;商品下单前,预购锁定。
|
||||
*
|
||||
* @param userEntity 用户实体对象
|
||||
* @param payActivityEntity 拼团,支付活动实体对象
|
||||
* @param payDiscountEntity 拼团,支付优惠实体对象
|
||||
* @return 拼团,预购订单营销实体对象
|
||||
*/
|
||||
MarketPayOrderEntity lockMarketPayOrder(UserEntity userEntity, PayActivityEntity payActivityEntity, PayDiscountEntity payDiscountEntity);
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package edu.whut.domain.trade.service;
|
||||
|
||||
import edu.whut.domain.trade.adapter.repository.ITradeRepository;
|
||||
import edu.whut.domain.trade.model.aggregate.GroupBuyOrderAggregate;
|
||||
import edu.whut.domain.trade.model.entity.MarketPayOrderEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayActivityEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayDiscountEntity;
|
||||
import edu.whut.domain.trade.model.entity.UserEntity;
|
||||
import edu.whut.domain.trade.model.valobj.GroupBuyProgressVO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 交易订单服务
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TradeOrderService implements ITradeOrderService {
|
||||
|
||||
@Resource
|
||||
private ITradeRepository repository;
|
||||
|
||||
@Override
|
||||
public MarketPayOrderEntity queryNoPayMarketPayOrderByOutTradeNo(String userId, String outTradeNo) {
|
||||
log.info("拼团交易-查询未支付营销订单:{} outTradeNo:{}", userId, outTradeNo);
|
||||
return repository.queryMarketPayOrderEntityByOutTradeNo(userId, outTradeNo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupBuyProgressVO queryGroupBuyProgress(String teamId) {
|
||||
log.info("拼团交易-查询拼单进度:{}", teamId);
|
||||
return repository.queryGroupBuyProgress(teamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarketPayOrderEntity lockMarketPayOrder(UserEntity userEntity, PayActivityEntity payActivityEntity, PayDiscountEntity payDiscountEntity) {
|
||||
log.info("拼团交易-锁定营销优惠支付订单:{} activityId:{} goodsId:{}", userEntity.getUserId(), payActivityEntity.getActivityId(), payDiscountEntity.getGoodsId());
|
||||
|
||||
// 构建聚合对象
|
||||
GroupBuyOrderAggregate groupBuyOrderAggregate = GroupBuyOrderAggregate.builder()
|
||||
.userEntity(userEntity)
|
||||
.payActivityEntity(payActivityEntity)
|
||||
.payDiscountEntity(payDiscountEntity)
|
||||
.build();
|
||||
|
||||
// 锁定聚合订单 - 这会用户只是下单还没有支付。后续会有2个流程;支付成功、超时未支付(回退)
|
||||
return repository.lockMarketPayOrder(groupBuyOrderAggregate);
|
||||
}
|
||||
|
||||
}
|
@ -9,7 +9,7 @@ import edu.whut.infrastructure.dao.IGroupBuyActivityDao;
|
||||
import edu.whut.infrastructure.dao.IGroupBuyDiscountDao;
|
||||
import edu.whut.infrastructure.dao.ISCSkuActivityDao;
|
||||
import edu.whut.infrastructure.dao.ISkuDao;
|
||||
import edu.whut.infrastructure.dao.dcc.DCCService;
|
||||
import edu.whut.infrastructure.dcc.DCCService;
|
||||
import edu.whut.infrastructure.dao.po.GroupBuyActivity;
|
||||
import edu.whut.infrastructure.dao.po.GroupBuyDiscount;
|
||||
import edu.whut.infrastructure.dao.po.SCSkuActivity;
|
||||
|
@ -0,0 +1,156 @@
|
||||
package edu.whut.infrastructure.adapter.repository;
|
||||
|
||||
import edu.whut.domain.trade.adapter.repository.ITradeRepository;
|
||||
import edu.whut.domain.trade.model.aggregate.GroupBuyOrderAggregate;
|
||||
import edu.whut.domain.trade.model.entity.MarketPayOrderEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayActivityEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayDiscountEntity;
|
||||
import edu.whut.domain.trade.model.entity.UserEntity;
|
||||
import edu.whut.domain.trade.model.valobj.GroupBuyProgressVO;
|
||||
import edu.whut.domain.trade.model.valobj.TradeOrderStatusEnumVO;
|
||||
import edu.whut.infrastructure.dao.IGroupBuyOrderDao;
|
||||
import edu.whut.infrastructure.dao.IGroupBuyOrderListDao;
|
||||
import edu.whut.infrastructure.dao.po.GroupBuyOrder;
|
||||
import edu.whut.infrastructure.dao.po.GroupBuyOrderList;
|
||||
import edu.whut.types.enums.ResponseCode;
|
||||
import edu.whut.types.exception.AppException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 仓储实现:负责把领域对象 <-> 数据库 PO 的转换与持久化;
|
||||
* 不做任何业务规则判断,只保证数据一致性。
|
||||
*/
|
||||
@Slf4j
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class TradeRepository implements ITradeRepository {
|
||||
|
||||
private final IGroupBuyOrderDao groupBuyOrderDao;
|
||||
|
||||
private final IGroupBuyOrderListDao groupBuyOrderListDao;
|
||||
|
||||
/**
|
||||
* 根据外部交易号 & 用户id 查询未支付的锁单记录(用于幂等)
|
||||
*/
|
||||
@Override
|
||||
public MarketPayOrderEntity queryMarketPayOrderEntityByOutTradeNo(String userId, String outTradeNo) {
|
||||
// 只拼装查询条件需要的字段
|
||||
GroupBuyOrderList query = new GroupBuyOrderList();
|
||||
query.setUserId(userId);
|
||||
query.setOutTradeNo(outTradeNo);
|
||||
|
||||
GroupBuyOrderList po = groupBuyOrderListDao.queryGroupBuyOrderRecordByOutTradeNo(query);
|
||||
if (po == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 只返回上层真正关心的属性
|
||||
return MarketPayOrderEntity.builder()
|
||||
.orderId(po.getOrderId())
|
||||
.deductionPrice(po.getDeductionPrice())
|
||||
.tradeOrderStatusEnumVO(TradeOrderStatusEnumVO.valueOf(po.getStatus()))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定营销预支付订单<br>
|
||||
* - 新团:自己创建团,写入 group_buy_order,再写入 group_buy_order_list<br>
|
||||
* - 老团:更新 lock_count,满员时抛异常<br>
|
||||
* 事务超时 500 ms,成功后返回锁定好的订单信息
|
||||
*/
|
||||
@Transactional(timeout = 500)
|
||||
@Override
|
||||
public MarketPayOrderEntity lockMarketPayOrder(GroupBuyOrderAggregate agg) {
|
||||
|
||||
// 从聚合中拆出各子实体(聚合内已校验业务规则)
|
||||
UserEntity user = agg.getUserEntity();
|
||||
PayActivityEntity activity = agg.getPayActivityEntity();
|
||||
PayDiscountEntity discount = agg.getPayDiscountEntity();
|
||||
|
||||
/* ---------- 1. 处理 group_buy_order(团单主表) ---------- */
|
||||
String teamId = activity.getTeamId();
|
||||
if (StringUtils.isBlank(teamId)) {
|
||||
// 新建团队,随机 8 位数字作 teamId(示例中用 RandomStringUtils,线上可换雪花算法等)
|
||||
teamId = RandomStringUtils.randomNumeric(8);
|
||||
|
||||
GroupBuyOrder orderPo = GroupBuyOrder.builder()
|
||||
.teamId(teamId)
|
||||
.activityId(activity.getActivityId())
|
||||
.source(discount.getSource())
|
||||
.channel(discount.getChannel())
|
||||
.originalPrice(discount.getOriginalPrice())
|
||||
.deductionPrice(discount.getDeductionPrice())
|
||||
.payPrice(discount.getDeductionPrice()) // 直减后应付价格
|
||||
.targetCount(activity.getTargetCount())
|
||||
.completeCount(0)
|
||||
.lockCount(1) // 首单已锁定
|
||||
.build();
|
||||
|
||||
groupBuyOrderDao.insert(orderPo);
|
||||
} else {
|
||||
// 参加已有团队,先尝试把 lockCount +1 返回的是满足条件的记录个数。
|
||||
int rows = groupBuyOrderDao.updateAddLockCount(teamId);
|
||||
if (rows != 1) {
|
||||
// 返回特定业务异常:拼团已满员
|
||||
throw new AppException(ResponseCode.E0005);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 2. 写入 group_buy_order_list(团单明细表) ---------- */
|
||||
String orderId = RandomStringUtils.randomNumeric(12); // 订单号
|
||||
|
||||
GroupBuyOrderList orderListPo = GroupBuyOrderList.builder()
|
||||
.userId(user.getUserId())
|
||||
.teamId(teamId)
|
||||
.orderId(orderId)
|
||||
.activityId(activity.getActivityId())
|
||||
.startTime(activity.getStartTime())
|
||||
.endTime(activity.getEndTime())
|
||||
.goodsId(discount.getGoodsId())
|
||||
.source(discount.getSource())
|
||||
.channel(discount.getChannel())
|
||||
.originalPrice(discount.getOriginalPrice())
|
||||
.deductionPrice(discount.getDeductionPrice())
|
||||
.status(TradeOrderStatusEnumVO.CREATE.getCode()) // 0 = 初始锁定
|
||||
.outTradeNo(discount.getOutTradeNo())
|
||||
.build();
|
||||
|
||||
try {
|
||||
groupBuyOrderListDao.insert(orderListPo);
|
||||
} catch (DuplicateKeyException e) {
|
||||
// 幂等冲突:同一 outTradeNo 已存在
|
||||
throw new AppException(ResponseCode.INDEX_EXCEPTION);
|
||||
}
|
||||
|
||||
/* ---------- 3. 返回领域对象给上层 ---------- */
|
||||
return MarketPayOrderEntity.builder()
|
||||
.orderId(orderId)
|
||||
.deductionPrice(discount.getDeductionPrice())
|
||||
.tradeOrderStatusEnumVO(TradeOrderStatusEnumVO.CREATE)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询拼团进度(当前已完成、目标、已锁定人数)
|
||||
*/
|
||||
@Override
|
||||
public GroupBuyProgressVO queryGroupBuyProgress(String teamId) {
|
||||
GroupBuyOrder po = groupBuyOrderDao.queryGroupBuyProgress(teamId);
|
||||
if (po == null) {
|
||||
return null;
|
||||
}
|
||||
return GroupBuyProgressVO.builder()
|
||||
.completeCount(po.getCompleteCount())
|
||||
.targetCount(po.getTargetCount())
|
||||
.lockCount(po.getLockCount())
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package edu.whut.infrastructure.dao;
|
||||
import edu.whut.infrastructure.dao.po.GroupBuyOrder;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户拼单
|
||||
*/
|
||||
@Mapper
|
||||
public interface IGroupBuyOrderDao {
|
||||
|
||||
void insert(GroupBuyOrder groupBuyOrder);
|
||||
|
||||
/**
|
||||
* 更新锁单数量
|
||||
* @param teamId
|
||||
* @return
|
||||
*/
|
||||
int updateAddLockCount(String teamId);
|
||||
|
||||
/**
|
||||
* 减少锁单数量
|
||||
* @param teamId
|
||||
* @return
|
||||
*/
|
||||
int updateSubtractionLockCount(String teamId);
|
||||
|
||||
/**
|
||||
* 查询拼团人数是否已满
|
||||
* @param teamId
|
||||
* @return
|
||||
*/
|
||||
GroupBuyOrder queryGroupBuyProgress(String teamId);
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package edu.whut.infrastructure.dao;
|
||||
import edu.whut.infrastructure.dao.po.GroupBuyOrderList;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户拼单明细
|
||||
*/
|
||||
@Mapper
|
||||
public interface IGroupBuyOrderListDao {
|
||||
|
||||
void insert(GroupBuyOrderList groupBuyOrderListReq);
|
||||
|
||||
/**
|
||||
* 根据外部交易单号查询是否有记录
|
||||
* @param groupBuyOrderListReq
|
||||
* @return
|
||||
*/
|
||||
GroupBuyOrderList queryGroupBuyOrderRecordByOutTradeNo(GroupBuyOrderList groupBuyOrderListReq);
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
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 GroupBuyOrder {
|
||||
|
||||
/** 自增ID */
|
||||
private Long id;
|
||||
/** 拼单组队ID */
|
||||
private String teamId;
|
||||
/** 活动ID */
|
||||
private Long activityId;
|
||||
/** 渠道 */
|
||||
private String source;
|
||||
/** 来源 */
|
||||
private String channel;
|
||||
/** 原始价格 */
|
||||
private BigDecimal originalPrice;
|
||||
/** 折扣金额 */
|
||||
private BigDecimal deductionPrice;
|
||||
/** 支付价格 */
|
||||
private BigDecimal payPrice;
|
||||
/** 目标数量 */
|
||||
private Integer targetCount;
|
||||
/** 完成数量 */
|
||||
private Integer completeCount;
|
||||
/** 锁单数量 */
|
||||
private Integer lockCount;
|
||||
/** 状态(0-拼单中、1-完成、2-失败) */
|
||||
private Integer status;
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
/** 更新时间 */
|
||||
private Date updateTime;
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
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 GroupBuyOrderList {
|
||||
|
||||
/** 自增ID */
|
||||
private Long id;
|
||||
/** 用户ID */
|
||||
private String userId;
|
||||
/** 拼单组队ID */
|
||||
private String teamId;
|
||||
/** 订单ID */
|
||||
private String orderId;
|
||||
/** 活动ID */
|
||||
private Long activityId;
|
||||
/** 活动开始时间 */
|
||||
private Date startTime;
|
||||
/** 活动结束时间 */
|
||||
private Date endTime;
|
||||
/** 商品ID */
|
||||
private String goodsId;
|
||||
/** 渠道 */
|
||||
private String source;
|
||||
/** 来源 */
|
||||
private String channel;
|
||||
/** 原始价格 */
|
||||
private BigDecimal originalPrice;
|
||||
/** 折扣金额 */
|
||||
private BigDecimal deductionPrice;
|
||||
/** 状态;0初始锁定、1消费完成 */
|
||||
private Integer status;
|
||||
/** 外部交易单号-确保外部调用唯一幂等 */
|
||||
private String outTradeNo;
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
/** 更新时间 */
|
||||
private Date updateTime;
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package edu.whut.infrastructure.dao.dcc;
|
||||
package edu.whut.infrastructure.dcc;
|
||||
import edu.whut.types.annotations.DCCValue;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -0,0 +1,172 @@
|
||||
package edu.whut.trigger.http;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import edu.whut.api.IMarketTradeService;
|
||||
import edu.whut.api.dto.LockMarketPayOrderRequestDTO;
|
||||
import edu.whut.api.dto.LockMarketPayOrderResponseDTO;
|
||||
import edu.whut.api.response.Response;
|
||||
import edu.whut.domain.activity.model.entity.MarketProductEntity;
|
||||
import edu.whut.domain.activity.model.entity.TrialBalanceEntity;
|
||||
import edu.whut.domain.activity.model.valobj.GroupBuyActivityDiscountVO;
|
||||
import edu.whut.domain.activity.service.IIndexGroupBuyMarketService;
|
||||
import edu.whut.domain.trade.model.entity.MarketPayOrderEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayActivityEntity;
|
||||
import edu.whut.domain.trade.model.entity.PayDiscountEntity;
|
||||
import edu.whut.domain.trade.model.entity.UserEntity;
|
||||
import edu.whut.domain.trade.model.valobj.GroupBuyProgressVO;
|
||||
import edu.whut.domain.trade.service.ITradeOrderService;
|
||||
import edu.whut.types.enums.ResponseCode;
|
||||
import edu.whut.types.exception.AppException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 营销交易服务
|
||||
* 对接 HTTP 层,只做「适配 + 编排」,
|
||||
* 具体业务规则(价格计算、拼团人数校验等)下沉到 Domain / Application Service。
|
||||
* 锁单完整流程:
|
||||
* 基础参数校验
|
||||
* 判断是否已有未支付订单
|
||||
* (拼团场景)校验团队目标是否已达成
|
||||
* 向营销服务做一次优惠试算
|
||||
* 组装领域对象并调用交易应用服务锁单
|
||||
* 返回锁单结果
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@CrossOrigin("*")
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/v1/gbm/trade/")
|
||||
public class MarketTradeController implements IMarketTradeService {
|
||||
|
||||
private final IIndexGroupBuyMarketService indexGroupBuyMarketService;
|
||||
|
||||
private final ITradeOrderService tradeOrderService;
|
||||
|
||||
/**
|
||||
* 锁定营销预支付订单(拼团 / 普通优惠)
|
||||
*
|
||||
* @param lockMarketPayOrderRequestDTO 请求参数
|
||||
* @return 统一响应结果
|
||||
*/
|
||||
@Override
|
||||
public Response<LockMarketPayOrderResponseDTO> lockMarketPayOrder(LockMarketPayOrderRequestDTO lockMarketPayOrderRequestDTO) {
|
||||
try {
|
||||
/* ---------- 1. 基础参数提取与校验 ---------- */
|
||||
String userId = lockMarketPayOrderRequestDTO.getUserId();
|
||||
String source = lockMarketPayOrderRequestDTO.getSource();
|
||||
String channel = lockMarketPayOrderRequestDTO.getChannel();
|
||||
String goodsId = lockMarketPayOrderRequestDTO.getGoodsId();
|
||||
Long activityId = lockMarketPayOrderRequestDTO.getActivityId();
|
||||
String outTradeNo = lockMarketPayOrderRequestDTO.getOutTradeNo();
|
||||
String teamId = lockMarketPayOrderRequestDTO.getTeamId(); //可为空
|
||||
|
||||
log.info("拼团交易锁单入参 userId={} req={}", userId, JSON.toJSONString(lockMarketPayOrderRequestDTO));
|
||||
|
||||
// 空值校验(任何一个关键字段为空则直接返回错误)
|
||||
if (StringUtils.isAnyBlank(userId, source, channel, goodsId) || activityId == null) {
|
||||
return Response.<LockMarketPayOrderResponseDTO>builder()
|
||||
.code(ResponseCode.ILLEGAL_PARAMETER.getCode())
|
||||
.info(ResponseCode.ILLEGAL_PARAMETER.getInfo())
|
||||
.build();
|
||||
}
|
||||
|
||||
/* ---------- 2. 查询是否已存在未支付锁单 ---------- */
|
||||
MarketPayOrderEntity marketPayOrderEntity =
|
||||
tradeOrderService.queryNoPayMarketPayOrderByOutTradeNo(userId, outTradeNo);
|
||||
if (marketPayOrderEntity != null) {
|
||||
// 若已锁单未支付,直接返回原锁单信息(幂等)
|
||||
return buildSuccessResp(marketPayOrderEntity);
|
||||
}
|
||||
|
||||
/* ---------- 3. 拼团目标人数校验(teamId 不为空表示 join 团) ---------- */
|
||||
if (teamId != null) {
|
||||
// 目标数量 完成下单数量 锁单数量
|
||||
GroupBuyProgressVO progress = tradeOrderService.queryGroupBuyProgress(teamId);
|
||||
// 如果目标人数已满,则拒绝继续锁单
|
||||
if (progress != null && Objects.equals(progress.getTargetCount(), progress.getLockCount())) {
|
||||
log.info("交易锁单拦截-拼单目标已达成 userId={} teamId={} target={} lock={}",
|
||||
userId, teamId, progress.getTargetCount(), progress.getLockCount());
|
||||
return Response.<LockMarketPayOrderResponseDTO>builder()
|
||||
.code(ResponseCode.E0006.getCode())
|
||||
.info(ResponseCode.E0006.getInfo())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 4. 调用营销服务进行优惠试算 ---------- */
|
||||
TrialBalanceEntity trialBalance = indexGroupBuyMarketService.indexMarketTrial(
|
||||
MarketProductEntity.builder()
|
||||
.userId(userId)
|
||||
.source(source)
|
||||
.channel(channel)
|
||||
.goodsId(goodsId)
|
||||
.activityId(activityId)
|
||||
.build());
|
||||
//获取拼团活动配置信息
|
||||
GroupBuyActivityDiscountVO discountVO = trialBalance.getGroupBuyActivityDiscountVO();
|
||||
|
||||
/* ---------- 5. 组装领域对象后调用应用服务锁单 ---------- */
|
||||
marketPayOrderEntity = tradeOrderService.lockMarketPayOrder(
|
||||
UserEntity.builder().userId(userId).build(),
|
||||
PayActivityEntity.builder()
|
||||
.teamId(teamId)
|
||||
.activityId(activityId)
|
||||
.activityName(discountVO.getActivityName())
|
||||
.startTime(discountVO.getStartTime())
|
||||
.endTime(discountVO.getEndTime())
|
||||
.targetCount(discountVO.getTarget())
|
||||
.build(),
|
||||
PayDiscountEntity.builder()
|
||||
.source(source)
|
||||
.channel(channel)
|
||||
.goodsId(goodsId)
|
||||
.goodsName(trialBalance.getGoodsName())
|
||||
.originalPrice(trialBalance.getOriginalPrice())
|
||||
.deductionPrice(trialBalance.getDeductionPrice())
|
||||
.outTradeNo(outTradeNo)
|
||||
.build());
|
||||
|
||||
log.info("交易锁单成功 userId={} order={}", userId, JSON.toJSONString(marketPayOrderEntity));
|
||||
|
||||
/* ---------- 6. 构建成功返回 ---------- */
|
||||
return buildSuccessResp(marketPayOrderEntity);
|
||||
} catch (AppException e) {
|
||||
// 可预期的业务异常(如活动已过期、优惠额度不足等)
|
||||
log.error("拼团交易锁单业务异常 userId={} req={}", lockMarketPayOrderRequestDTO.getUserId(),
|
||||
JSON.toJSONString(lockMarketPayOrderRequestDTO), e);
|
||||
return Response.<LockMarketPayOrderResponseDTO>builder()
|
||||
.code(e.getCode())
|
||||
.info(e.getInfo())
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
// 系统异常(NPE、RPC 调用失败等)
|
||||
log.error("拼团交易锁单未知错误 userId={} req={}", lockMarketPayOrderRequestDTO.getUserId(),
|
||||
JSON.toJSONString(lockMarketPayOrderRequestDTO), e);
|
||||
return Response.<LockMarketPayOrderResponseDTO>builder()
|
||||
.code(ResponseCode.UN_ERROR.getCode())
|
||||
.info(ResponseCode.UN_ERROR.getInfo())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建成功响应体,避免重复写样板代码
|
||||
*/
|
||||
private Response<LockMarketPayOrderResponseDTO> buildSuccessResp(MarketPayOrderEntity entity) {
|
||||
return Response.<LockMarketPayOrderResponseDTO>builder()
|
||||
.code(ResponseCode.SUCCESS.getCode())
|
||||
.info(ResponseCode.SUCCESS.getInfo())
|
||||
.data(LockMarketPayOrderResponseDTO.builder()
|
||||
.orderId(entity.getOrderId())
|
||||
.deductionPrice(entity.getDeductionPrice())
|
||||
.tradeOrderStatus(entity.getTradeOrderStatusEnumVO().getCode())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
}
|
@ -12,10 +12,13 @@ public enum ResponseCode {
|
||||
SUCCESS("0000", "成功"),
|
||||
UN_ERROR("0001", "未知失败"),
|
||||
ILLEGAL_PARAMETER("0002", "非法参数"),
|
||||
INDEX_EXCEPTION("0003", "唯一索引冲突"),
|
||||
E0001("E0001", "不存在对应的折扣计算服务"),
|
||||
E0002("E0002", "无拼团营销配置"),
|
||||
E0003("E0003", "拼团活动降级拦截"),
|
||||
E0004("E0004", "拼团活动切量拦截"),
|
||||
E0005("E0005", "拼团组队失败,记录更新为0"),
|
||||
E0006("E0006", "拼团组队完结,锁单量已达成"),
|
||||
;
|
||||
|
||||
private String code;
|
||||
|
@ -1,5 +1,6 @@
|
||||
package edu.whut.types.exception;
|
||||
|
||||
import edu.whut.types.enums.ResponseCode;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ -19,6 +20,11 @@ public class AppException extends RuntimeException {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public AppException(ResponseCode responseCode) {
|
||||
this.code = responseCode.getCode();
|
||||
this.info = responseCode.getInfo();
|
||||
}
|
||||
|
||||
public AppException(String code, Throwable cause) {
|
||||
this.code = code;
|
||||
super.initCause(cause);
|
||||
|
Loading…
x
Reference in New Issue
Block a user