diff --git a/docs/dev-ops/mysql/sql/0630group_buying_sys.sql b/docs/dev-ops/mysql/sql/0630group_buying_sys.sql new file mode 100644 index 0000000..022a880 --- /dev/null +++ b/docs/dev-ops/mysql/sql/0630group_buying_sys.sql @@ -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; diff --git a/docs/dev-ops/mysql/sql/0626group_buying_sys.sql b/docs/dev-ops/sql-back/0628group_buying_sys.sql similarity index 100% rename from docs/dev-ops/mysql/sql/0626group_buying_sys.sql rename to docs/dev-ops/sql-back/0628group_buying_sys.sql diff --git a/group-buying-sys-api/src/main/java/edu/whut/api/IMarketTradeService.java b/group-buying-sys-api/src/main/java/edu/whut/api/IMarketTradeService.java new file mode 100644 index 0000000..9589bdb --- /dev/null +++ b/group-buying-sys-api/src/main/java/edu/whut/api/IMarketTradeService.java @@ -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 lockMarketPayOrder(LockMarketPayOrderRequestDTO lockMarketPayOrderRequestDTO); + +} diff --git a/group-buying-sys-api/src/main/java/edu/whut/api/dto/LockMarketPayOrderRequestDTO.java b/group-buying-sys-api/src/main/java/edu/whut/api/dto/LockMarketPayOrderRequestDTO.java new file mode 100644 index 0000000..e23f29c --- /dev/null +++ b/group-buying-sys-api/src/main/java/edu/whut/api/dto/LockMarketPayOrderRequestDTO.java @@ -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; + +} diff --git a/group-buying-sys-api/src/main/java/edu/whut/api/dto/LockMarketPayOrderResponseDTO.java b/group-buying-sys-api/src/main/java/edu/whut/api/dto/LockMarketPayOrderResponseDTO.java new file mode 100644 index 0000000..50d687a --- /dev/null +++ b/group-buying-sys-api/src/main/java/edu/whut/api/dto/LockMarketPayOrderResponseDTO.java @@ -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; + +} diff --git a/group-buying-sys-api/src/main/java/edu/whut/api/dto/package-info.java b/group-buying-sys-api/src/main/java/edu/whut/api/dto/package-info.java deleted file mode 100644 index e19888a..0000000 --- a/group-buying-sys-api/src/main/java/edu/whut/api/dto/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 数据传输对象 xxxRequestDTO xxxResponseDTO - */ -package edu.whut.api.dto; \ No newline at end of file diff --git a/group-buying-sys-app/src/main/resources/mybatis/mapper/group_buy_order_list_mapper.xml b/group-buying-sys-app/src/main/resources/mybatis/mapper/group_buy_order_list_mapper.xml new file mode 100644 index 0000000..0c43d01 --- /dev/null +++ b/group-buying-sys-app/src/main/resources/mybatis/mapper/group_buy_order_list_mapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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() + ) + + + + + diff --git a/group-buying-sys-app/src/main/resources/mybatis/mapper/group_buy_order_mapper.xml b/group-buying-sys-app/src/main/resources/mybatis/mapper/group_buy_order_mapper.xml new file mode 100644 index 0000000..f9ea1a7 --- /dev/null +++ b/group-buying-sys-app/src/main/resources/mybatis/mapper/group_buy_order_mapper.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + 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() + ) + + + + + + + + update group_buy_order + set lock_count = lock_count - 1, update_time= now() + where team_id = #{teamId} and lock_count > 0 + + + + + diff --git a/group-buying-sys-app/src/main/resources/mybatis/mapper/sc_sku_activity_mapper.xml b/group-buying-sys-app/src/main/resources/mybatis/mapper/sc_sku_activity_mapper.xml index bf469df..18fd6d9 100644 --- a/group-buying-sys-app/src/main/resources/mybatis/mapper/sc_sku_activity_mapper.xml +++ b/group-buying-sys-app/src/main/resources/mybatis/mapper/sc_sku_activity_mapper.xml @@ -15,7 +15,7 @@ diff --git a/group-buying-sys-app/src/test/java/edu/whut/test/domain/trade/ITradeOrderServiceTest.java b/group-buying-sys-app/src/test/java/edu/whut/test/domain/trade/ITradeOrderServiceTest.java new file mode 100644 index 0000000..6824263 --- /dev/null +++ b/group-buying-sys-app/src/test/java/edu/whut/test/domain/trade/ITradeOrderServiceTest.java @@ -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)); + } + +} diff --git a/group-buying-sys-app/src/test/java/edu/whut/test/trigger/MarketTradeControllerTest.java b/group-buying-sys-app/src/test/java/edu/whut/test/trigger/MarketTradeControllerTest.java new file mode 100644 index 0000000..d177cf9 --- /dev/null +++ b/group-buying-sys-app/src/test/java/edu/whut/test/trigger/MarketTradeControllerTest.java @@ -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 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 lockMarketPayOrderResponseDTOResponse = marketTradeService.lockMarketPayOrder(lockMarketPayOrderRequestDTO); + + log.info("测试结果 req:{} res:{}", JSON.toJSONString(lockMarketPayOrderRequestDTO), JSON.toJSONString(lockMarketPayOrderResponseDTOResponse)); + } + +} diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/adapter/package-info.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/adapter/package-info.java deleted file mode 100644 index 107a8ad..0000000 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/adapter/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 外部接口适配器层;当需要调用外部接口时,则创建出这一层,并定义接口,之后由基础设施层的 adapter 层具体实现 - */ -package edu.whut.domain.activity.adapter; \ No newline at end of file diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/adapter/repository/package-info.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/adapter/repository/package-info.java deleted file mode 100644 index 354c4b6..0000000 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/adapter/repository/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * 仓储服务 - * 1. 定义仓储接口,之后由基础设施层做具体实现 - */ -package edu.whut.domain.activity.adapter.repository; \ No newline at end of file diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/MarketProductEntity.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/MarketProductEntity.java index e04f657..6b4e8d5 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/MarketProductEntity.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/MarketProductEntity.java @@ -13,7 +13,8 @@ import lombok.NoArgsConstructor; @AllArgsConstructor @NoArgsConstructor public class MarketProductEntity { - + /** 活动ID */ + private Long activityId; /** 用户ID */ private String userId; /** 商品ID */ diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/TrialBalanceEntity.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/TrialBalanceEntity.java index 50fece5..08ac77e 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/TrialBalanceEntity.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/model/entity/TrialBalanceEntity.java @@ -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; } diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/IndexGroupBuyMarketServiceImpl.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/IndexGroupBuyMarketServiceImpl.java index 3da2e68..e914010 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/IndexGroupBuyMarketServiceImpl.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/IndexGroupBuyMarketServiceImpl.java @@ -20,11 +20,10 @@ public class IndexGroupBuyMarketServiceImpl implements IIndexGroupBuyMarketServi @Override public TrialBalanceEntity indexMarketTrial(MarketProductEntity marketProductEntity) throws Exception { + // 获取执行策略 StrategyHandler strategyHandler = defaultActivityStrategyFactory.strategyHandler(); - - TrialBalanceEntity trialBalanceEntity = strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext()); - - return trialBalanceEntity; + // 受理试算操作 + return strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext()); } } diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/package-info.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/package-info.java deleted file mode 100644 index b048c41..0000000 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package edu.whut.domain.activity.service; \ No newline at end of file diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/node/EndNode.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/node/EndNode.java index 0f2a159..b18784a 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/node/EndNode.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/node/EndNode.java @@ -40,6 +40,7 @@ public class EndNode extends AbstractGroupBuyMarketSupport groupBuyActivityDiscountVOFutureTask = new FutureTask<>(queryGroupBuyActivityDiscountVOThreadTask); threadPoolExecutor.execute(groupBuyActivityDiscountVOFutureTask); diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/thread/QueryGroupBuyActivityDiscountVOThreadTask.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/thread/QueryGroupBuyActivityDiscountVOThreadTask.java index 6696643..4f98fb8 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/thread/QueryGroupBuyActivityDiscountVOThreadTask.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/activity/service/trial/thread/QueryGroupBuyActivityDiscountVOThreadTask.java @@ -13,6 +13,11 @@ import java.util.concurrent.Callable; @RequiredArgsConstructor public class QueryGroupBuyActivityDiscountVOThreadTask implements Callable { + /** + * 活动ID + */ + private final Long activityId; + /** * 来源 */ @@ -37,11 +42,16 @@ public class QueryGroupBuyActivityDiscountVOThreadTask implements Callable 数据库 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(); + } + + /** + * 锁定营销预支付订单
+ * - 新团:自己创建团,写入 group_buy_order,再写入 group_buy_order_list
+ * - 老团:更新 lock_count,满员时抛异常
+ * 事务超时 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(); + } +} diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/IGroupBuyOrderDao.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/IGroupBuyOrderDao.java new file mode 100644 index 0000000..ca39948 --- /dev/null +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/IGroupBuyOrderDao.java @@ -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); + +} diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/IGroupBuyOrderListDao.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/IGroupBuyOrderListDao.java new file mode 100644 index 0000000..57ac8af --- /dev/null +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/IGroupBuyOrderListDao.java @@ -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); + +} diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/GroupBuyOrder.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/GroupBuyOrder.java new file mode 100644 index 0000000..32881ae --- /dev/null +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/GroupBuyOrder.java @@ -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; + +} diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/GroupBuyOrderList.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/GroupBuyOrderList.java new file mode 100644 index 0000000..f23f6a0 --- /dev/null +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/po/GroupBuyOrderList.java @@ -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; + +} diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/dcc/DCCService.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dcc/DCCService.java similarity index 96% rename from group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/dcc/DCCService.java rename to group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dcc/DCCService.java index 95051b0..a13665a 100644 --- a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/dcc/DCCService.java +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dcc/DCCService.java @@ -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; diff --git a/group-buying-sys-trigger/src/main/java/edu/whut/trigger/http/MarketTradeController.java b/group-buying-sys-trigger/src/main/java/edu/whut/trigger/http/MarketTradeController.java new file mode 100644 index 0000000..ea0bd4a --- /dev/null +++ b/group-buying-sys-trigger/src/main/java/edu/whut/trigger/http/MarketTradeController.java @@ -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 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.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.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.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.builder() + .code(ResponseCode.UN_ERROR.getCode()) + .info(ResponseCode.UN_ERROR.getInfo()) + .build(); + } + } + + /** + * 构建成功响应体,避免重复写样板代码 + */ + private Response buildSuccessResp(MarketPayOrderEntity entity) { + return Response.builder() + .code(ResponseCode.SUCCESS.getCode()) + .info(ResponseCode.SUCCESS.getInfo()) + .data(LockMarketPayOrderResponseDTO.builder() + .orderId(entity.getOrderId()) + .deductionPrice(entity.getDeductionPrice()) + .tradeOrderStatus(entity.getTradeOrderStatusEnumVO().getCode()) + .build()) + .build(); + } +} diff --git a/group-buying-sys-types/src/main/java/edu/whut/types/enums/ResponseCode.java b/group-buying-sys-types/src/main/java/edu/whut/types/enums/ResponseCode.java index 1e4ea55..40c8c77 100644 --- a/group-buying-sys-types/src/main/java/edu/whut/types/enums/ResponseCode.java +++ b/group-buying-sys-types/src/main/java/edu/whut/types/enums/ResponseCode.java @@ -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; diff --git a/group-buying-sys-types/src/main/java/edu/whut/types/exception/AppException.java b/group-buying-sys-types/src/main/java/edu/whut/types/exception/AppException.java index d06854e..50b2282 100644 --- a/group-buying-sys-types/src/main/java/edu/whut/types/exception/AppException.java +++ b/group-buying-sys-types/src/main/java/edu/whut/types/exception/AppException.java @@ -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);