JDWA Green-U 奖品兑换模块
本文档详细介绍了JDWA Green-U项目中奖品兑换模块的设计思路、业务规则和核心实现。该模块负责奖品的管理、用户兑换流程、订单处理及物流集成,是用户环保行为获得实际回报的重要途径。
模块概述
奖品兑换模块是JDWA Green-U平台激励体系的重要组成部分,将用户通过环保活动获得的积分和成就转化为实际可感知的奖励。通过丰富多样的奖品类型和便捷的兑换流程,提升用户参与环保活动的积极性,形成"行为-奖励-行为"的良性循环。
业务价值
- 为用户提供环保行为的实际回报,增强用户获得感
- 通过多样化的奖品选择,满足不同用户的需求和喜好
- 提升用户参与平台环保活动的积极性和持续性
- 丰富平台运营活动形式,支持特定主题的奖品兑换活动
- 为合作伙伴提供生态友好产品的展示和推广渠道
- 通过数据分析,了解用户偏好,指导产品和活动设计
功能范围
- 奖品分类与管理
- 积分兑换与兑换规则设置
- 虚拟奖品与实物奖品处理
- 兑换订单管理
- 物流配送集成
- 奖品推荐与个性化展示
- 限时兑换与专题活动支持
- 兑换数据统计与分析
核心功能设计
奖品类型定义
系统支持以下几种奖品类型:
奖品类型 | 代码标识 | 说明 | 示例 |
---|---|---|---|
实物奖品 | PHYSICAL | 需要物流配送的实体商品 | 环保购物袋、不锈钢水杯、植物种子 |
虚拟商品 | VIRTUAL | 自动发放的电子商品 | 电子书、音乐下载、视频课程 |
优惠券 | COUPON | 可在平台内或合作商家使用的折扣券 | 商城折扣券、合作商家优惠券 |
会员特权 | PRIVILEGE | 平台特定功能的使用权限 | VIP功能、高级数据分析 |
公益捐赠 | DONATION | 将积分转化为公益捐赠 | 植树、环保项目支持 |
奖品等级设计
根据价值和稀缺性,奖品分为以下等级:
普通奖品(COMMON):
- 积分门槛低,库存充足
- 兑换无特殊限制
- 例如:环保袋、电子书券
精品奖品(PREMIUM):
- 中等积分要求,限量供应
- 可能有每人限兑数量
- 例如:品牌环保水杯、优质植物种子套装
珍稀奖品(RARE):
- 高积分要求,极为稀缺
- 严格的兑换条件,如需同时满足积分和特定成就
- 例如:限量版环保主题艺术品、高端环保科技产品
专属奖品(EXCLUSIVE):
- 不对外公开,仅特定用户可见
- 通常为定制化奖品或限时邀请
- 例如:环保活动VIP体验、品牌联名定制品
兑换规则设计
积分兑换规则:
- 基础规则:不同奖品设置不同的积分门槛
- 会员等级影响:高级会员可享受积分优惠
- 限时优惠:特定时段可降低积分要求
- 组合支付:部分高价值奖品支持积分+现金组合支付
兑换限制:
- 数量限制:单用户单个奖品的兑换上限
- 时间限制:特定奖品的兑换开放时间段
- 资格限制:需达到特定成就或会员等级
- 区域限制:部分实物奖品可能有配送区域限制
库存管理:
- 实时库存控制,避免超额兑换
- 库存预警机制,及时补充热门奖品
- 临时缺货时的候补登记功能
- 部分奖品采用预约制,按批次生产和发放
兑换流程设计
虚拟奖品兑换流程:
- 用户选择奖品 → 确认兑换信息
- 系统校验资格 → 扣减积分
- 生成兑换码/激活账号权限 → 发送通知
- 兑换完成,记录订单
实物奖品兑换流程:
- 用户选择奖品 → 填写收货信息
- 系统校验资格与库存 → 扣减积分
- 生成物流订单 → 等待发货
- 物流配送 → 确认收货 → 兑换完成
公益捐赠流程:
- 用户选择捐赠项目 → 确认捐赠金额
- 系统扣减对应积分 → 生成捐赠证书
- 定期执行实际捐赠 → 公示捐赠结果
- 向用户反馈捐赠成果
订单状态管理
实物奖品订单状态流转:
- 待处理(PENDING):订单创建,等待系统确认
- 已确认(CONFIRMED):订单确认,等待仓库处理
- 备货中(PREPARING):仓库正在准备商品
- 已发货(SHIPPED):商品已发出,物流配送中
- 已签收(RECEIVED):用户已签收商品
- 已完成(COMPLETED):订单完成,可评价
- 已取消(CANCELLED):订单被取消
- 退换中(RETURNING):订单正在退换流程中
- 已退换(RETURNED):退换流程完成
虚拟奖品订单状态流转:
- 待处理(PENDING):订单创建,等待系统处理
- 处理中(PROCESSING):系统正在处理虚拟奖品发放
- 已发放(DELIVERED):虚拟奖品已发放到账
- 已使用(USED):虚拟奖品已被使用
- 已过期(EXPIRED):虚拟奖品已过期失效
- 已取消(CANCELLED):订单被取消
物流集成方案
系统支持多种物流配送方式:
第三方物流API集成:
- 与主流快递公司API对接
- 自动生成运单号和面单
- 实时查询物流状态
- 支持定时提醒和异常预警
自有配送体系(适用于部分城市):
- 区域配送员派送
- 实时配送状态更新
- 用户与配送员直接沟通
门店自提点:
- 合作商家作为自提点
- 用户就近选择自提
- 凭兑换码现场领取
- 减少物流环节,更环保
奖品推荐系统
基于用户行为和偏好的个性化奖品推荐:
基础推荐维度:
- 用户历史兑换记录
- 用户环保活动偏好
- 用户基本属性(年龄、性别、地区)
- 当前积分余额
推荐策略:
- 协同过滤:相似用户喜欢的奖品
- 内容相似:与历史兑换奖品相似的新品
- 积分匹配:符合用户当前积分范围的奖品
- 热门榜单:当前平台热门兑换奖品
数据模型设计
核心表结构
奖品兑换模块主要涉及以下数据表:
- jdwa_prize:奖品信息表
- jdwa_prize_category:奖品分类表
- jdwa_prize_stock:奖品库存表
- jdwa_exchange_order:兑换订单表
- jdwa_exchange_order_item:订单明细表
- jdwa_logistics_info:物流信息表
- jdwa_address:收货地址表
- jdwa_prize_rule:兑换规则表
详细表结构如下:
CREATE TABLE `jdwa_prize` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '奖品ID',
`prize_code` varchar(50) NOT NULL COMMENT '奖品编码',
`prize_name` varchar(100) NOT NULL COMMENT '奖品名称',
`prize_type` varchar(20) NOT NULL COMMENT '奖品类型:PHYSICAL/VIRTUAL/COUPON/PRIVILEGE/DONATION',
`prize_level` varchar(20) NOT NULL DEFAULT 'COMMON' COMMENT '奖品等级:COMMON/PREMIUM/RARE/EXCLUSIVE',
`category_id` bigint(20) NOT NULL COMMENT '分类ID',
`points_price` int(11) NOT NULL COMMENT '积分价格',
`market_price` decimal(10,2) DEFAULT NULL COMMENT '市场价格',
`cash_price` decimal(10,2) DEFAULT NULL COMMENT '现金价格(组合支付时使用)',
`thumbnail` varchar(255) DEFAULT NULL COMMENT '缩略图',
`images` text COMMENT '详情图片(JSON数组)',
`description` text COMMENT '奖品描述',
`specification` text COMMENT '规格参数(JSON)',
`exchange_limit` int(11) DEFAULT NULL COMMENT '每人兑换限制',
`exchange_start` datetime DEFAULT NULL COMMENT '兑换开始时间',
`exchange_end` datetime DEFAULT NULL COMMENT '兑换结束时间',
`weight` decimal(10,2) DEFAULT NULL COMMENT '重量(kg)',
`delivery_fee` decimal(10,2) DEFAULT '0.00' COMMENT '运费',
`supplier` varchar(100) DEFAULT NULL COMMENT '供应商',
`supplier_contact` varchar(100) DEFAULT NULL COMMENT '供应商联系方式',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:0-下架,1-上架',
`is_hot` tinyint(4) DEFAULT '0' COMMENT '是否热门',
`is_new` tinyint(4) DEFAULT '0' COMMENT '是否新品',
`is_recommend` tinyint(4) DEFAULT '0' COMMENT '是否推荐',
`sort_order` int(11) DEFAULT '0' COMMENT '排序顺序',
`view_count` int(11) DEFAULT '0' COMMENT '浏览次数',
`exchange_count` int(11) DEFAULT '0' 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`),
UNIQUE KEY `uk_prize_code` (`prize_code`),
KEY `idx_category_id` (`category_id`),
KEY `idx_prize_level` (`prize_level`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='奖品信息表';
CREATE TABLE `jdwa_prize_category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`category_name` varchar(50) NOT NULL COMMENT '分类名称',
`parent_id` bigint(20) DEFAULT '0' COMMENT '父分类ID',
`icon` varchar(255) DEFAULT NULL COMMENT '分类图标',
`description` varchar(255) DEFAULT NULL COMMENT '分类描述',
`sort_order` int(11) DEFAULT '0' COMMENT '排序顺序',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
`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`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='奖品分类表';
CREATE TABLE `jdwa_prize_stock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '库存ID',
`prize_id` bigint(20) NOT NULL COMMENT '奖品ID',
`total_stock` int(11) NOT NULL DEFAULT '0' COMMENT '总库存',
`available_stock` int(11) NOT NULL DEFAULT '0' COMMENT '可用库存',
`locked_stock` int(11) NOT NULL DEFAULT '0' COMMENT '锁定库存',
`warn_stock` int(11) DEFAULT '10' COMMENT '预警库存',
`warehouse` varchar(50) DEFAULT NULL COMMENT '仓库编码',
`last_restock_time` datetime 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`),
UNIQUE KEY `uk_prize_id` (`prize_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='奖品库存表';
CREATE TABLE `jdwa_exchange_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`order_type` varchar(20) NOT NULL COMMENT '订单类型:PHYSICAL/VIRTUAL/MIXED',
`total_points` int(11) NOT NULL COMMENT '总积分',
`total_cash` decimal(10,2) DEFAULT '0.00' COMMENT '总现金(组合支付)',
`total_amount` int(11) NOT NULL COMMENT '商品总数量',
`status` varchar(20) NOT NULL COMMENT '订单状态',
`payment_status` tinyint(4) DEFAULT '0' COMMENT '支付状态:0-未支付,1-已支付',
`payment_time` datetime DEFAULT NULL COMMENT '支付时间',
`delivery_method` varchar(20) DEFAULT NULL COMMENT '配送方式:EXPRESS/SELF_DELIVERY/PICKUP',
`delivery_fee` decimal(10,2) DEFAULT '0.00' COMMENT '运费',
`address_id` bigint(20) DEFAULT NULL COMMENT '收货地址ID',
`consignee` varchar(50) DEFAULT NULL COMMENT '收货人',
`consignee_phone` varchar(20) DEFAULT NULL COMMENT '收货人电话',
`consignee_address` varchar(255) DEFAULT NULL COMMENT '收货地址',
`logistics_company` varchar(50) DEFAULT NULL COMMENT '物流公司',
`logistics_no` varchar(50) DEFAULT NULL COMMENT '物流单号',
`delivery_time` datetime DEFAULT NULL COMMENT '发货时间',
`receive_time` datetime DEFAULT NULL COMMENT '收货时间',
`completion_time` datetime DEFAULT NULL COMMENT '完成时间',
`cancel_reason` varchar(255) DEFAULT NULL COMMENT '取消原因',
`cancel_time` datetime DEFAULT NULL COMMENT '取消时间',
`remark` varchar(255) 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`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='兑换订单表';
CREATE TABLE `jdwa_exchange_order_item` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '明细ID',
`order_id` bigint(20) NOT NULL COMMENT '订单ID',
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`prize_id` bigint(20) NOT NULL COMMENT '奖品ID',
`prize_code` varchar(50) NOT NULL COMMENT '奖品编码',
`prize_name` varchar(100) NOT NULL COMMENT '奖品名称',
`prize_type` varchar(20) NOT NULL COMMENT '奖品类型',
`prize_image` varchar(255) DEFAULT NULL COMMENT '奖品图片',
`points_price` int(11) NOT NULL COMMENT '积分价格',
`cash_price` decimal(10,2) DEFAULT '0.00' COMMENT '现金价格',
`quantity` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
`total_points` int(11) NOT NULL COMMENT '总积分',
`total_cash` decimal(10,2) DEFAULT '0.00' COMMENT '总现金',
`virtual_code` varchar(100) DEFAULT NULL COMMENT '虚拟商品兑换码',
`virtual_info` text COMMENT '虚拟商品信息',
`status` varchar(20) NOT NULL COMMENT '状态',
`is_reviewed` tinyint(4) DEFAULT '0' COMMENT '是否已评价',
`expire_time` datetime 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`),
KEY `idx_order_id` (`order_id`),
KEY `idx_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_prize_id` (`prize_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
CREATE TABLE `jdwa_logistics_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '物流ID',
`order_id` bigint(20) NOT NULL COMMENT '订单ID',
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`logistics_company` varchar(50) NOT NULL COMMENT '物流公司',
`logistics_code` varchar(20) DEFAULT NULL COMMENT '物流公司编码',
`logistics_no` varchar(50) NOT NULL COMMENT '物流单号',
`sender` varchar(50) DEFAULT NULL COMMENT '发件人',
`sender_phone` varchar(20) DEFAULT NULL COMMENT '发件人电话',
`sender_address` varchar(255) DEFAULT NULL COMMENT '发货地址',
`consignee` varchar(50) NOT NULL COMMENT '收件人',
`consignee_phone` varchar(20) NOT NULL COMMENT '收件人电话',
`consignee_address` varchar(255) NOT NULL COMMENT '收货地址',
`package_weight` decimal(10,2) DEFAULT NULL COMMENT '包裹重量(kg)',
`package_quantity` int(11) DEFAULT '1' COMMENT '包裹数量',
`status` varchar(20) DEFAULT NULL COMMENT '物流状态',
`tracking_info` text COMMENT '物流跟踪信息(JSON)',
`delivery_time` datetime DEFAULT NULL COMMENT '发货时间',
`estimated_delivery` datetime DEFAULT NULL COMMENT '预计送达时间',
`actual_delivery` datetime 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`),
KEY `idx_order_id` (`order_id`),
KEY `idx_order_no` (`order_no`),
KEY `idx_logistics_no` (`logistics_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流信息表';
CREATE TABLE `jdwa_address` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '地址ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`receiver` varchar(50) NOT NULL COMMENT '收货人',
`phone` varchar(20) NOT NULL COMMENT '联系电话',
`province` varchar(20) NOT NULL COMMENT '省份',
`city` varchar(20) NOT NULL COMMENT '城市',
`district` varchar(20) NOT NULL COMMENT '区/县',
`detail_address` varchar(255) NOT NULL COMMENT '详细地址',
`post_code` varchar(10) DEFAULT NULL COMMENT '邮政编码',
`is_default` tinyint(4) DEFAULT '0' COMMENT '是否默认:0-否,1-是',
`is_deleted` tinyint(4) DEFAULT '0' COMMENT '是否删除:0-否,1-是',
`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`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货地址表';
CREATE TABLE `jdwa_prize_rule` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '规则ID',
`rule_code` varchar(50) NOT NULL COMMENT '规则编码',
`rule_name` varchar(100) NOT NULL COMMENT '规则名称',
`prize_id` bigint(20) DEFAULT NULL COMMENT '奖品ID(NULL表示全局规则)',
`rule_type` varchar(20) NOT NULL COMMENT '规则类型:DISCOUNT/LIMIT/QUALIFICATION',
`rule_condition` text NOT NULL COMMENT '规则条件(JSON)',
`rule_value` varchar(255) NOT NULL COMMENT '规则值',
`priority` int(11) DEFAULT '0' COMMENT '优先级',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
`description` varchar(255) 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`),
UNIQUE KEY `uk_rule_code` (`rule_code`),
KEY `idx_prize_id` (`prize_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='兑换规则表';
核心实体类
奖品实体 (JDWAPrize)
@Data
@TableName("jdwa_prize")
public class JDWAPrize {
@TableId(type = IdType.AUTO)
private Long id;
private String prizeCode;
private String prizeName;
private String prizeType;
private String prizeLevel;
private Long categoryId;
private Integer pointsPrice;
private BigDecimal marketPrice;
private BigDecimal cashPrice;
private String thumbnail;
private String images;
private String description;
private String specification;
private Integer exchangeLimit;
private LocalDateTime exchangeStart;
private LocalDateTime exchangeEnd;
private BigDecimal weight;
private BigDecimal deliveryFee;
private String supplier;
private String supplierContact;
private Integer status;
private Integer isHot;
private Integer isNew;
private Integer isRecommend;
private Integer sortOrder;
private Integer viewCount;
private Integer exchangeCount;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@TableField(exist = false)
private JDWAPrizeCategory category;
@TableField(exist = false)
private JDWAPrizeStock stock;
@TableField(exist = false)
private List<JDWAPrizeRule> rules;
}
奖品分类实体 (JDWAPrizeCategory)
@Data
@TableName("jdwa_prize_category")
public class JDWAPrizeCategory {
@TableId(type = IdType.AUTO)
private Long id;
private String categoryName;
private Long parentId;
private String icon;
private String description;
private Integer sortOrder;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@TableField(exist = false)
private List<JDWAPrizeCategory> children;
}
奖品库存实体 (JDWAPrizeStock)
@Data
@TableName("jdwa_prize_stock")
public class JDWAPrizeStock {
@TableId(type = IdType.AUTO)
private Long id;
private Long prizeId;
private Integer totalStock;
private Integer availableStock;
private Integer lockedStock;
private Integer warnStock;
private String warehouse;
private LocalDateTime lastRestockTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
兑换订单实体 (JDWAExchangeOrder)
@Data
@TableName("jdwa_exchange_order")
public class JDWAExchangeOrder {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private String orderType;
private Integer totalPoints;
private BigDecimal totalCash;
private Integer totalAmount;
private String status;
private Integer paymentStatus;
private LocalDateTime paymentTime;
private String deliveryMethod;
private BigDecimal deliveryFee;
private Long addressId;
private String consignee;
private String consigneePhone;
private String consigneeAddress;
private String logisticsCompany;
private String logisticsNo;
private LocalDateTime deliveryTime;
private LocalDateTime receiveTime;
private LocalDateTime completionTime;
private String cancelReason;
private LocalDateTime cancelTime;
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@TableField(exist = false)
private List<JDWAExchangeOrderItem> items;
@TableField(exist = false)
private JDWALogisticsInfo logisticsInfo;
}
订单明细实体 (JDWAExchangeOrderItem)
@Data
@TableName("jdwa_exchange_order_item")
public class JDWAExchangeOrderItem {
@TableId(type = IdType.AUTO)
private Long id;
private Long orderId;
private String orderNo;
private Long userId;
private Long prizeId;
private String prizeCode;
private String prizeName;
private String prizeType;
private String prizeImage;
private Integer pointsPrice;
private BigDecimal cashPrice;
private Integer quantity;
private Integer totalPoints;
private BigDecimal totalCash;
private String virtualCode;
private String virtualInfo;
private String status;
private Integer isReviewed;
private LocalDateTime expireTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
物流信息实体 (JDWALogisticsInfo)
@Data
@TableName("jdwa_logistics_info")
public class JDWALogisticsInfo {
@TableId(type = IdType.AUTO)
private Long id;
private Long orderId;
private String orderNo;
private String logisticsCompany;
private String logisticsCode;
private String logisticsNo;
private String sender;
private String senderPhone;
private String senderAddress;
private String consignee;
private String consigneePhone;
private String consigneeAddress;
private BigDecimal packageWeight;
private Integer packageQuantity;
private String status;
private String trackingInfo;
private LocalDateTime deliveryTime;
private LocalDateTime estimatedDelivery;
private LocalDateTime actualDelivery;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
收货地址实体 (JDWAAddress)
@Data
@TableName("jdwa_address")
public class JDWAAddress {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private String receiver;
private String phone;
private String province;
private String city;
private String district;
private String detailAddress;
private String postCode;
private Integer isDefault;
private Integer isDeleted;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
兑换规则实体 (JDWAPrizeRule)
@Data
@TableName("jdwa_prize_rule")
public class JDWAPrizeRule {
@TableId(type = IdType.AUTO)
private Long id;
private String ruleCode;
private String ruleName;
private Long prizeId;
private String ruleType;
private String ruleCondition;
private String ruleValue;
private Integer priority;
private LocalDateTime startTime;
private LocalDateTime endTime;
private Integer status;
private String description;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
常量定义
public class JDWAPrizeConstants {
// 奖品类型
public static final String PRIZE_TYPE_PHYSICAL = "PHYSICAL"; // 实物奖品
public static final String PRIZE_TYPE_VIRTUAL = "VIRTUAL"; // 虚拟商品
public static final String PRIZE_TYPE_COUPON = "COUPON"; // 优惠券
public static final String PRIZE_TYPE_PRIVILEGE = "PRIVILEGE"; // 会员特权
public static final String PRIZE_TYPE_DONATION = "DONATION"; // 公益捐赠
// 奖品等级
public static final String PRIZE_LEVEL_COMMON = "COMMON"; // 普通奖品
public static final String PRIZE_LEVEL_PREMIUM = "PREMIUM"; // 精品奖品
public static final String PRIZE_LEVEL_RARE = "RARE"; // 珍稀奖品
public static final String PRIZE_LEVEL_EXCLUSIVE = "EXCLUSIVE"; // 专属奖品
// 订单类型
public static final String ORDER_TYPE_PHYSICAL = "PHYSICAL"; // 实物订单
public static final String ORDER_TYPE_VIRTUAL = "VIRTUAL"; // 虚拟订单
public static final String ORDER_TYPE_MIXED = "MIXED"; // 混合订单
// 订单状态 - 实物
public static final String ORDER_STATUS_PENDING = "PENDING"; // 待处理
public static final String ORDER_STATUS_CONFIRMED = "CONFIRMED"; // 已确认
public static final String ORDER_STATUS_PREPARING = "PREPARING"; // 备货中
public static final String ORDER_STATUS_SHIPPED = "SHIPPED"; // 已发货
public static final String ORDER_STATUS_RECEIVED = "RECEIVED"; // 已签收
public static final String ORDER_STATUS_COMPLETED = "COMPLETED"; // 已完成
public static final String ORDER_STATUS_CANCELLED = "CANCELLED"; // 已取消
public static final String ORDER_STATUS_RETURNING = "RETURNING"; // 退换中
public static final String ORDER_STATUS_RETURNED = "RETURNED"; // 已退换
// 订单状态 - 虚拟
public static final String VIRTUAL_STATUS_PENDING = "PENDING"; // 待处理
public static final String VIRTUAL_STATUS_PROCESSING = "PROCESSING"; // 处理中
public static final String VIRTUAL_STATUS_DELIVERED = "DELIVERED"; // 已发放
public static final String VIRTUAL_STATUS_USED = "USED"; // 已使用
public static final String VIRTUAL_STATUS_EXPIRED = "EXPIRED"; // 已过期
public static final String VIRTUAL_STATUS_CANCELLED = "CANCELLED"; // 已取消
// 配送方式
public static final String DELIVERY_EXPRESS = "EXPRESS"; // 快递配送
public static final String DELIVERY_SELF_DELIVERY = "SELF_DELIVERY"; // 自有配送
public static final String DELIVERY_PICKUP = "PICKUP"; // 门店自提
// 规则类型
public static final String RULE_TYPE_DISCOUNT = "DISCOUNT"; // 积分优惠
public static final String RULE_TYPE_LIMIT = "LIMIT"; // 兑换限制
public static final String RULE_TYPE_QUALIFICATION = "QUALIFICATION"; // 资格限制
}
核心类与接口
控制器(JDWAPrizeController)
@Slf4j
@RestController
@RequestMapping("/api/prizes")
public class JDWAPrizeController {
@Autowired
private JDWAPrizeService prizeService;
/**
* 获取奖品列表
* @param categoryId 分类ID
* @param prizeType 奖品类型
* @param prizeLevel 奖品等级
* @param keyword 关键词
* @param pageNum 页码
* @param pageSize 每页大小
* @return 奖品列表
*/
@GetMapping("/list")
public Result<PageResult<JDWAPrizeVO>> getPrizeList(
@RequestParam(required = false) Long categoryId,
@RequestParam(required = false) String prizeType,
@RequestParam(required = false) String prizeLevel,
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
log.info("Get prize list: categoryId={}, prizeType={}, prizeLevel={}, keyword={}",
categoryId, prizeType, prizeLevel, keyword);
PageResult<JDWAPrizeVO> result = prizeService.getPrizeList(
categoryId, prizeType, prizeLevel, keyword, pageNum, pageSize);
return Result.success(result);
}
/**
* 获取奖品详情
* @param prizeCode 奖品编码
* @return 奖品详情
*/
@GetMapping("/detail/{prizeCode}")
public Result<JDWAPrizeDetailVO> getPrizeDetail(@PathVariable String prizeCode) {
log.info("Get prize detail: prizeCode={}", prizeCode);
JDWAPrizeDetailVO detail = prizeService.getPrizeDetail(prizeCode);
if (detail == null) {
return Result.error(ErrorCode.PRIZE_NOT_FOUND, "奖品不存在");
}
return Result.success(detail);
}
/**
* 获取奖品分类列表
* @return 分类列表
*/
@GetMapping("/categories")
public Result<List<JDWAPrizeCategoryVO>> getCategories() {
List<JDWAPrizeCategoryVO> categories = prizeService.getAllCategories();
return Result.success(categories);
}
/**
* 根据身份和条件推荐奖品
* @param type 推荐类型:HOT/NEW/RECOMMEND/POINTS_MATCH
* @param limit 限制数量
* @return 推荐奖品列表
*/
@GetMapping("/recommend")
public Result<List<JDWAPrizeVO>> getRecommendPrizes(
@RequestParam(defaultValue = "RECOMMEND") String type,
@RequestParam(defaultValue = "4") Integer limit) {
List<JDWAPrizeVO> prizes = prizeService.getRecommendPrizes(type, limit);
return Result.success(prizes);
}
/**
* 加入兑换购物车
* @param request 加入购物车请求
* @return 操作结果
*/
@PostMapping("/cart/add")
public Result<Void> addToCart(@RequestBody @Validated JDWACartAddRequest request) {
log.info("Add to cart: {}", request);
boolean success = prizeService.addToCart(request);
if (!success) {
return Result.error(ErrorCode.OPERATION_FAILED, "加入购物车失败");
}
return Result.success();
}
/**
* 获取购物车列表
* @return 购物车列表
*/
@GetMapping("/cart/list")
public Result<List<JDWACartItemVO>> getCartList() {
List<JDWACartItemVO> cartItems = prizeService.getCartItems();
return Result.success(cartItems);
}
/**
* 移除购物车商品
* @param cartItemIds 购物车项ID列表
* @return 操作结果
*/
@PostMapping("/cart/remove")
public Result<Void> removeFromCart(@RequestBody List<Long> cartItemIds) {
log.info("Remove from cart: {}", cartItemIds);
boolean success = prizeService.removeFromCart(cartItemIds);
if (!success) {
return Result.error(ErrorCode.OPERATION_FAILED, "移除购物车商品失败");
}
return Result.success();
}
/**
* 更新购物车数量
* @param cartItemId 购物车项ID
* @param quantity 数量
* @return 操作结果
*/
@PostMapping("/cart/update")
public Result<Void> updateCartQuantity(
@RequestParam Long cartItemId,
@RequestParam Integer quantity) {
log.info("Update cart quantity: cartItemId={}, quantity={}", cartItemId, quantity);
boolean success = prizeService.updateCartQuantity(cartItemId, quantity);
if (!success) {
return Result.error(ErrorCode.OPERATION_FAILED, "更新购物车数量失败");
}
return Result.success();
}
/**
* 创建兑换订单
* @param request 创建订单请求
* @return 订单编号
*/
@PostMapping("/order/create")
public Result<String> createExchangeOrder(@RequestBody @Validated JDWAExchangeOrderCreateRequest request) {
log.info("Create exchange order: {}", request);
String orderNo = prizeService.createExchangeOrder(request);
if (StringUtils.isEmpty(orderNo)) {
return Result.error(ErrorCode.OPERATION_FAILED, "创建订单失败");
}
return Result.success(orderNo);
}
/**
* 获取订单列表
* @param status 订单状态
* @param pageNum 页码
* @param pageSize 每页大小
* @return 订单列表
*/
@GetMapping("/order/list")
public Result<PageResult<JDWAExchangeOrderVO>> getOrderList(
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
PageResult<JDWAExchangeOrderVO> result = prizeService.getOrderList(status, pageNum, pageSize);
return Result.success(result);
}
/**
* 获取订单详情
* @param orderNo 订单编号
* @return 订单详情
*/
@GetMapping("/order/detail/{orderNo}")
public Result<JDWAExchangeOrderDetailVO> getOrderDetail(@PathVariable String orderNo) {
JDWAExchangeOrderDetailVO detail = prizeService.getOrderDetail(orderNo);
if (detail == null) {
return Result.error(ErrorCode.ORDER_NOT_FOUND, "订单不存在");
}
return Result.success(detail);
}
/**
* 取消订单
* @param orderNo 订单编号
* @param reason 取消原因
* @return 操作结果
*/
@PostMapping("/order/cancel")
public Result<Void> cancelOrder(
@RequestParam String orderNo,
@RequestParam String reason) {
log.info("Cancel order: orderNo={}, reason={}", orderNo, reason);
boolean success = prizeService.cancelOrder(orderNo, reason);
if (!success) {
return Result.error(ErrorCode.OPERATION_FAILED, "取消订单失败");
}
return Result.success();
}
/**
* 确认收货
* @param orderNo 订单编号
* @return 操作结果
*/
@PostMapping("/order/confirm")
public Result<Void> confirmReceive(@RequestParam String orderNo) {
log.info("Confirm receive: orderNo={}", orderNo);
boolean success = prizeService.confirmReceive(orderNo);
if (!success) {
return Result.error(ErrorCode.OPERATION_FAILED, "确认收货失败");
}
return Result.success();
}
/**
* 获取订单物流信息
* @param orderNo 订单编号
* @return 物流信息
*/
@GetMapping("/order/logistics/{orderNo}")
public Result<JDWALogisticsInfoVO> getLogisticsInfo(@PathVariable String orderNo) {
JDWALogisticsInfoVO logisticsInfo = prizeService.getLogisticsInfo(orderNo);
if (logisticsInfo == null) {
return Result.error(ErrorCode.LOGISTICS_NOT_FOUND, "物流信息不存在");
}
return Result.success(logisticsInfo);
}
/**
* 激活虚拟奖品
* @param orderItemId 订单项ID
* @return 操作结果
*/
@PostMapping("/virtual/activate")
public Result<JDWAVirtualPrizeVO> activateVirtualPrize(@RequestParam Long orderItemId) {
log.info("Activate virtual prize: orderItemId={}", orderItemId);
JDWAVirtualPrizeVO result = prizeService.activateVirtualPrize(orderItemId);
if (result == null) {
return Result.error(ErrorCode.OPERATION_FAILED, "激活虚拟奖品失败");
}
return Result.success(result);
}
/**
* 获取收货地址列表
* @return 地址列表
*/
@GetMapping("/address/list")
public Result<List<JDWAAddressVO>> getAddressList() {
List<JDWAAddressVO> addresses = prizeService.getUserAddresses();
return Result.success(addresses);
}
/**
* 添加新地址
* @param address 地址信息
* @return 操作结果
*/
@PostMapping("/address/add")
public Result<Long> addAddress(@RequestBody @Validated JDWAAddressVO address) {
log.info("Add address: {}", address);
Long addressId = prizeService.addAddress(address);
if (addressId == null) {
return Result.error(ErrorCode.OPERATION_FAILED, "添加地址失败");
}
return Result.success(addressId);
}
/**
* 修改地址
* @param address 地址信息
* @return 操作结果
*/
@PostMapping("/address/update")
public Result<Void> updateAddress(@RequestBody @Validated JDWAAddressVO address) {
log.info("Update address: {}", address);
boolean success = prizeService.updateAddress(address);
if (!success) {
return Result.error(ErrorCode.OPERATION_FAILED, "修改地址失败");
}
return Result.success();
}
/**
* 删除地址
* @param addressId 地址ID
* @return 操作结果
*/
@PostMapping("/address/delete")
public Result<Void> deleteAddress(@RequestParam Long addressId) {
log.info("Delete address: addressId={}", addressId);
boolean success = prizeService.deleteAddress(addressId);
if (!success) {
return Result.error(ErrorCode.OPERATION_FAILED, "删除地址失败");
}
return Result.success();
}
/**
* 设置默认地址
* @param addressId 地址ID
* @return 操作结果
*/
@PostMapping("/address/default")
public Result<Void> setDefaultAddress(@RequestParam Long addressId) {
log.info("Set default address: addressId={}", addressId);
boolean success = prizeService.setDefaultAddress(addressId);
if (!success) {
return Result.error(ErrorCode.OPERATION_FAILED, "设置默认地址失败");
}
return Result.success();
}
}
服务接口(JDWAPrizeService)
/**
* 奖品兑换服务接口
*/
public interface JDWAPrizeService {
/**
* 获取奖品列表
* @param categoryId 分类ID
* @param prizeType 奖品类型
* @param prizeLevel 奖品等级
* @param keyword 关键词
* @param pageNum 页码
* @param pageSize 每页大小
* @return 奖品列表
*/
PageResult<JDWAPrizeVO> getPrizeList(Long categoryId, String prizeType,
String prizeLevel, String keyword,
Integer pageNum, Integer pageSize);
/**
* 获取奖品详情
* @param prizeCode 奖品编码
* @return 奖品详情
*/
JDWAPrizeDetailVO getPrizeDetail(String prizeCode);
/**
* 获取所有奖品分类
* @return 分类列表
*/
List<JDWAPrizeCategoryVO> getAllCategories();
/**
* 根据推荐类型获取推荐奖品
* @param type 推荐类型
* @param limit 限制数量
* @return 推荐奖品列表
*/
List<JDWAPrizeVO> getRecommendPrizes(String type, Integer limit);
/**
* 加入兑换购物车
* @param request 加入购物车请求
* @return 是否成功
*/
boolean addToCart(JDWACartAddRequest request);
/**
* 获取购物车商品列表
* @return 购物车商品列表
*/
List<JDWACartItemVO> getCartItems();
/**
* 从购物车移除商品
* @param cartItemIds 购物车项ID列表
* @return 是否成功
*/
boolean removeFromCart(List<Long> cartItemIds);
/**
* 更新购物车商品数量
* @param cartItemId 购物车项ID
* @param quantity 数量
* @return 是否成功
*/
boolean updateCartQuantity(Long cartItemId, Integer quantity);
/**
* 创建兑换订单
* @param request 创建订单请求
* @return 订单编号
*/
String createExchangeOrder(JDWAExchangeOrderCreateRequest request);
/**
* 获取订单列表
* @param status 订单状态
* @param pageNum 页码
* @param pageSize 每页大小
* @return 订单列表
*/
PageResult<JDWAExchangeOrderVO> getOrderList(String status, Integer pageNum, Integer pageSize);
/**
* 获取订单详情
* @param orderNo 订单编号
* @return 订单详情
*/
JDWAExchangeOrderDetailVO getOrderDetail(String orderNo);
/**
* 取消订单
* @param orderNo 订单编号
* @param reason 取消原因
* @return 是否成功
*/
boolean cancelOrder(String orderNo, String reason);
/**
* 确认收货
* @param orderNo 订单编号
* @return 是否成功
*/
boolean confirmReceive(String orderNo);
/**
* 获取物流信息
* @param orderNo 订单编号
* @return 物流信息
*/
JDWALogisticsInfoVO getLogisticsInfo(String orderNo);
/**
* 激活虚拟奖品
* @param orderItemId 订单项ID
* @return 虚拟奖品信息
*/
JDWAVirtualPrizeVO activateVirtualPrize(Long orderItemId);
/**
* 获取用户收货地址列表
* @return 地址列表
*/
List<JDWAAddressVO> getUserAddresses();
/**
* 添加收货地址
* @param address 地址信息
* @return 地址ID
*/
Long addAddress(JDWAAddressVO address);
/**
* 更新收货地址
* @param address 地址信息
* @return 是否成功
*/
boolean updateAddress(JDWAAddressVO address);
/**
* 删除收货地址
* @param addressId 地址ID
* @return 是否成功
*/
boolean deleteAddress(Long addressId);
/**
* 设置默认收货地址
* @param addressId 地址ID
* @return 是否成功
*/
boolean setDefaultAddress(Long addressId);
/**
* 验证库存和兑换资格
* @param prizeId 奖品ID
* @param quantity 数量
* @return 验证结果
*/
JDWAExchangeVerifyResult verifyExchangeQualification(Long prizeId, Integer quantity);
/**
* 锁定奖品库存
* @param prizeId 奖品ID
* @param quantity 数量
* @return 是否成功
*/
boolean lockPrizeStock(Long prizeId, Integer quantity);
/**
* 释放奖品库存
* @param prizeId 奖品ID
* @param quantity 数量
* @return 是否成功
*/
boolean releasePrizeStock(Long prizeId, Integer quantity);
/**
* 扣减用户积分
* @param userId 用户ID
* @param points 积分数量
* @param orderNo 订单号
* @param description 描述
* @return 是否成功
*/
boolean deductUserPoints(Long userId, Integer points, String orderNo, String description);
}
服务实现类(JDWAPrizeServiceImpl)
@Slf4j
@Service
public class JDWAPrizeServiceImpl implements JDWAPrizeService {
@Autowired
private JDWAPrizeMapper prizeMapper;
@Autowired
private JDWAPrizeCategoryMapper categoryMapper;
@Autowired
private JDWAPrizeStockMapper stockMapper;
@Autowired
private JDWAPrizeRuleMapper ruleMapper;
@Autowired
private JDWACartItemMapper cartItemMapper;
@Autowired
private JDWAExchangeOrderMapper orderMapper;
@Autowired
private JDWAExchangeOrderItemMapper orderItemMapper;
@Autowired
private JDWALogisticsInfoMapper logisticsInfoMapper;
@Autowired
private JDWAAddressMapper addressMapper;
@Autowired
private JDWAUserPointsService userPointsService;
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${jdwa.prize.stock.lock.timeout:300}")
private Long stockLockTimeout;
@Value("${jdwa.prize.order.expire.minutes:30}")
private Integer orderExpireMinutes;
@Override
public PageResult<JDWAPrizeVO> getPrizeList(Long categoryId, String prizeType,
String prizeLevel, String keyword,
Integer pageNum, Integer pageSize) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 构建查询条件
LambdaQueryWrapper<JDWAPrize> wrapper = Wrappers.<JDWAPrize>lambdaQuery()
.eq(JDWAPrize::getStatus, 1) // 只查询上架商品
.eq(categoryId != null, JDWAPrize::getCategoryId, categoryId)
.eq(StringUtils.isNotEmpty(prizeType), JDWAPrize::getPrizeType, prizeType)
.eq(StringUtils.isNotEmpty(prizeLevel), JDWAPrize::getPrizeLevel, prizeLevel)
.and(StringUtils.isNotEmpty(keyword), w -> w
.like(JDWAPrize::getPrizeName, keyword)
.or()
.like(JDWAPrize::getDescription, keyword)
)
.le(JDWAPrize::getExchangeStart, LocalDateTime.now())
.ge(JDWAPrize::getExchangeEnd, LocalDateTime.now())
.orderByDesc(JDWAPrize::getIsHot)
.orderByDesc(JDWAPrize::getIsRecommend)
.orderByDesc(JDWAPrize::getSortOrder);
// 分页查询
Page<JDWAPrize> page = new Page<>(pageNum, pageSize);
Page<JDWAPrize> prizePage = prizeMapper.selectPage(page, wrapper);
// 获取奖品对应库存
List<JDWAPrize> prizeList = prizePage.getRecords();
if (CollectionUtils.isEmpty(prizeList)) {
return PageResult.empty();
}
// 获取库存信息
List<Long> prizeIds = prizeList.stream()
.map(JDWAPrize::getId)
.collect(Collectors.toList());
List<JDWAPrizeStock> stockList = stockMapper.selectList(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.in(JDWAPrizeStock::getPrizeId, prizeIds));
Map<Long, JDWAPrizeStock> stockMap = stockList.stream()
.collect(Collectors.toMap(JDWAPrizeStock::getPrizeId, stock -> stock));
// 转换为VO对象
List<JDWAPrizeVO> voList = prizeList.stream().map(prize -> {
JDWAPrizeVO vo = new JDWAPrizeVO();
BeanUtils.copyProperties(prize, vo);
// 设置库存信息
JDWAPrizeStock stock = stockMap.get(prize.getId());
if (stock != null) {
vo.setAvailableStock(stock.getAvailableStock());
} else {
vo.setAvailableStock(0);
}
// 检查用户是否有足够积分
JDWAUserPoints userPoints = userPointsService.getUserPoints(userId);
if (userPoints != null) {
vo.setHasEnoughPoints(userPoints.getAvailablePoints() >= prize.getPointsPrice());
} else {
vo.setHasEnoughPoints(false);
}
return vo;
}).collect(Collectors.toList());
return new PageResult<>(voList, prizePage.getTotal(), pageNum, pageSize);
}
@Override
public JDWAPrizeDetailVO getPrizeDetail(String prizeCode) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询奖品信息
JDWAPrize prize = prizeMapper.selectOne(
Wrappers.<JDWAPrize>lambdaQuery()
.eq(JDWAPrize::getPrizeCode, prizeCode)
.eq(JDWAPrize::getStatus, 1));
if (prize == null) {
return null;
}
// 增加浏览次数
prizeMapper.increaseViewCount(prize.getId());
// 查询库存信息
JDWAPrizeStock stock = stockMapper.selectOne(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.eq(JDWAPrizeStock::getPrizeId, prize.getId()));
// 查询分类信息
JDWAPrizeCategory category = categoryMapper.selectById(prize.getCategoryId());
// 查询规则信息
List<JDWAPrizeRule> rules = ruleMapper.selectList(
Wrappers.<JDWAPrizeRule>lambdaQuery()
.eq(JDWAPrizeRule::getPrizeId, prize.getId())
.eq(JDWAPrizeRule::getStatus, 1)
.le(JDWAPrizeRule::getStartTime, LocalDateTime.now())
.ge(JDWAPrizeRule::getEndTime, LocalDateTime.now())
.orderByDesc(JDWAPrizeRule::getPriority));
// 查询用户积分
JDWAUserPoints userPoints = userPointsService.getUserPoints(userId);
// 转换为VO对象
JDWAPrizeDetailVO detailVO = new JDWAPrizeDetailVO();
BeanUtils.copyProperties(prize, detailVO);
// 设置库存信息
if (stock != null) {
detailVO.setAvailableStock(stock.getAvailableStock());
detailVO.setTotalStock(stock.getTotalStock());
} else {
detailVO.setAvailableStock(0);
detailVO.setTotalStock(0);
}
// 设置分类信息
if (category != null) {
detailVO.setCategoryName(category.getCategoryName());
}
// 设置规则信息
if (!CollectionUtils.isEmpty(rules)) {
List<JDWAPrizeRuleVO> ruleVOs = rules.stream().map(rule -> {
JDWAPrizeRuleVO ruleVO = new JDWAPrizeRuleVO();
BeanUtils.copyProperties(rule, ruleVO);
return ruleVO;
}).collect(Collectors.toList());
detailVO.setRules(ruleVOs);
}
// 设置积分信息
if (userPoints != null) {
detailVO.setUserCurrentPoints(userPoints.getAvailablePoints());
detailVO.setHasEnoughPoints(userPoints.getAvailablePoints() >= prize.getPointsPrice());
} else {
detailVO.setUserCurrentPoints(0);
detailVO.setHasEnoughPoints(false);
}
// 设置兑换资格
detailVO.setCanExchange(verifyExchangeQualification(prize.getId(), 1).isQualified());
return detailVO;
}
@Override
public List<JDWAPrizeCategoryVO> getAllCategories() {
// 查询所有启用的分类
List<JDWAPrizeCategory> categories = categoryMapper.selectList(
Wrappers.<JDWAPrizeCategory>lambdaQuery()
.eq(JDWAPrizeCategory::getStatus, 1)
.orderByAsc(JDWAPrizeCategory::getSortOrder));
if (CollectionUtils.isEmpty(categories)) {
return Collections.emptyList();
}
// 转换为树形结构
Map<Long, List<JDWAPrizeCategory>> childrenMap = categories.stream()
.filter(category -> !category.getParentId().equals(0L))
.collect(Collectors.groupingBy(JDWAPrizeCategory::getParentId));
// 转换为VO对象
return categories.stream()
.filter(category -> category.getParentId().equals(0L))
.map(category -> {
JDWAPrizeCategoryVO vo = new JDWAPrizeCategoryVO();
BeanUtils.copyProperties(category, vo);
// 设置子分类
List<JDWAPrizeCategory> children = childrenMap.get(category.getId());
if (!CollectionUtils.isEmpty(children)) {
List<JDWAPrizeCategoryVO> childrenVOs = children.stream().map(child -> {
JDWAPrizeCategoryVO childVO = new JDWAPrizeCategoryVO();
BeanUtils.copyProperties(child, childVO);
return childVO;
}).collect(Collectors.toList());
vo.setChildren(childrenVOs);
}
return vo;
}).collect(Collectors.toList());
}
@Override
public List<JDWAPrizeVO> getRecommendPrizes(String type, Integer limit) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 构建查询条件
LambdaQueryWrapper<JDWAPrize> wrapper = Wrappers.<JDWAPrize>lambdaQuery()
.eq(JDWAPrize::getStatus, 1) // 只查询上架商品
.le(JDWAPrize::getExchangeStart, LocalDateTime.now())
.ge(JDWAPrize::getExchangeEnd, LocalDateTime.now());
// 根据推荐类型筛选
switch (type) {
case "HOT":
wrapper.eq(JDWAPrize::getIsHot, 1)
.orderByDesc(JDWAPrize::getExchangeCount);
break;
case "NEW":
wrapper.eq(JDWAPrize::getIsNew, 1)
.orderByDesc(JDWAPrize::getCreateTime);
break;
case "RECOMMEND":
wrapper.eq(JDWAPrize::getIsRecommend, 1)
.orderByDesc(JDWAPrize::getSortOrder);
break;
case "POINTS_MATCH":
// 获取用户积分
JDWAUserPoints userPoints = userPointsService.getUserPoints(userId);
if (userPoints != null) {
Integer availablePoints = userPoints.getAvailablePoints();
// 积分匹配:查询积分价格小于等于用户可用积分的奖品
wrapper.le(JDWAPrize::getPointsPrice, availablePoints)
.orderByDesc(JDWAPrize::getPointsPrice);
}
break;
default:
wrapper.orderByDesc(JDWAPrize::getSortOrder);
}
// 限制查询数量
Page<JDWAPrize> page = new Page<>(1, limit);
Page<JDWAPrize> prizePage = prizeMapper.selectPage(page, wrapper);
List<JDWAPrize> prizeList = prizePage.getRecords();
if (CollectionUtils.isEmpty(prizeList)) {
return Collections.emptyList();
}
// 获取库存信息
List<Long> prizeIds = prizeList.stream()
.map(JDWAPrize::getId)
.collect(Collectors.toList());
List<JDWAPrizeStock> stockList = stockMapper.selectList(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.in(JDWAPrizeStock::getPrizeId, prizeIds));
Map<Long, JDWAPrizeStock> stockMap = stockList.stream()
.collect(Collectors.toMap(JDWAPrizeStock::getPrizeId, stock -> stock));
// 转换为VO对象
return prizeList.stream().map(prize -> {
JDWAPrizeVO vo = new JDWAPrizeVO();
BeanUtils.copyProperties(prize, vo);
// 设置库存信息
JDWAPrizeStock stock = stockMap.get(prize.getId());
if (stock != null) {
vo.setAvailableStock(stock.getAvailableStock());
} else {
vo.setAvailableStock(0);
}
return vo;
}).collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addToCart(JDWACartAddRequest request) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询奖品信息
JDWAPrize prize = prizeMapper.selectById(request.getPrizeId());
if (prize == null || prize.getStatus() != 1) {
log.error("Prize not found or not active: prizeId={}", request.getPrizeId());
return false;
}
// 验证库存
JDWAPrizeStock stock = stockMapper.selectOne(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.eq(JDWAPrizeStock::getPrizeId, request.getPrizeId()));
if (stock == null || stock.getAvailableStock() < request.getQuantity()) {
log.error("Insufficient stock: prizeId={}, required={}, available={}",
request.getPrizeId(), request.getQuantity(),
stock != null ? stock.getAvailableStock() : 0);
return false;
}
// 验证兑换资格
JDWAExchangeVerifyResult verifyResult = verifyExchangeQualification(
request.getPrizeId(), request.getQuantity());
if (!verifyResult.isQualified()) {
log.error("Exchange qualification check failed: prizeId={}, reason={}",
request.getPrizeId(), verifyResult.getMessage());
return false;
}
// 检查购物车中是否已存在该商品
JDWACartItem existingItem = cartItemMapper.selectOne(
Wrappers.<JDWACartItem>lambdaQuery()
.eq(JDWACartItem::getUserId, userId)
.eq(JDWACartItem::getPrizeId, request.getPrizeId()));
if (existingItem != null) {
// 更新数量
existingItem.setQuantity(existingItem.getQuantity() + request.getQuantity());
existingItem.setUpdateTime(LocalDateTime.now());
cartItemMapper.updateById(existingItem);
} else {
// 创建新购物车项
JDWACartItem cartItem = new JDWACartItem();
cartItem.setUserId(userId);
cartItem.setPrizeId(request.getPrizeId());
cartItem.setPrizeCode(prize.getPrizeCode());
cartItem.setPrizeName(prize.getPrizeName());
cartItem.setPrizeType(prize.getPrizeType());
cartItem.setPrizeImage(prize.getThumbnail());
cartItem.setPointsPrice(prize.getPointsPrice());
cartItem.setCashPrice(prize.getCashPrice());
cartItem.setQuantity(request.getQuantity());
cartItem.setSelectedStatus(1);
cartItem.setCreateTime(LocalDateTime.now());
cartItem.setUpdateTime(LocalDateTime.now());
cartItemMapper.insert(cartItem);
}
return true;
}
@Override
public List<JDWACartItemVO> getCartItems() {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询购物车项
List<JDWACartItem> cartItems = cartItemMapper.selectList(
Wrappers.<JDWACartItem>lambdaQuery()
.eq(JDWACartItem::getUserId, userId)
.orderByDesc(JDWACartItem::getCreateTime));
if (CollectionUtils.isEmpty(cartItems)) {
return Collections.emptyList();
}
// 获取奖品ID列表
List<Long> prizeIds = cartItems.stream()
.map(JDWACartItem::getPrizeId)
.distinct()
.collect(Collectors.toList());
// 查询奖品库存
List<JDWAPrizeStock> stockList = stockMapper.selectList(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.in(JDWAPrizeStock::getPrizeId, prizeIds));
Map<Long, JDWAPrizeStock> stockMap = stockList.stream()
.collect(Collectors.toMap(JDWAPrizeStock::getPrizeId, stock -> stock));
// 查询奖品当前信息
List<JDWAPrize> prizeList = prizeMapper.selectList(
Wrappers.<JDWAPrize>lambdaQuery()
.in(JDWAPrize::getId, prizeIds));
Map<Long, JDWAPrize> prizeMap = prizeList.stream()
.collect(Collectors.toMap(JDWAPrize::getId, prize -> prize));
// 转换为VO对象
return cartItems.stream().map(item -> {
JDWACartItemVO vo = new JDWACartItemVO();
BeanUtils.copyProperties(item, vo);
// 更新奖品最新信息
JDWAPrize prize = prizeMap.get(item.getPrizeId());
if (prize != null) {
vo.setPointsPrice(prize.getPointsPrice());
vo.setCashPrice(prize.getCashPrice());
vo.setPrizeStatus(prize.getStatus());
// 判断是否可兑换(商品状态、时间)
boolean canExchange = prize.getStatus() == 1
&& LocalDateTime.now().isAfter(prize.getExchangeStart())
&& LocalDateTime.now().isBefore(prize.getExchangeEnd());
vo.setCanExchange(canExchange);
} else {
vo.setPrizeStatus(0);
vo.setCanExchange(false);
}
// 设置库存信息
JDWAPrizeStock stock = stockMap.get(item.getPrizeId());
if (stock != null) {
vo.setAvailableStock(stock.getAvailableStock());
vo.setStockEnough(stock.getAvailableStock() >= item.getQuantity());
} else {
vo.setAvailableStock(0);
vo.setStockEnough(false);
}
// 计算总积分和总金额
vo.setTotalPoints(vo.getPointsPrice() * item.getQuantity());
vo.setTotalCash(vo.getCashPrice() != null ?
vo.getCashPrice().multiply(new BigDecimal(item.getQuantity())) : BigDecimal.ZERO);
return vo;
}).collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean removeFromCart(List<Long> cartItemIds) {
if (CollectionUtils.isEmpty(cartItemIds)) {
return true;
}
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 删除购物车项
int result = cartItemMapper.delete(
Wrappers.<JDWACartItem>lambdaQuery()
.eq(JDWACartItem::getUserId, userId)
.in(JDWACartItem::getId, cartItemIds));
return result > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateCartQuantity(Long cartItemId, Integer quantity) {
if (quantity <= 0) {
return false;
}
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询购物车项
JDWACartItem cartItem = cartItemMapper.selectOne(
Wrappers.<JDWACartItem>lambdaQuery()
.eq(JDWACartItem::getId, cartItemId)
.eq(JDWACartItem::getUserId, userId));
if (cartItem == null) {
return false;
}
// 验证库存
JDWAPrizeStock stock = stockMapper.selectOne(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.eq(JDWAPrizeStock::getPrizeId, cartItem.getPrizeId()));
if (stock == null || stock.getAvailableStock() < quantity) {
log.error("Insufficient stock: prizeId={}, required={}, available={}",
cartItem.getPrizeId(), quantity,
stock != null ? stock.getAvailableStock() : 0);
return false;
}
// 更新数量
cartItem.setQuantity(quantity);
cartItem.setUpdateTime(LocalDateTime.now());
cartItemMapper.updateById(cartItem);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createExchangeOrder(JDWAExchangeOrderCreateRequest request) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 生成订单号
String orderNo = generateOrderNo();
// 处理订单商品
List<JDWAExchangeOrderItemCreateDTO> items = request.getItems();
if (CollectionUtils.isEmpty(items)) {
log.error("Order items cannot be empty: userId={}", userId);
return null;
}
// 检查库存和资格
Map<Long, JDWAPrize> prizeMap = new HashMap<>();
Map<Long, Integer> prizeQuantityMap = new HashMap<>();
for (JDWAExchangeOrderItemCreateDTO item : items) {
// 检查商品信息
JDWAPrize prize = prizeMapper.selectById(item.getPrizeId());
if (prize == null || prize.getStatus() != 1) {
log.error("Prize not found or not active: prizeId={}", item.getPrizeId());
return null;
}
prizeMap.put(prize.getId(), prize);
// 累加同种商品数量
prizeQuantityMap.merge(prize.getId(), item.getQuantity(), Integer::sum);
}
// 逐个检查库存和兑换资格
for (Map.Entry<Long, Integer> entry : prizeQuantityMap.entrySet()) {
Long prizeId = entry.getKey();
Integer quantity = entry.getValue();
// 检查库存
JDWAPrizeStock stock = stockMapper.selectOne(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.eq(JDWAPrizeStock::getPrizeId, prizeId));
if (stock == null || stock.getAvailableStock() < quantity) {
log.error("Insufficient stock: prizeId={}, required={}, available={}",
prizeId, quantity, stock != null ? stock.getAvailableStock() : 0);
return null;
}
// 检查兑换资格
JDWAExchangeVerifyResult verifyResult = verifyExchangeQualification(prizeId, quantity);
if (!verifyResult.isQualified()) {
log.error("Exchange qualification check failed: prizeId={}, reason={}",
prizeId, verifyResult.getMessage());
return null;
}
// 锁定库存
boolean locked = lockPrizeStock(prizeId, quantity);
if (!locked) {
log.error("Failed to lock stock: prizeId={}, quantity={}", prizeId, quantity);
return null;
}
}
try {
// 创建订单
JDWAExchangeOrder order = new JDWAExchangeOrder();
order.setOrderNo(orderNo);
order.setUserId(userId);
// 确定订单类型
boolean hasPhysical = false;
boolean hasVirtual = false;
// 计算总积分和总数量
int totalPoints = 0;
BigDecimal totalCash = BigDecimal.ZERO;
int totalAmount = 0;
// 创建订单项
List<JDWAExchangeOrderItem> orderItems = new ArrayList<>();
for (JDWAExchangeOrderItemCreateDTO itemDTO : items) {
JDWAPrize prize = prizeMap.get(itemDTO.getPrizeId());
// 判断商品类型
if (JDWAPrizeConstants.PRIZE_TYPE_PHYSICAL.equals(prize.getPrizeType())) {
hasPhysical = true;
} else {
hasVirtual = true;
}
// 创建订单项
JDWAExchangeOrderItem orderItem = new JDWAExchangeOrderItem();
orderItem.setOrderId(null); // 稍后设置
orderItem.setOrderNo(orderNo);
orderItem.setUserId(userId);
orderItem.setPrizeId(prize.getId());
orderItem.setPrizeCode(prize.getPrizeCode());
orderItem.setPrizeName(prize.getPrizeName());
orderItem.setPrizeType(prize.getPrizeType());
orderItem.setPrizeImage(prize.getThumbnail());
orderItem.setPointsPrice(prize.getPointsPrice());
orderItem.setCashPrice(prize.getCashPrice());
orderItem.setQuantity(itemDTO.getQuantity());
orderItem.setTotalPoints(prize.getPointsPrice() * itemDTO.getQuantity());
if (prize.getCashPrice() != null) {
orderItem.setTotalCash(prize.getCashPrice()
.multiply(new BigDecimal(itemDTO.getQuantity())));
} else {
orderItem.setTotalCash(BigDecimal.ZERO);
}
// 设置状态
if (JDWAPrizeConstants.PRIZE_TYPE_PHYSICAL.equals(prize.getPrizeType())) {
orderItem.setStatus(JDWAPrizeConstants.ORDER_STATUS_PENDING);
} else {
orderItem.setStatus(JDWAPrizeConstants.VIRTUAL_STATUS_PENDING);
}
// 设置过期时间(虚拟奖品)
if (!JDWAPrizeConstants.PRIZE_TYPE_PHYSICAL.equals(prize.getPrizeType())) {
orderItem.setExpireTime(LocalDateTime.now().plusDays(90)); // 设置90天有效期
}
orderItem.setCreateTime(LocalDateTime.now());
orderItem.setUpdateTime(LocalDateTime.now());
orderItems.add(orderItem);
// 累计总积分和总数量
totalPoints += orderItem.getTotalPoints();
totalCash = totalCash.add(orderItem.getTotalCash());
totalAmount += itemDTO.getQuantity();
}
// 设置订单类型
if (hasPhysical && hasVirtual) {
order.setOrderType(JDWAPrizeConstants.ORDER_TYPE_MIXED);
} else if (hasPhysical) {
order.setOrderType(JDWAPrizeConstants.ORDER_TYPE_PHYSICAL);
} else {
order.setOrderType(JDWAPrizeConstants.ORDER_TYPE_VIRTUAL);
}
// 设置订单基本信息
order.setTotalPoints(totalPoints);
order.setTotalCash(totalCash);
order.setTotalAmount(totalAmount);
// 设置订单状态
if (hasPhysical) {
order.setStatus(JDWAPrizeConstants.ORDER_STATUS_PENDING);
// 设置收货地址
if (request.getAddressId() != null) {
JDWAAddress address = addressMapper.selectById(request.getAddressId());
if (address != null) {
order.setAddressId(address.getId());
order.setConsignee(address.getReceiver());
order.setConsigneePhone(address.getPhone());
order.setConsigneeAddress(address.getProvince() + address.getCity() +
address.getDistrict() + address.getDetailAddress());
}
}
// 设置配送方式
order.setDeliveryMethod(request.getDeliveryMethod());
// 设置运费
// TODO: 根据地址和配送方式计算运费
order.setDeliveryFee(BigDecimal.ZERO);
} else {
order.setStatus(JDWAPrizeConstants.VIRTUAL_STATUS_PENDING);
}
// 设置支付状态
order.setPaymentStatus(0); // 未支付
// 设置备注
order.setRemark(request.getRemark());
order.setCreateTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
// 保存订单
orderMapper.insert(order);
// 设置订单项的订单ID并保存
for (JDWAExchangeOrderItem orderItem : orderItems) {
orderItem.setOrderId(order.getId());
orderItemMapper.insert(orderItem);
}
// 扣减用户积分
boolean deducted = deductUserPoints(userId, totalPoints, orderNo, "兑换奖品");
if (!deducted) {
log.error("Failed to deduct user points: userId={}, points={}", userId, totalPoints);
throw new RuntimeException("Failed to deduct user points");
}
// 如果有现金部分,需要处理支付
if (totalCash.compareTo(BigDecimal.ZERO) > 0) {
// TODO: 处理现金支付
} else {
// 无需现金支付,直接更新支付状态
order.setPaymentStatus(1);
order.setPaymentTime(LocalDateTime.now());
orderMapper.updateById(order);
}
// 处理虚拟奖品
if (!hasPhysical) {
// 虚拟奖品自动完成
for (JDWAExchangeOrderItem orderItem : orderItems) {
if (!JDWAPrizeConstants.PRIZE_TYPE_PHYSICAL.equals(orderItem.getPrizeType())) {
// 生成虚拟兑换码
String virtualCode = generateVirtualCode(orderItem.getPrizeId());
orderItem.setVirtualCode(virtualCode);
orderItem.setStatus(JDWAPrizeConstants.VIRTUAL_STATUS_DELIVERED);
orderItemMapper.updateById(orderItem);
}
}
// 更新订单状态
order.setStatus(JDWAPrizeConstants.VIRTUAL_STATUS_DELIVERED);
order.setCompletionTime(LocalDateTime.now());
orderMapper.updateById(order);
}
// 从购物车移除已购商品
if (!CollectionUtils.isEmpty(request.getCartItemIds())) {
cartItemMapper.delete(
Wrappers.<JDWACartItem>lambdaQuery()
.eq(JDWACartItem::getUserId, userId)
.in(JDWACartItem::getId, request.getCartItemIds()));
}
// 设置订单过期时间
redisTemplate.opsForValue().set(
String.format("order:expire:%s", orderNo),
orderNo,
orderExpireMinutes,
TimeUnit.MINUTES);
return orderNo;
} catch (Exception e) {
log.error("Create order failed: userId={}, error={}", userId, e.getMessage(), e);
// 释放锁定的库存
for (Map.Entry<Long, Integer> entry : prizeQuantityMap.entrySet()) {
releasePrizeStock(entry.getKey(), entry.getValue());
}
throw e;
}
}
/**
* 生成订单号
*/
private String generateOrderNo() {
return "JD" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now())
+ RandomStringUtils.randomNumeric(6);
}
/**
* 生成虚拟奖品兑换码
*/
private String generateVirtualCode(Long prizeId) {
return "VC" + prizeId + DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDateTime.now())
+ RandomStringUtils.randomAlphanumeric(8).toUpperCase();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean lockPrizeStock(Long prizeId, Integer quantity) {
// 先用Redis分布式锁防止并发问题
String lockKey = String.format("prize:stock:lock:%d", prizeId);
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(locked)) {
log.warn("Failed to acquire Redis lock for stock: prizeId={}", prizeId);
return false;
}
try {
// 查询当前库存
JDWAPrizeStock stock = stockMapper.selectOne(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.eq(JDWAPrizeStock::getPrizeId, prizeId));
if (stock == null || stock.getAvailableStock() < quantity) {
log.error("Insufficient stock when locking: prizeId={}, required={}, available={}",
prizeId, quantity, stock != null ? stock.getAvailableStock() : 0);
return false;
}
// 更新库存
int updated = stockMapper.lockStock(prizeId, quantity);
return updated > 0;
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean releasePrizeStock(Long prizeId, Integer quantity) {
// 先用Redis分布式锁防止并发问题
String lockKey = String.format("prize:stock:lock:%d", prizeId);
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(locked)) {
log.warn("Failed to acquire Redis lock for releasing stock: prizeId={}", prizeId);
return false;
}
try {
// 查询当前库存
JDWAPrizeStock stock = stockMapper.selectOne(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.eq(JDWAPrizeStock::getPrizeId, prizeId));
if (stock == null) {
log.error("Stock not found when releasing: prizeId={}", prizeId);
return false;
}
// 更新库存
int updated = stockMapper.releaseStock(prizeId, quantity);
return updated > 0;
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
@Override
public JDWAExchangeVerifyResult verifyExchangeQualification(Long prizeId, Integer quantity) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询奖品信息
JDWAPrize prize = prizeMapper.selectById(prizeId);
if (prize == null || prize.getStatus() != 1) {
return new JDWAExchangeVerifyResult(false, "奖品不存在或已下架");
}
// 检查兑换时间
LocalDateTime now = LocalDateTime.now();
if (prize.getExchangeStart() != null && now.isBefore(prize.getExchangeStart())) {
return new JDWAExchangeVerifyResult(false, "兑换尚未开始");
}
if (prize.getExchangeEnd() != null && now.isAfter(prize.getExchangeEnd())) {
return new JDWAExchangeVerifyResult(false, "兑换已结束");
}
// 检查库存
JDWAPrizeStock stock = stockMapper.selectOne(
Wrappers.<JDWAPrizeStock>lambdaQuery()
.eq(JDWAPrizeStock::getPrizeId, prizeId));
if (stock == null || stock.getAvailableStock() < quantity) {
return new JDWAExchangeVerifyResult(false, "库存不足");
}
// 检查用户积分
JDWAUserPoints userPoints = userPointsService.getUserPoints(userId);
if (userPoints == null || userPoints.getAvailablePoints() < prize.getPointsPrice() * quantity) {
return new JDWAExchangeVerifyResult(false, "积分不足");
}
// 检查每人兑换限制
if (prize.getExchangeLimit() != null && prize.getExchangeLimit() > 0) {
// 查询用户已兑换数量
Integer exchangedCount = orderItemMapper.selectUserExchangedCount(userId, prizeId);
if (exchangedCount != null && exchangedCount + quantity > prize.getExchangeLimit()) {
return new JDWAExchangeVerifyResult(false,
String.format("超出每人限兑%d个的限制", prize.getExchangeLimit()));
}
}
// 检查兑换规则
List<JDWAPrizeRule> rules = ruleMapper.selectList(
Wrappers.<JDWAPrizeRule>lambdaQuery()
.eq(JDWAPrizeRule::getPrizeId, prizeId)
.eq(JDWAPrizeRule::getStatus, 1)
.le(JDWAPrizeRule::getStartTime, now)
.ge(JDWAPrizeRule::getEndTime, now)
.orderByDesc(JDWAPrizeRule::getPriority));
if (!CollectionUtils.isEmpty(rules)) {
// 检查资格限制类规则
for (JDWAPrizeRule rule : rules) {
if (JDWAPrizeConstants.RULE_TYPE_QUALIFICATION.equals(rule.getRuleType())) {
// 解析规则条件
try {
QualificationRule qualificationRule =
JsonUtils.parseObject(rule.getRuleCondition(), QualificationRule.class);
if (qualificationRule != null) {
// 根据规则类型进行检查
switch (qualificationRule.getQualificationType()) {
case "USER_LEVEL":
// 检查用户等级
JDWAUser user = userService.getUserById(userId);
if (user == null || user.getUserLevel() < qualificationRule.getMinLevel()) {
return new JDWAExchangeVerifyResult(false,
String.format("需要达到%d级才能兑换", qualificationRule.getMinLevel()));
}
break;
case "ACTIVITY_COUNT":
// 检查活动参与次数
Integer activityCount =
activityService.getUserActivityCount(userId, qualificationRule.getActivityType());
if (activityCount < qualificationRule.getMinCount()) {
return new JDWAExchangeVerifyResult(false,
String.format("需要参与%d次%s活动才能兑换",
qualificationRule.getMinCount(),
qualificationRule.getActivityTypeName()));
}
break;
case "ACHIEVEMENT":
// 检查成就解锁
boolean hasAchievement =
achievementService.hasAchievement(userId, qualificationRule.getAchievementCode());
if (!hasAchievement) {
return new JDWAExchangeVerifyResult(false,
String.format("需要解锁%s成就才能兑换", qualificationRule.getAchievementName()));
}
break;
default:
log.warn("Unknown qualification type: {}", qualificationRule.getQualificationType());
}
}
} catch (Exception e) {
log.error("Failed to parse qualification rule: ruleId={}, error={}",
rule.getId(), e.getMessage(), e);
}
}
}
}
return new JDWAExchangeVerifyResult(true, "验证通过");
}
@Override
public PageResult<JDWAExchangeOrderVO> getOrderList(String status, Integer pageNum, Integer pageSize) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 构建查询条件
LambdaQueryWrapper<JDWAExchangeOrder> wrapper = Wrappers.<JDWAExchangeOrder>lambdaQuery()
.eq(JDWAExchangeOrder::getUserId, userId)
.eq(StringUtils.isNotEmpty(status), JDWAExchangeOrder::getStatus, status)
.orderByDesc(JDWAExchangeOrder::getCreateTime);
// 分页查询
Page<JDWAExchangeOrder> page = new Page<>(pageNum, pageSize);
Page<JDWAExchangeOrder> orderPage = orderMapper.selectPage(page, wrapper);
List<JDWAExchangeOrder> orderList = orderPage.getRecords();
if (CollectionUtils.isEmpty(orderList)) {
return PageResult.empty();
}
// 获取订单ID列表
List<Long> orderIds = orderList.stream()
.map(JDWAExchangeOrder::getId)
.collect(Collectors.toList());
// 查询订单项
List<JDWAExchangeOrderItem> itemList = orderItemMapper.selectList(
Wrappers.<JDWAExchangeOrderItem>lambdaQuery()
.in(JDWAExchangeOrderItem::getOrderId, orderIds));
// 按订单ID分组
Map<Long, List<JDWAExchangeOrderItem>> itemMap = itemList.stream()
.collect(Collectors.groupingBy(JDWAExchangeOrderItem::getOrderId));
// 转换为VO对象
List<JDWAExchangeOrderVO> voList = orderList.stream().map(order -> {
JDWAExchangeOrderVO vo = new JDWAExchangeOrderVO();
BeanUtils.copyProperties(order, vo);
// 设置订单项信息
List<JDWAExchangeOrderItem> items = itemMap.get(order.getId());
if (!CollectionUtils.isEmpty(items)) {
List<JDWAExchangeOrderItemVO> itemVOs = items.stream().map(item -> {
JDWAExchangeOrderItemVO itemVO = new JDWAExchangeOrderItemVO();
BeanUtils.copyProperties(item, itemVO);
return itemVO;
}).collect(Collectors.toList());
vo.setItems(itemVOs);
// 设置首个商品图片作为订单展示图
vo.setFirstPrizeImage(items.get(0).getPrizeImage());
}
return vo;
}).collect(Collectors.toList());
return new PageResult<>(voList, orderPage.getTotal(), pageNum, pageSize);
}
@Override
public JDWAExchangeOrderDetailVO getOrderDetail(String orderNo) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询订单信息
JDWAExchangeOrder order = orderMapper.selectOne(
Wrappers.<JDWAExchangeOrder>lambdaQuery()
.eq(JDWAExchangeOrder::getOrderNo, orderNo)
.eq(JDWAExchangeOrder::getUserId, userId));
if (order == null) {
return null;
}
// 查询订单项
List<JDWAExchangeOrderItem> items = orderItemMapper.selectList(
Wrappers.<JDWAExchangeOrderItem>lambdaQuery()
.eq(JDWAExchangeOrderItem::getOrderId, order.getId()));
// 查询物流信息
JDWALogisticsInfo logistics = null;
if (JDWAPrizeConstants.PRIZE_TYPE_PHYSICAL.equals(order.getOrderType())
|| JDWAPrizeConstants.PRIZE_TYPE_MIXED.equals(order.getOrderType())) {
logistics = logisticsInfoMapper.selectOne(
Wrappers.<JDWALogisticsInfo>lambdaQuery()
.eq(JDWALogisticsInfo::getOrderId, order.getId()));
}
// 转换为VO对象
JDWAExchangeOrderDetailVO detailVO = new JDWAExchangeOrderDetailVO();
BeanUtils.copyProperties(order, detailVO);
// 设置订单项信息
if (!CollectionUtils.isEmpty(items)) {
List<JDWAExchangeOrderItemVO> itemVOs = items.stream().map(item -> {
JDWAExchangeOrderItemVO itemVO = new JDWAExchangeOrderItemVO();
BeanUtils.copyProperties(item, itemVO);
return itemVO;
}).collect(Collectors.toList());
detailVO.setItems(itemVOs);
}
// 设置物流信息
if (logistics != null) {
JDWALogisticsInfoVO logisticsVO = new JDWALogisticsInfoVO();
BeanUtils.copyProperties(logistics, logisticsVO);
detailVO.setLogistics(logisticsVO);
}
return detailVO;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean cancelOrder(String orderNo, String reason) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询订单信息
JDWAExchangeOrder order = orderMapper.selectOne(
Wrappers.<JDWAExchangeOrder>lambdaQuery()
.eq(JDWAExchangeOrder::getOrderNo, orderNo)
.eq(JDWAExchangeOrder::getUserId, userId));
if (order == null) {
log.error("Order not found for cancellation: orderNo={}", orderNo);
return false;
}
// 检查订单状态
if (JDWAPrizeConstants.ORDER_TYPE_PHYSICAL.equals(order.getOrderType())
|| JDWAPrizeConstants.ORDER_TYPE_MIXED.equals(order.getOrderType())) {
// 实物订单只有待处理和已确认状态可以取消
if (!JDWAPrizeConstants.ORDER_STATUS_PENDING.equals(order.getStatus())
&& !JDWAPrizeConstants.ORDER_STATUS_CONFIRMED.equals(order.getStatus())) {
log.error("Cannot cancel order with status: orderNo={}, status={}",
orderNo, order.getStatus());
return false;
}
} else {
// 虚拟订单只有待处理和处理中状态可以取消
if (!JDWAPrizeConstants.VIRTUAL_STATUS_PENDING.equals(order.getStatus())
&& !JDWAPrizeConstants.VIRTUAL_STATUS_PROCESSING.equals(order.getStatus())) {
log.error("Cannot cancel virtual order with status: orderNo={}, status={}",
orderNo, order.getStatus());
return false;
}
}
// 查询订单项
List<JDWAExchangeOrderItem> items = orderItemMapper.selectList(
Wrappers.<JDWAExchangeOrderItem>lambdaQuery()
.eq(JDWAExchangeOrderItem::getOrderId, order.getId()));
if (CollectionUtils.isEmpty(items)) {
log.error("No order items found: orderNo={}", orderNo);
return false;
}
// 更新订单状态
order.setStatus(JDWAPrizeConstants.ORDER_STATUS_CANCELLED);
order.setCancelReason(reason);
order.setCancelTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
orderMapper.updateById(order);
// 更新订单项状态
for (JDWAExchangeOrderItem item : items) {
if (JDWAPrizeConstants.PRIZE_TYPE_PHYSICAL.equals(item.getPrizeType())) {
item.setStatus(JDWAPrizeConstants.ORDER_STATUS_CANCELLED);
} else {
item.setStatus(JDWAPrizeConstants.VIRTUAL_STATUS_CANCELLED);
}
item.setUpdateTime(LocalDateTime.now());
orderItemMapper.updateById(item);
// 释放库存
releasePrizeStock(item.getPrizeId(), item.getQuantity());
}
// 退还积分
if (order.getTotalPoints() > 0) {
userPointsService.refundPoints(
userId, order.getTotalPoints(), order.getOrderNo(), "取消订单退还积分");
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean confirmReceive(String orderNo) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询订单信息
JDWAExchangeOrder order = orderMapper.selectOne(
Wrappers.<JDWAExchangeOrder>lambdaQuery()
.eq(JDWAExchangeOrder::getOrderNo, orderNo)
.eq(JDWAExchangeOrder::getUserId, userId));
if (order == null) {
log.error("Order not found for confirmation: orderNo={}", orderNo);
return false;
}
// 检查订单状态,只有已发货状态可以确认收货
if (!JDWAPrizeConstants.ORDER_STATUS_SHIPPED.equals(order.getStatus())) {
log.error("Cannot confirm receipt for order with status: orderNo={}, status={}",
orderNo, order.getStatus());
return false;
}
// 更新订单状态
order.setStatus(JDWAPrizeConstants.ORDER_STATUS_RECEIVED);
order.setReceiveTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
// 设置完成时间
order.setCompletionTime(LocalDateTime.now());
order.setStatus(JDWAPrizeConstants.ORDER_STATUS_COMPLETED);
orderMapper.updateById(order);
// 更新订单项状态
List<JDWAExchangeOrderItem> items = orderItemMapper.selectList(
Wrappers.<JDWAExchangeOrderItem>lambdaQuery()
.eq(JDWAExchangeOrderItem::getOrderId, order.getId()));
if (!CollectionUtils.isEmpty(items)) {
for (JDWAExchangeOrderItem item : items) {
if (JDWAPrizeConstants.PRIZE_TYPE_PHYSICAL.equals(item.getPrizeType())) {
item.setStatus(JDWAPrizeConstants.ORDER_STATUS_COMPLETED);
}
item.setUpdateTime(LocalDateTime.now());
orderItemMapper.updateById(item);
}
}
// 更新物流信息
JDWALogisticsInfo logistics = logisticsInfoMapper.selectOne(
Wrappers.<JDWALogisticsInfo>lambdaQuery()
.eq(JDWALogisticsInfo::getOrderId, order.getId()));
if (logistics != null) {
logistics.setStatus("DELIVERED");
logistics.setActualDelivery(LocalDateTime.now());
logistics.setUpdateTime(LocalDateTime.now());
logisticsInfoMapper.updateById(logistics);
}
return true;
}
@Override
public JDWALogisticsInfoVO getLogisticsInfo(String orderNo) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询订单信息
JDWAExchangeOrder order = orderMapper.selectOne(
Wrappers.<JDWAExchangeOrder>lambdaQuery()
.eq(JDWAExchangeOrder::getOrderNo, orderNo)
.eq(JDWAExchangeOrder::getUserId, userId));
if (order == null) {
log.error("Order not found for logistics query: orderNo={}", orderNo);
return null;
}
// 检查订单类型,只有实物或混合订单才有物流信息
if (!JDWAPrizeConstants.ORDER_TYPE_PHYSICAL.equals(order.getOrderType())
&& !JDWAPrizeConstants.ORDER_TYPE_MIXED.equals(order.getOrderType())) {
log.error("Not a physical order: orderNo={}, type={}", orderNo, order.getOrderType());
return null;
}
// 查询物流信息
JDWALogisticsInfo logistics = logisticsInfoMapper.selectOne(
Wrappers.<JDWALogisticsInfo>lambdaQuery()
.eq(JDWALogisticsInfo::getOrderId, order.getId()));
if (logistics == null) {
return null;
}
// 转换为VO对象
JDWALogisticsInfoVO logisticsVO = new JDWALogisticsInfoVO();
BeanUtils.copyProperties(logistics, logisticsVO);
// 补充订单信息
logisticsVO.setOrderNo(order.getOrderNo());
logisticsVO.setOrderStatus(order.getStatus());
// 解析跟踪信息
if (StringUtils.isNotEmpty(logistics.getTrackingInfo())) {
try {
List<JDWALogisticsTrackingVO> trackingList =
JsonUtils.parseArray(logistics.getTrackingInfo(), JDWALogisticsTrackingVO.class);
logisticsVO.setTrackingList(trackingList);
} catch (Exception e) {
log.error("Failed to parse tracking info: logisticsId={}, error={}",
logistics.getId(), e.getMessage(), e);
}
}
return logisticsVO;
}
@Override
public JDWAVirtualPrizeVO activateVirtualPrize(Long orderItemId) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询订单项
JDWAExchangeOrderItem orderItem = orderItemMapper.selectOne(
Wrappers.<JDWAExchangeOrderItem>lambdaQuery()
.eq(JDWAExchangeOrderItem::getId, orderItemId)
.eq(JDWAExchangeOrderItem::getUserId, userId));
if (orderItem == null) {
log.error("Order item not found for activation: orderItemId={}", orderItemId);
return null;
}
// 检查订单项类型和状态
if (JDWAPrizeConstants.PRIZE_TYPE_PHYSICAL.equals(orderItem.getPrizeType())) {
log.error("Cannot activate physical prize: orderItemId={}", orderItemId);
return null;
}
if (!JDWAPrizeConstants.VIRTUAL_STATUS_DELIVERED.equals(orderItem.getStatus())) {
log.error("Cannot activate prize with status: orderItemId={}, status={}",
orderItemId, orderItem.getStatus());
return null;
}
// 检查是否已过期
if (orderItem.getExpireTime() != null && LocalDateTime.now().isAfter(orderItem.getExpireTime())) {
log.error("Virtual prize has expired: orderItemId={}, expireTime={}",
orderItemId, orderItem.getExpireTime());
return null;
}
// 更新状态为已使用
orderItem.setStatus(JDWAPrizeConstants.VIRTUAL_STATUS_USED);
orderItem.setUpdateTime(LocalDateTime.now());
orderItemMapper.updateById(orderItem);
// 构建虚拟奖品信息
JDWAVirtualPrizeVO vo = new JDWAVirtualPrizeVO();
vo.setOrderItemId(orderItem.getId());
vo.setOrderNo(orderItem.getOrderNo());
vo.setPrizeId(orderItem.getPrizeId());
vo.setPrizeCode(orderItem.getPrizeCode());
vo.setPrizeName(orderItem.getPrizeName());
vo.setPrizeType(orderItem.getPrizeType());
vo.setVirtualCode(orderItem.getVirtualCode());
// 解析虚拟商品信息
if (StringUtils.isNotEmpty(orderItem.getVirtualInfo())) {
try {
Map<String, Object> virtualInfo =
JsonUtils.parseObject(orderItem.getVirtualInfo(), new TypeReference<Map<String, Object>>() {});
vo.setVirtualInfo(virtualInfo);
} catch (Exception e) {
log.error("Failed to parse virtual info: orderItemId={}, error={}",
orderItemId, e.getMessage(), e);
}
}
vo.setExpireTime(orderItem.getExpireTime());
vo.setStatus(orderItem.getStatus());
return vo;
}
@Override
public List<JDWAAddressVO> getUserAddresses() {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询用户地址
List<JDWAAddress> addresses = addressMapper.selectList(
Wrappers.<JDWAAddress>lambdaQuery()
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDeleted, 0)
.orderByDesc(JDWAAddress::getIsDefault)
.orderByDesc(JDWAAddress::getUpdateTime));
if (CollectionUtils.isEmpty(addresses)) {
return Collections.emptyList();
}
// 转换为VO对象
return addresses.stream().map(address -> {
JDWAAddressVO vo = new JDWAAddressVO();
BeanUtils.copyProperties(address, vo);
return vo;
}).collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long addAddress(JDWAAddressVO addressVO) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 检查用户地址数量
Integer addressCount = addressMapper.selectCount(
Wrappers.<JDWAAddress>lambdaQuery()
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDeleted, 0));
if (addressCount >= 20) {
log.error("Too many addresses: userId={}, count={}", userId, addressCount);
return null;
}
// 创建地址
JDWAAddress address = new JDWAAddress();
BeanUtils.copyProperties(addressVO, address);
address.setUserId(userId);
// 如果是第一个地址,设为默认
if (addressCount == 0) {
address.setIsDefault(1);
} else if (addressVO.getIsDefault() != null && addressVO.getIsDefault() == 1) {
// 如果设为默认,先取消其他默认地址
addressMapper.update(null,
Wrappers.<JDWAAddress>lambdaUpdate()
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDefault, 1)
.set(JDWAAddress::getIsDefault, 0));
}
address.setCreateTime(LocalDateTime.now());
address.setUpdateTime(LocalDateTime.now());
addressMapper.insert(address);
return address.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateAddress(JDWAAddressVO addressVO) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询地址是否存在
JDWAAddress existingAddress = addressMapper.selectOne(
Wrappers.<JDWAAddress>lambdaQuery()
.eq(JDWAAddress::getId, addressVO.getId())
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDeleted, 0));
if (existingAddress == null) {
log.error("Address not found for update: addressId={}", addressVO.getId());
return false;
}
// 更新地址
JDWAAddress address = new JDWAAddress();
BeanUtils.copyProperties(addressVO, address);
address.setUserId(userId);
address.setUpdateTime(LocalDateTime.now());
// 如果设为默认,先取消其他默认地址
if (addressVO.getIsDefault() != null && addressVO.getIsDefault() == 1
&& existingAddress.getIsDefault() != 1) {
addressMapper.update(null,
Wrappers.<JDWAAddress>lambdaUpdate()
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDefault, 1)
.set(JDWAAddress::getIsDefault, 0));
}
addressMapper.updateById(address);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteAddress(Long addressId) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询地址是否存在
JDWAAddress existingAddress = addressMapper.selectOne(
Wrappers.<JDWAAddress>lambdaQuery()
.eq(JDWAAddress::getId, addressId)
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDeleted, 0));
if (existingAddress == null) {
log.error("Address not found for deletion: addressId={}", addressId);
return false;
}
// 逻辑删除地址
existingAddress.setIsDeleted(1);
existingAddress.setUpdateTime(LocalDateTime.now());
addressMapper.updateById(existingAddress);
// 如果删除的是默认地址,设置最新的地址为默认
if (existingAddress.getIsDefault() == 1) {
JDWAAddress newestAddress = addressMapper.selectOne(
Wrappers.<JDWAAddress>lambdaQuery()
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDeleted, 0)
.orderByDesc(JDWAAddress::getUpdateTime)
.last("LIMIT 1"));
if (newestAddress != null) {
newestAddress.setIsDefault(1);
newestAddress.setUpdateTime(LocalDateTime.now());
addressMapper.updateById(newestAddress);
}
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean setDefaultAddress(Long addressId) {
// 获取当前登录用户
Long userId = JDWASecurityUtil.getCurrentUserId();
// 查询地址是否存在
JDWAAddress existingAddress = addressMapper.selectOne(
Wrappers.<JDWAAddress>lambdaQuery()
.eq(JDWAAddress::getId, addressId)
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDeleted, 0));
if (existingAddress == null) {
log.error("Address not found for setting default: addressId={}", addressId);
return false;
}
// 如果已经是默认地址,不需要操作
if (existingAddress.getIsDefault() == 1) {
return true;
}
// 取消其他默认地址
addressMapper.update(null,
Wrappers.<JDWAAddress>lambdaUpdate()
.eq(JDWAAddress::getUserId, userId)
.eq(JDWAAddress::getIsDefault, 1)
.set(JDWAAddress::getIsDefault, 0));
// 设置为默认地址
existingAddress.setIsDefault(1);
existingAddress.setUpdateTime(LocalDateTime.now());
addressMapper.updateById(existingAddress);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deductUserPoints(Long userId, Integer points, String orderNo, String description) {
// 调用积分服务扣减用户积分
return userPointsService.deductPoints(userId, points, "EXCHANGE", orderNo, description);
}
}
性能优化
高并发处理
奖品兑换模块在活动期间可能面临高并发请求,尤其是限量奖品的发放过程。为了应对这一挑战,我们采取了以下优化措施:
库存锁机制
为防止超卖问题,实现了基于Redis的分布式锁来保证库存操作的原子性:
// Redis分布式锁示例
String lockKey = String.format("prize:stock:lock:%d", prizeId);
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(locked)) {
log.warn("Failed to acquire Redis lock for stock: prizeId={}", prizeId);
return false;
}
try {
// 库存操作...
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
异步处理
通过消息队列对非核心流程进行异步处理,减轻系统负担:
// 异步更新奖品兑换数量
@Async
public void asyncUpdateExchangeCount(Long prizeId, Integer quantity) {
try {
prizeMapper.increaseExchangeCount(prizeId, quantity);
} catch (Exception e) {
log.error("Failed to update exchange count: prizeId={}, quantity={}, error={}",
prizeId, quantity, e.getMessage(), e);
}
}
多级缓存
采用多级缓存策略提高热点数据读取效率:
- 本地缓存:使用Caffeine缓存热门奖品数据
- 分布式缓存:使用Redis缓存奖品列表和分类信息
- 缓存预热:系统启动和大促前预热热门奖品缓存
数据库优化
表结构优化
- 合理设计索引,针对高频查询字段创建索引
- 使用冷热数据分离策略,将历史订单数据定期归档
- 采用分库分表策略,按用户ID对订单表进行水平分表
查询优化
- 使用延迟加载技术,按需加载订单详情和物流信息
- 实现分页查询,限制大数据量返回
- 优化统计查询,使用预聚合数据提高统计效率
常见问题与解决方案
库存管理问题
超卖问题
问题描述:在高并发情况下,多个用户同时兑换可能导致库存实际扣减超过可用库存。
解决方案:
- 使用Redis分布式锁保证库存操作原子性
- 实现悲观锁或乐观锁机制进行并发控制
- 数据库层面使用FOR UPDATE锁定库存记录
库存不足处理
问题描述:用户下单后发现实际库存不足。
解决方案:
- 建立库存预警机制,当库存低于阈值时自动提醒补货
- 实现库存检查与下单的事务处理,保证数据一致性
- 对于热门商品,设置排队机制,按顺序分配库存
订单处理问题
订单超时未支付
问题描述:用户创建订单后长时间未支付(适用于组合支付方式)。
解决方案:
- 使用延迟队列自动取消超时订单
- 释放锁定的库存,返还扣减的积分
- 定时任务扫描未支付订单,批量处理
// 订单超时处理示例
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void handleExpiredOrders() {
List<JDWAExchangeOrder> expiredOrders = orderMapper.selectExpiredOrders(
JDWAPrizeConstants.ORDER_STATUS_PENDING, LocalDateTime.now().minusMinutes(30));
for (JDWAExchangeOrder order : expiredOrders) {
try {
cancelOrder(order.getOrderNo(), "订单超时自动取消");
} catch (Exception e) {
log.error("Failed to cancel expired order: orderNo={}, error={}",
order.getOrderNo(), e.getMessage(), e);
}
}
}
物流跟踪问题
问题描述:无法实时获取物流最新状态。
解决方案:
- 与主流物流公司建立API对接,定时同步物流状态
- 使用第三方物流聚合服务,统一管理多家物流信息
- 实现物流状态变更主动推送机制
兑换资格验证问题
问题描述:复杂的兑换资格规则导致验证逻辑复杂,影响系统性能。
解决方案:
- 引入规则引擎,将资格验证规则与业务代码解耦
- 对验证结果进行缓存,避免频繁重复验证
- 使用批量验证方式,减少数据库查询次数
未来优化规划
短期规划(1-3个月)
个性化推荐系统
- 基于用户历史兑换记录和行为数据,实现个性化奖品推荐
- 引入协同过滤算法,提高推荐精准度
社交分享功能
- 允许用户将兑换的奖品分享到社交平台
- 实现分享邀请好友兑换的奖励机制
积分兑换活动优化
- 实现限时兑换活动自动化管理
- 开发活动预热和预约功能,缓解大促期间系统压力
中期规划(3-6个月)
多渠道支付集成
- 集成更多支付方式,支持积分+现金的组合支付
- 实现支付渠道的动态路由和负载均衡
智能库存管理
- 基于历史数据和AI算法,实现库存智能预测
- 开发自动补货和库存调配系统
虚拟奖品生态扩展
- 扩展虚拟奖品类型,如电子书、在线课程等
- 建立第三方虚拟商品对接平台
长期规划(6-12个月)
区块链积分兑换
- 研究区块链技术在积分兑换中的应用
- 提高积分交易的透明度和安全性
国际化支持
- 支持多语言、多币种的奖品兑换
- 适配国际物流和海外仓储系统
生态合作伙伴计划
- 建立开放的API接口,允许合作伙伴接入奖品兑换系统
- 发展线下实体店兑换网络
总结
JDWA Green-U奖品兑换模块作为碳减排激励体系的重要组成部分,通过将用户碳减排活动获得的积分兑换为实物或虚拟奖励,实现了"绿色行为-积分奖励-奖品兑换"的完整闭环,有效提高了用户参与环保活动的积极性和持续性。
模块设计以用户体验为中心,兼顾系统性能和业务灵活性,成功应对了高并发兑换场景的挑战。通过与积分系统、成就系统等多模块的无缝集成,形成了完整的用户激励体系,为平台的用户增长和活跃度提升提供了有力支撑。
未来,模块将继续优化用户体验,扩展奖品类型,增强个性化推荐能力,并探索更多创新的兑换模式,为用户提供更丰富、更便捷的绿色生活方式转化路径,助力实现JDWA Green-U平台的环保价值主张和业务目标。
API接口规范
奖品管理接口
获取奖品列表
接口URL:
/api/prizes/list
请求方式: GET
请求参数:
参数名 类型 必须 描述 categoryId Long 否 分类ID prizeType String 否 奖品类型:PHYSICAL/VIRTUAL/COUPON/PRIVILEGE/DONATION prizeLevel String 否 奖品等级:COMMON/PREMIUM/RARE/EXCLUSIVE keyword String 否 搜索关键词 pageNum Integer 否 页码,默认1 pageSize Integer 否 每页大小,默认10 响应结果:
{ "code": 200, "message": "操作成功", "data": { "records": [ { "id": 1, "prizeCode": "P20251001001", "prizeName": "环保购物袋", "prizeType": "PHYSICAL", "prizeLevel": "COMMON", "categoryId": 1, "pointsPrice": 100, "marketPrice": 15.00, "cashPrice": null, "thumbnail": "http://example.com/images/bag.jpg", "description": "环保材质购物袋,减少塑料使用", "availableStock": 100, "hasEnoughPoints": true } ], "total": 10, "pageNum": 1, "pageSize": 10 } }
获取奖品详情
接口URL:
/api/prizes/detail/{prizeCode}
请求方式: GET
请求参数:
参数名 类型 必须 描述 prizeCode String 是 奖品编码 响应结果:
{ "code": 200, "message": "操作成功", "data": { "id": 1, "prizeCode": "P20251001001", "prizeName": "环保购物袋", "prizeType": "PHYSICAL", "prizeLevel": "COMMON", "categoryId": 1, "categoryName": "环保用品", "pointsPrice": 100, "marketPrice": 15.00, "cashPrice": null, "thumbnail": "http://example.com/images/bag.jpg", "images": ["http://example.com/images/bag1.jpg", "http://example.com/images/bag2.jpg"], "description": "环保材质购物袋,减少塑料使用", "specification": "尺寸: 40x30cm,材质: 可降解环保材料", "exchangeLimit": 5, "exchangeStart": "2025-10-01 00:00:00", "exchangeEnd": "2025-12-31 23:59:59", "weight": 0.2, "deliveryFee": 0, "availableStock": 100, "totalStock": 150, "userCurrentPoints": 300, "hasEnoughPoints": true, "canExchange": true, "rules": [ { "id": 1, "ruleCode": "LIMIT_001", "ruleName": "每人限兑5个", "ruleType": "LIMIT", "ruleCondition": "{\"limit\":5}", "ruleValue": "5", "description": "每个用户最多可兑换5个" } ] } }
购物车接口
添加到购物车
接口URL:
/api/prizes/cart/add
请求方式: POST
请求参数:
{ "prizeId": 1, "quantity": 2 }
响应结果:
{ "code": 200, "message": "操作成功", "data": null }
购物车列表
接口URL:
/api/prizes/cart/list
请求方式: GET
响应结果:
{ "code": 200, "message": "操作成功", "data": [ { "id": 1, "prizeId": 1, "prizeCode": "P20251001001", "prizeName": "环保购物袋", "prizeType": "PHYSICAL", "prizeImage": "http://example.com/images/bag.jpg", "pointsPrice": 100, "cashPrice": null, "quantity": 2, "totalPoints": 200, "totalCash": 0, "prizeStatus": 1, "canExchange": true, "availableStock": 100, "stockEnough": true, "selectedStatus": 1 } ] }
订单接口
创建兑换订单
接口URL:
/api/prizes/order/create
请求方式: POST
请求参数:
{ "items": [ { "prizeId": 1, "quantity": 2 } ], "addressId": 10, "deliveryMethod": "EXPRESS", "cartItemIds": [1], "remark": "请包装好,谢谢" }
响应结果:
{ "code": 200, "message": "操作成功", "data": "JD2025101012345678901234" }
获取订单列表
接口URL:
/api/prizes/order/list
请求方式: GET
请求参数:
参数名 类型 必须 描述 status String 否 订单状态 pageNum Integer 否 页码,默认1 pageSize Integer 否 每页大小,默认10 响应结果:
{ "code": 200, "message": "操作成功", "data": { "records": [ { "id": 1, "orderNo": "JD2025101012345678901234", "orderType": "PHYSICAL", "totalPoints": 200, "totalCash": 0, "totalAmount": 2, "status": "PENDING", "createTime": "2025-10-10 12:34:56", "firstPrizeImage": "http://example.com/images/bag.jpg", "items": [ { "id": 1, "orderId": 1, "orderNo": "JD2025101012345678901234", "prizeId": 1, "prizeName": "环保购物袋", "prizeType": "PHYSICAL", "prizeImage": "http://example.com/images/bag.jpg", "pointsPrice": 100, "quantity": 2, "totalPoints": 200, "status": "PENDING" } ] } ], "total": 10, "pageNum": 1, "pageSize": 10 } }
获取订单详情
接口URL:
/api/prizes/order/detail/{orderNo}
请求方式: GET
请求参数:
参数名 类型 必须 描述 orderNo String 是 订单编号 响应结果:
{ "code": 200, "message": "操作成功", "data": { "id": 1, "orderNo": "JD2025101012345678901234", "orderType": "PHYSICAL", "totalPoints": 200, "totalCash": 0, "totalAmount": 2, "status": "PENDING", "paymentStatus": 1, "paymentTime": "2025-10-10 12:35:12", "deliveryMethod": "EXPRESS", "deliveryFee": 0, "consignee": "张三", "consigneePhone": "13800138000", "consigneeAddress": "北京市海淀区中关村南大街5号", "logisticsCompany": "顺丰快递", "logisticsNo": "SF1234567890", "createTime": "2025-10-10 12:34:56", "remark": "请包装好,谢谢", "items": [ { "id": 1, "orderId": 1, "orderNo": "JD2025101012345678901234", "prizeId": 1, "prizeName": "环保购物袋", "prizeType": "PHYSICAL", "prizeImage": "http://example.com/images/bag.jpg", "pointsPrice": 100, "quantity": 2, "totalPoints": 200, "status": "PENDING" } ], "logistics": { "logisticsCompany": "顺丰快递", "logisticsNo": "SF1234567890", "status": "SHIPPED", "trackingList": [ { "time": "2025-10-11 09:30:00", "status": "已揽收", "description": "快递员已揽收" }, { "time": "2025-10-11 12:45:00", "status": "运输中", "description": "货物已到达分拣中心" } ] } } }
系统集成
奖品兑换模块与其他系统模块紧密集成,主要包括以下方面:
用户积分系统
- 积分验证:兑换前验证用户积分是否足够
- 积分扣减:兑换成功后,扣减用户相应积分
- 积分返还:订单取消时,返还已扣减的积分
成就系统
- 成就条件验证:部分特殊奖品需要用户达成特定成就才能兑换
- 兑换触发成就:特定奖品兑换可能触发特定成就解锁
活动模块
- 活动奖品发放:活动模块可通过奖品兑换模块向用户发放活动奖励
- 活动资格验证:部分奖品需要用户参与特定活动才能兑换
库存管理系统
- 实时库存监控:监控奖品库存,及时发现库存不足情况
- 库存预警:当库存低于预警阈值时,自动通知管理员补充库存
- 库存锁定机制:在下单过程中锁定库存,防止超卖
物流配送系统
- 物流订单创建:实物奖品兑换成功后,自动创建物流订单
- 物流信息查询:提供物流信息实时查询功能
- 配送通知:物流状态更新时,向用户推送配送通知
用户通知系统
- 兑换成功通知:奖品兑换成功后,向用户发送通知
- 订单状态变更通知:订单状态变更时,实时通知用户
- 虚拟奖品激活通知:虚拟奖品激活时,发送激活信息
业务流程示例
以下是一个完整的奖品兑换流程示例:
实物奖品兑换流程
- 用户浏览奖品:用户在平台浏览可兑换的奖品列表
- 选择奖品加入购物车:用户选择心仪的奖品加入购物车
- 提交兑换订单:
- 用户确认购物车中的奖品并提交兑换订单
- 系统验证用户积分是否足够
- 系统验证奖品库存是否充足
- 系统验证用户是否满足兑换资格
- 订单创建与积分扣减:
- 创建兑换订单记录
- 锁定奖品库存
- 扣减用户积分
- 后台审核与发货:
- 管理员审核订单
- 准备奖品并安排发货
- 录入物流信息
- 用户收货确认:
- 用户收到奖品后确认收货
- 系统完成订单流程,更新订单状态为已完成
虚拟奖品兑换流程
- 用户选择虚拟奖品:用户选择虚拟奖品(如优惠券、会员特权等)
- 提交兑换请求:用户提交虚拟奖品兑换请求
- 系统自动处理:
- 验证用户积分和兑换资格
- 扣减用户积分
- 自动生成虚拟奖品兑换码或权益
- 用户激活使用:
- 用户接收到虚拟奖品信息
- 根据指引激活或使用虚拟奖品
兑换规则引擎
奖品兑换模块包含一个灵活的规则引擎,用于管理各种兑换限制和优惠规则:
规则类型
积分优惠规则
- 限时折扣:在特定时间段内,减少兑换所需积分
- 会员折扣:根据用户等级提供积分优惠
- 套装优惠:购买多件同类奖品时提供积分折扣
兑换限制规则
- 每人限量:限制每位用户的最大兑换数量
- 每日限量:限制每日平台兑换总量
- 新用户限制:对新注册用户的兑换权限进行限制
资格限制规则
- 等级要求:需要达到特定用户等级才能兑换
- 成就要求:需要解锁特定成就才能兑换
- 活动要求:需要参与特定活动才能兑换
规则执行机制
规则引擎采用优先级排序的方式执行规则:
- 根据规则优先级对适用规则进行排序
- 按优先级从高到低依次应用规则
- 对于互斥规则,只应用优先级最高的规则
- 兑换资格规则必须全部满足才能通过验证