springboot+websocket实现并发抢红包功能,2024前端岗面试题知识点小结

public void dealAfterOrderPayCallback(String userId,ReqOrderPayCallbackVO data) {

RedPacketExample example = new RedPacketExample();

final String packetNo = data.getPacketNo();

final String paySeq = data.getPaySeq();

final Integer payStatus = data.getPayStatus();

example.createCriteria().andPacketNoEqualTo(packetNo)

.andPaySeqEqualTo(paySeq)

.andOrderStatusEqualTo(1);//待支付状态

//更新订单支付状态

Date now = new Date();

RedPacket updateRedPacket = new RedPacket();

updateRedPacket.setOrderStatus(payStatus);

updateRedPacket.setUpdateTime(now);

updateRedPacket.setPayTime(now);

int i = redPacketMapper.updateByExampleSelective(updateRedPacket, example);

if (i != 1) {

throw new ServiceException(“订单状态更新失败”, ExceptionType.SYS_ERR);

}

if (payStatus == 2) {

RedPacketExample query = new RedPacketExample();

query.createCriteria().andPacketNoEqualTo(packetNo)

.andPaySeqEqualTo(paySeq)

.andOrderStatusEqualTo(2);

final RedPacket redPacket = redPacketMapper.selectByExample(query).get(0);

final List detailList = getRedPacketDetail(redPacket.getAmount(), redPacket.getNum());

final int size = detailList.size();

if (size <= 100) {

i = detailMapper.batchInsert(detailList, redPacket);

if (size != i) {

throw new ServiceException(“生成红包失败”, ExceptionType.SYS_ERR);

}

} else {

int times = size % 100 == 0 ? size / 100 : (size / 100 + 1);

for (int j = 0; j < times; j++) {

int fromIndex = 100 * j;

int toIndex = 100 * (j + 1) - 1;

if (toIndex > size - 1) {

toIndex = size - 1;

}

final List subList = detailList.subList(fromIndex, toIndex);

i = detailMapper.batchInsert(subList, redPacket);

if (subList.size() != i) {

throw new ServiceException(“生成红包失败”, ExceptionType.SYS_ERR);

}

}

}

final String redisKey = REDPACKET_NUM_PREFIX + redPacket.getPacketNo();

String lua = “local i = redis.call(‘setnx’,KEYS[1],ARGV[1])\r\n” +

“if i == 1 then \r\n” +

" local j = redis.call(‘expire’,KEYS[1],ARGV[2])\r\n" +

“end \r\n” +

“return i”;

//优化成lua脚本

final Long execute = redisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Arrays.asList(redisKey), size, 3600 * 24);

if (execute != 1L) {

throw new ServiceException(“生成红包失败”, ExceptionType.SYS_ERR);

}

//websocket通知在线用户收到新的红包

Websocket.sendMessageToUser(userId, JSONObject.toJSONString(redPacket));

}

}

/**

  • 红包随机算法

  • @param amount 红包金额

  • @param num 红包数量

  • @return 随机红包集合

*/

private List getRedPacketDetail(BigDecimal amount, Integer num) {

List redPacketsList = new ArrayList<>(num);

//最小红包金额

final BigDecimal min = new BigDecimal(“0.01”);

//最少需要红包金额

final BigDecimal bigNum = new BigDecimal(num);

final BigDecimal atLastAmount = min.multiply(bigNum);

//出去最少红包金额后剩余金额

BigDecimal remain = amount.subtract(atLastAmount);

if (remain.compareTo(BigDecimal.ZERO) == 0) {

for (int i = 0; i < num; i++) {

redPacketsList.add(min);

}

return redPacketsList;

}

final Random random = new Random();

final BigDecimal hundred = new BigDecimal(“100”);

final BigDecimal two = new BigDecimal(“2”);

BigDecimal redPacket;

for (int i = 0; i < num; i++) {

if (i == num - 1) {

redPacket = remain;

} else {

//100内随机获得的整数

final int rand = random.nextInt(100);

redPacket = new BigDecimal(rand).multiply(remain.multiply(two).divide(bigNum.subtract(new BigDecimal(i)), 2, RoundingMode.CEILING)).divide(hundred, 2, RoundingMode.FLOOR);

}

if (remain.compareTo(redPacket) > 0) {

remain = remain.subtract(redPacket);

} else {

remain = BigDecimal.ZERO;

}

redPacketsList.add(min.add(redPacket));

}

return redPacketsList;

}

