前言
模拟 2 万元的红包,共分为2千个可抢的小红包,有2千人同时抢夺的场景 ,模拟出现超发和如何保证数据一致性的问题。
项目结构

数据库表
/*==============================================================*/
/* Table: 红包表 */
/*==============================================================*/
create table T_RED_PACKET
(
id int(12) not null auto_increment COMMENT '红包编号',
user_id int(12) not null COMMENT '发红包的用户id',
amount decimal(16,2) not null COMMENT '红包金额',
send_date timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '发红包日期',
total int(12) not null COMMENT '红包总数',
unit_amount decimal(12) not null COMMENT '单个红包的金额',
stock int(12) not null COMMENT '红包剩余个数',
version int(12) default 0 not null COMMENT '版本(为后续扩展用)',
note varchar(256) null COMMENT '备注',,
primary key clustered (id)
);
/*==============================================================*/
/* Table: 用户抢红包表 */
/*==============================================================*/
create table T_USER_RED_PACKET
(
id int(12) not null auto_increment COMMENT '用户抢到的红包id',
red_packet_id int(12) not null COMMENT '红包id',
user_id int(12) not null COMMENT '抢红包用户的id',
amount decimal(16,2) not null COMMENT '抢到的红包金额',
grab_time timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '抢红包时间',
note varchar(256) null COMMENT '备注',
primary key clustered (id)
);
Controller
- RedPacketController
@RestController
@RequestMapping("redPacket")
public class RedPacketController {
@Resource
RedPacketService redPacketService;
@GetMapping("/getRedPacket/{id}")
public RedPacket getRedPacket(@PathVariable Long id){
return redPacketService.getRedPacket(id);
}
}
- UserRedPacketController
@RestController
@RequestMapping("userRedPacket")
public class UserRedPacketController {
@Resource
UserRedPacketService userRedPacketService;
@GetMapping("/grapRedPacket")
public FrontResult grapRedPacket(Long redPacketId, Long userId){
//抢红包
int result = userRedPacketService.grapRedPacket(redPacketId, userId);
if (result > 0){
return FrontResult.getSuccessResultOnlyMessage("抢红包成功");
}
return FrontResult.build("1111","抢红包失败!",null);
}
}
Dao
- RedPacketDao
@Mapper
public interface RedPacketDao {
/**
* 获取红包信息.
* @param id --红包id
* @return 红包具体信息
*/
RedPacket getRedPacket(Long id);
/**
* 扣减抢红包数.
* @param id -- 红包id
* @return 更新记录条数
*/
int decreaseRedPacket(Long id);
}
- UserRedPacketDao
@Mapper
public interface UserRedPacketDao {
/**
* 插入抢红包信息.
* @param userRedPacket ——抢红包信息
* @return 影响记录数
*/
int grapRedPacket(UserRedPacket userRedPacket);
}
pojo
- FrontResult
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FrontResult implements Serializable {
/**
* 结果状态码
*/
private String code;
/**
* 响应结果描述
*/
private String message;
/**
* 返回数据
*/
private Object data;
/**
* 静态方法,返回前端实体结果
*
* @param code 状态码
* @param message 消息
* @param data 数据
* @return 前端实体结果
* @author liuBing
*/
public static com.xiaogu.pojo.FrontResult build(String code, String message, Object data) {
return new com.xiaogu.pojo.FrontResult(code, message, data);
}
/**
* 返回成功的结果实体
*
* @param message 消息
* @param data 数据
* @return 实体
* @author: liuBing
* @DATE: 2020/5/25
*/
public static com.xiaogu.pojo.FrontResult getSuccessResult(String message, Object data) {
com.xiaogu.pojo.FrontResult result = new com.xiaogu.pojo.FrontResult();
result.code = ResultCodeEnum.SUCCESS.code;
result.message = message;
result.data = data;
return result;
}
/**
* 返回无需data的成功结果实体
*
* @param message 消息内容
* @return 返回结果
* @author liuBing
*/
public static com.xiaogu.pojo.FrontResult getSuccessResultOnlyMessage(String message) {
com.xiaogu.pojo.FrontResult result = new com.xiaogu.pojo.FrontResult();
result.code = ResultCodeEnum.SUCCESS.code;
result.message = message;
result.data = "1";
return result;
}
/**
* 获取一个异常结果
*
* @param code 错误码
* @param message 自定义异常信息
* @return FrontResult
* @author 刘兵
*/
public static com.xiaogu.pojo.FrontResult getExceptionResult(String code, String message) {
com.xiaogu.pojo.FrontResult result = new com.xiaogu.pojo.FrontResult();
result.code = code.isEmpty() ? ResultCodeEnum.CODE_EXCEPTION.code : code;
result.message = message.isEmpty() ? ResultCodeEnum.CODE_EXCEPTION.getMsg() : message;
return result;
}
}
- RedPacket
@Data
public class RedPacket implements Serializable {
private static final long serialVersionUID = 9036484563091364939L;
// 红包编号
private Long id;
// 发红包的用户id
private Long userId;
// 红包金额
private Double amount;
// 发红包日期
private Timestamp sendDate;
// 红包总数
private Integer total;
// 单个红包的金额
private Double unitAmount;
// 红包剩余个数
private Integer stock;
// 版本(为后续扩展用)
private Integer version;
// 备注
private String note;
}
- UserRedPacket
@Data
@Accessors(chain = true)
public class UserRedPacket implements Serializable {
private static final long serialVersionUID = 7049215937937620886L;
// 用户红包id
private Long id;
// 红包id
private Long redPacketId;
// 抢红包的用户的id
private Long userId;
// 抢红包金额
private Double amount;
// 抢红包时间
private Timestamp grabTime;
// 备注
private String note;
}
Service
- RedPacketService
@Service
public class RedPacketService {
@Resource
RedPacketDao redPacketDao;
/**
* 获取红包
* @param id 红包id
* @return 红包信息
*/
public RedPacket getRedPacket(Long id) {
return redPacketDao.getRedPacket(id);
}
/**
* 扣减红包
* @param id——编号
* @return 影响条数.
*/
public int decreaseRedPacket(Long id){
return redPacketDao.decreaseRedPacket(id);
}
}
- UserRedPacketService
@Service
public class UserRedPacketService {
@Resource
UserRedPacketDao userRedPacketDao;
@Resource
RedPacketDao redPacketDao;
public int grapRedPacket(Long redPacketId, Long userId) {
//获取红包信息
RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);
//获取红包剩余库存
int leftRedPacket = redPacket.getStock();
if (leftRedPacket <= 0) {
return 0;
}
//抢到红包,更新红包库存
redPacketDao.decreaseRedPacket(redPacketId);
System.out.println("剩余Stock数量:" + leftRedPacket);
// 生成抢红包信息
UserRedPacket userRedPacket = new UserRedPacket();
userRedPacket.setRedPacketId(redPacketId)
.setUserId(userId)
.setAmount(redPacket.getUnitAmount())
.setNote("redpacket- " + redPacketId);
return userRedPacketDao.grapRedPacket(userRedPacket);
}
}
mapper
- RedPacketMapper.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xiaogu.dao.RedPacketDao">
<!-- 查询红包具体信息 -->
<select id="getRedPacket" parameterType="long"
resultType="com.xiaogu.pojo.RedPacket">
select id, user_id as userId, amount, send_date as
sendDate, total,
unit_amount as unitAmount, stock, version, note from
T_RED_PACKET
where id = #{id}
</select>
<!-- 扣减抢红包库存 -->
<update id="decreaseRedPacket">
update T_RED_PACKET set stock = stock - 1 where id =
#{id}
</update>
</mapper>
- UserRedPacketMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xiaogu.dao.UserRedPacketDao">
<!-- 插入抢红包信息 -->
<insert id="grapRedPacket" useGeneratedKeys="true"
keyProperty="id" parameterType="com.xiaogu.pojo.UserRedPacket">
insert into
T_USER_RED_PACKET
( red_packet_id, user_id, amount, grab_time, note)
values
(#{redPacketId}, #{userId}, #{amount}, now(), #{note})
</insert>
</mapper>

本文详细介绍了红包系统的数据库设计,包括红包表和用户抢红包表的结构,以及对应的Controller、Service、Dao层代码实现,展示了如何通过扣减库存和插入抢红包记录来保证红包发放的正确性和数据一致性。
2万+

被折叠的 条评论
为什么被折叠?