复制代码

页面加载成功后初始化websocket,监听后端新红包生成成功,动态添加红包到聊天窗口。

$(function (){

var websocket;

if(‘WebSocket’ in window) {

console.log(“此浏览器支持websocket”);

websocket = new WebSocket(“ws://127.0.0.1:8082/websocket/${session.id}”);

} else if(‘MozWebSocket’ in window) {

alert(“此浏览器只支持MozWebSocket”);

} else {

alert(“此浏览器只支持SockJS”);

}

websocket.onopen = function(evnt) {

console.log(“链接服务器成功!”)

};

websocket.onmessage = function(evnt) {

console.log(evnt.data);

var json = eval(‘(’+evnt.data+ ‘)’);

obj.addPacket(json.id,json.packetNo,json.userId)

};

websocket.onerror = function(evnt) {};

websocket.onclose = function(evnt) {

console.log(“与服务器断开了链接!”)

}

});

复制代码

抢红包设计

抢红包设计高并发,本地单机项目,通过原子Integer控制抢红包接口并发限制为20,

private AtomicInteger receiveCount = new AtomicInteger(0);

@PostMapping(“/receive”)

public CommonJsonResponse receiveOne(@Validated @RequestBody CommonJsonRequest vo) {

Integer num = null;

try {

//控制并发不要超过20

if (receiveCount.get() > 20) {

return new CommonJsonResponse(“9999”, “太快了”);

}

num = receiveCount.incrementAndGet();

final String s = orderService.receiveOne(vo.getData());

return StringUtils.isEmpty(s) ? CommonJsonResponse.ok() : new CommonJsonResponse(“9999”, s);

} finally {

if (num != null) {

receiveCount.decrementAndGet();

}

}

}

复制代码

对于没有领取过该红包的用户,在红包没有过期且红包还有剩余的情况下,抢红包成功,记录成功标识入redis,设置标识过期时间为5秒。

public String receiveOne(ReqReceiveRedPacketVO data) {

final Long redPacketId = data.getPacketId();

final String redPacketNo = data.getPacketNo();

final String redisKey = REDPACKET_NUM_PREFIX + redPacketNo;

if (!redisTemplate.hasKey(redisKey)) {

return “红包已经过期”;

}

final Integer num = (Integer) redisTemplate.opsForValue().get(redisKey);

if (num <= 0) {

return “红包已抢完”;

}

RedPacketDetailExample example = new RedPacketDetailExample();

example.createCriteria().andPacketIdEqualTo(redPacketId)

.andReceivedEqualTo(1)

.andUserIdEqualTo(data.getUserId());

final List details = detailMapper.selectByExample(example);

if (!details.isEmpty()) {

return “该红包已经领取过了”;

}

final String receiveKey = REDPACKET_RECEIVE_PREFIX + redPacketNo + “:” + data.getUserId();

//优化成lua脚本

String lua = “local i = redis.call(‘setnx’,KEYS[1],ARGV[1])\r\n” +

“if i == 1 then \r\n” +

" local j = redis.call(‘expire’,KEYS[1],ARGV[2])\r\n" +

“end \r\n” +

“return i”;

//优化成lua脚本

final Long execute = redisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Arrays.asList(receiveKey), 1, 5);

if (execute != 1L) {

return “太快了”;

}

return “”;

}

复制代码

拆红包设计

在用户抢红包成功标识未过期的状态下,且红包未过期红包未领完时,从数据库中领取一个红包,领取成功将领取记录写入redis以供查询过期时间为48小时。

@Transactional(rollbackFor = Exception.class)

public String openRedPacket(ReqReceiveRedPacketVO data) {

final Long packetId = data.getPacketId();

final String packetNo = data.getPacketNo();

final String userId = data.getUserId();

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

最后更多分享:前端字节跳动真题解析

g-42x1FzsO-1711787702681)]
[外链图片转存中…(img-5Iw4eLuJ-1711787702682)]
[外链图片转存中…(img-hX4NOttx-1711787702682)]
[外链图片转存中…(img-lZqisqGV-1711787702683)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-AlB4D6w9-1711787702684)]

最后更多分享:前端字节跳动真题解析
  • [外链图片转存中…(img-MrD2hQzN-1711787702684)]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值