抽奖系统-奖品-活动

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 获取奖品列表
  • 前端页面
  • 活动创建需求分析
  • 活动创建后端实现1-控制层实现及校验活动
  • 活动创建后端实现2-保存信息
    • 活动插入
    • 活动奖品插入
  • 整合活动信息
  • 存入redis
  • 测试
  • 活动创建前端实现
  • 查询活动列表后端
  • 活动列表页前端实现
  • 抽奖需求分析
  • 查询活动详细信息后端实现
  • 抽奖接口设计
  • MQ配置
  • 总结


前言

获取奖品列表

在这里插入图片描述

@Data
public class PageParam implements Serializable {private Integer currentPage=1;private Integer pageSize=10;public Integer offset(){return (currentPage-1)*pageSize;}
}
@Data
public class FindPrizeListResult {private Integer  total;private List<PrizeInfo> records;@Datapublic static class PrizeInfo{private Long PrizeId;private String prizeName;private BigDecimal price;private String description;private String imageUrl;}
}
@Data
public class PageListDTO<T> {private Integer total;private List<T> records;public PageListDTO(Integer total, List<T> records) {this.total = total;this.records = records;}
}
@Data
public class PrizeDTO {private Long id;private String name;public String description;private BigDecimal price;private String imageUrl;
}

controller

    @RequestMapping("/prize/find-list")public CommonResult<FindPrizeListResult> findPrizeList(PageParam param){log.info("查询奖品列表开始,param:{}", JacksonUtil.writeValueAsString(param));PageListDTO<PrizeDTO> prizeListDTO = prizeService.findPrizeList(param);return CommonResult.success(convertToFindPrizeListResult(prizeListDTO));}private FindPrizeListResult convertToFindPrizeListResult(PageListDTO<PrizeDTO> pageListDTO) {if(null==pageListDTO){throw new ControllerException(ControllerErrorCodeConstants.FIND_PRIZE_LIST_ERROR);}FindPrizeListResult findPrizeListResult = new FindPrizeListResult();findPrizeListResult.setTotal(pageListDTO.getTotal());findPrizeListResult.setRecords(pageListDTO.getRecords().stream().map(prizeDTO -> { FindPrizeListResult.PrizeInfo prizeInfo = new FindPrizeListResult.PrizeInfo();prizeInfo.setPrizeId(prizeDTO.getId());prizeInfo.setPrizeName(prizeDTO.getName());prizeInfo.setDescription(prizeDTO.getDescription());prizeInfo.setPrice(prizeDTO.getPrice());prizeInfo.setImageUrl(prizeDTO.getImageUrl());return prizeInfo;}).collect(Collectors.toList()));return findPrizeListResult;}

mapper

    @Select("select count(*) from prize")int count();@Select("select * from prize order by id desc limit #{offset},#{pageSize}")List<PrizeDO> selectPrizeList(@Param("offset") Integer offset,@Param("pageSize") Integer pageSize);

service

    @Overridepublic PageListDTO<PrizeDTO> findPrizeList(PageParam param) {int total = prizeMapper.count();List<PrizeDTO> prizeDTOList =new ArrayList<>();List<PrizeDO> prizeDOList = prizeMapper.findPrizeList(param.offset(),param.getPageSize());for (PrizeDO prizeDO : prizeDOList) {PrizeDTO prizeDTO = new PrizeDTO();prizeDTO.setId(prizeDO.getId());prizeDTO.setName(prizeDO.getName());prizeDTO.setDescription(prizeDO.getDescription());prizeDTO.setPrice(prizeDO.getPrice());prizeDTO.setImageUrl(prizeDO.getImageUrl());prizeDTOList.add(prizeDTO);}return new PageListDTO<>(total,prizeDTOList);}

controller

    @RequestMapping("/prize/find-list")public CommonResult<FindPrizeListResult> findPrizeList(PageParam param){log.info("查询奖品列表开始,param:{}", JacksonUtil.writeValueAsString(param));PageListDTO<PrizeDTO> prizeListDTO = prizeService.findPrizeList(param);return CommonResult.success(convertToFindPrizeListResult(prizeListDTO));}private FindPrizeListResult convertToFindPrizeListResult(PageListDTO<PrizeDTO> pageListDTO) {if(null==pageListDTO){throw new ControllerException(ControllerErrorCodeConstants.FIND_PRIZE_LIST_ERROR);}FindPrizeListResult findPrizeListResult = new FindPrizeListResult();findPrizeListResult.setTotal(pageListDTO.getTotal());findPrizeListResult.setRecords(pageListDTO.getRecords().stream().map(prizeDTO -> {FindPrizeListResult.PrizeInfo prizeInfo = new FindPrizeListResult.PrizeInfo();prizeInfo.setPrizeId(prizeDTO.getId());prizeInfo.setPrizeName(prizeDTO.getName());prizeInfo.setDescription(prizeDTO.getDescription());prizeInfo.setPrice(prizeDTO.getPrice());prizeInfo.setImageUrl(prizeDTO.getImageUrl());return prizeInfo;}).collect(Collectors.toList()));return findPrizeListResult;}

在这里插入图片描述

前端页面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

活动创建需求分析

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
人员只有普通用户

在这里插入图片描述

最后一个状态就是活动有没有正在进行的意思

设置奖品数量的时候,必须在创建活动的时候才有意义
所以还有奖品与活动关联表
在这里插入图片描述
最后一个状态是看这个奖品有没有抽完

对应还有活动人员关联表
在这里插入图片描述
因为user_name常用,所以放在关联表里面

status就是看这个人员有没有中奖

在这里插入图片描述
放在redis中是为了未来的抽奖

活动创建后端实现1-控制层实现及校验活动

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

controller
在这里插入图片描述

service:
在这里插入图片描述
检查service

在这里插入图片描述
检查的mapper

在这里插入图片描述
在这里插入图片描述

活动创建后端实现2-保存信息

在这里插入图片描述
安装一个插件
点击变量,alt+回车,那么就可以生成set和get方法了

在这里插入图片描述

活动插入

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

活动奖品插入

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

先检查奖品满足几等奖不

在这里插入图片描述
然后再插入两张表
在这里插入图片描述

整合活动信息

为将来抽奖活动准备

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

存入redis

缓存这里我们不抛异常,没有缓存成功就没有缓存成功吧,不影响整个项目的流程,就算缓存没有成功的话,也可以去数据库中查询数据,然后再缓存,所以我们在缓存中的异常捕获掉,这样就不会抛异常,就不会因为缓存失败而回滚了

然后还要写一个获取缓存信息的函数

在这里插入图片描述
然后就是在检查函数那里,
List existUserIds=userMapper.selectExistByIds(userIds);
这里的existUserIds容器一定要判断是不是为空,不然后面执行会报错
在这里插入图片描述
这样就可以了

测试

在这里插入图片描述

在这里插入图片描述
还有就是
在这里插入图片描述

这里的mapper语句写的有问题,原来写的是items.属性

然后还有一些代码要改一下
原:
在这里插入图片描述
现在:
在这里插入图片描述

原:
在这里插入图片描述

现在:
在这里插入图片描述

然后就成功了
在这里插入图片描述
但是redis还没有成功
原:
在这里插入图片描述
现在:
在这里插入图片描述
然后在测试
在这里插入图片描述
在这里插入图片描述

活动创建前端实现

在这里插入图片描述
这里pagesize设的足够大,那么就会显示完全了,因为这里的奖品页面展示没有设置分页

在这里插入图片描述
在这里插入图片描述

总共三个ajax请求
在这里插入图片描述

查询活动列表后端

在这里插入图片描述

[请求] /activity/find-list?currentPage=1&pageSize=10 GET
[响应] 
{"code": 200,"data": {"total": 10,"records": [{"activityId": 23,"activityName": "抽奖测试","description": "年会抽奖活动","valid": true},{"activityId": 22,"activityName": "抽奖测试","description": "年会抽奖活动","valid": true},{"activityId": 21,"activityName": "节⽇抽奖","description": "⽐特年会抽奖活动","valid": true}]},"msg": ""
}

在这里插入图片描述

在这里插入图片描述
controller
在这里插入图片描述

service:
在这里插入图片描述
mapper
在这里插入图片描述
然后测试一下

在这里插入图片描述

活动列表页前端实现

在这里插入图片描述
在这里插入图片描述

抽奖需求分析

在这里插入图片描述
在这里插入图片描述

流程是这样的,先展示奖品,然后闪动抽奖(前端干),然后谁中奖了,最后公布所有中奖名单
人员数量大于奖品数量
在这里插入图片描述

对应还有中奖通知

查询活动详细信息后端实现

在这里插入图片描述
在这里插入图片描述

[请求] /activity-detail/find?activityId=24 GET
[响应] 
{"code": 200,"data": {"activityId": 24,"activityName": "测试抽奖活动","description": "测试抽奖活动","valid": true,"prizes": [{"prizeId": 18,"name": "⼿机","description": "⼿机","price": 5000.00,"imageUrl": "e606c8db-218a-40c2-8946-0d9f8570626d.jpg","prizeAmount": 1,"prizeTierName": "⼀等奖","valid": true},{"prizeId": 19,"name": "吹⻛机","description": "吹⻛机","price": 200.00,"imageUrl": "63404e12-26f7-4974-9a99-41993586093c.jpg","prizeAmount": 1,"prizeTierName": "⼆等奖","valid": true}],"users": [{"userId": 44,"userName": "郭靖","valid": true},{"userId": 45,"userName": "杨康","valid": true}]},"msg": ""
}

在这里插入图片描述
controller:
在这里插入图片描述
service

在这里插入图片描述

mapper:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

每使用一个方法就换一行,那么这样的话,调试的时候就非常方便了

在这里插入图片描述

抽奖接口设计

在这里插入图片描述
在这里插入图片描述

这样在前端点击的时候就不会卡着半天等响应了,这个都是马上响应的,但是业务的处理是在其他时间处理的

因为马上响应的,所以结果必须得成功,大话已经说出去了
在这里插入图片描述
返回的就是成功
补救措施就是重新落库

  1. 抽奖请求处理(重要)
    • 随机抽取:前端随机选择后端提供的参与者,确保每次抽取的结果是公平的。
    • 请求提交:在活动进⾏时,管理员可发起抽奖请求。请求包含活动ID、奖品ID和中奖⼈员等附加
    信息。
    • 消息队列通知:有效的抽奖请求被发送⾄MQ队列中,等待MQ消费者真正处理抽奖逻辑。
    • 请求返回:抽奖的请求处理接⼝将不再完成任何的事情,直接返回。
  2. 抽奖结果公布
    • 前端展⽰:中奖名单通过前端随机抽取的⼈员,公布展⽰出来。
  3. 抽奖逻辑执⾏(重要)
    • 消息消费:MQ消费者收到异步消息,系统开始执⾏以下抽奖逻辑。
  4. 中奖结果处理(重要)
    • 请求验证:
    ◦ 系统验证抽奖请求的有效性,如是否满⾜系统根据设定的规则(如奖品数量、每⼈中奖次数
    限制等)等;
    ◦ 幂等性:若消息多发,已抽取的内容不能再次抽取
    • 状态扭转:根据中奖结果扭转活动/奖品/参与者状态,如奖品是否已被抽取, ⼈员是否已中奖等。
    • 结果记录:中奖结果被记录在数据库中,并同步更新 Redis 缓存。
  5. 中奖者通知
    • 通知中奖者:通知中奖者和其他相关系统(如邮件发送服务)。
    • 奖品领取:中奖者根据通知中的指引领取奖品。
  6. 抽奖异常处理
    • 回滚处理:当抽奖过程中发⽣异常,需要保证事务⼀致性。
    • 补救措施:抽奖⾏为是⼀次性的,因此异步处理抽奖任务必须保证成功,若过程异常,需采取补
    救措施

技术实现细节
• 异步处理:提⾼抽奖性能,不影响抽奖流程,将抽奖处理放⼊队列中进⾏异步处理,且保证了幂
等性。
• 活动状态扭转处理:状态扭转会涉及活动及奖品等多横向维度扭转,不能避免未来不会有其他内
容牵扯进活动中,因此对于状态扭转处理,需要⾼扩展性(设计模式)与维护性。
• 并发处理:中奖者通知,可能要通知多系统,但相互解耦,可以设计为并发处理,加快抽奖效率
作⽤。
• 事务处理:在抽奖逻辑执⾏时,如若发⽣异常,需要确保数据库表原⼦性、事务⼀致性,因此要
做好事务处理。
通过以上流程,抽奖系统能够确保抽奖过程的公平性和⾼效性,同时提供良好的⽤⼾体验。⽽且还整合了 Redis 和 MQ , 进⼀步提⾼系统的性能

异步处理用的就是RabbitMQ
就是前端把请求放入MQ队列中,然后就什么都不管了

MQ配置

先引入依赖
然后配置

## mq ##
spring.rabbitmq.host=124.71.229.73
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
#消息确认机制,默认auto
spring.rabbitmq.listener.simple.acknowledge-mode=auto
#设置失败重试 5次
spring.rabbitmq.listener.simple.retry.enabled=true
spring.rabbitmq.listener.simple.retry.max-attempts=5

spring.rabbitmq.listener.simple.acknowledge-mode=auto
的意思是消费者消费消息没有抛出异常,那么就是消费成功了,不然就是消费失败的

#设置失败重试 5次
的意思就是消费者消费消息失败的话,在重发五次消息

在这里插入图片描述
然后是配置队列,交换机

@Configuration
public class DirectRabbitConfig {public static final String QUEUE_NAME = "DirectQueue";public static final String EXCHANGE_NAME = "DirectExchange";public static final String ROUTING = "DirectRouting";/*** 队列 起名:DirectQueue** @return*/@Beanpublic Queue directQueue() {// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效// exclusive:默认也是false,只能被当前创建的连接使⽤,⽽且当连接关闭后队列即被删除。此参考优先级⾼于durable// autoDelete:是否⾃动删除,当没有⽣产者或者消费者使⽤此队列,该队列会⾃动删除。// return new Queue("DirectQueue",true,true,false);// ⼀般设置⼀下队列的持久化就好,其余两个就是默认falsereturn new Queue(QUEUE_NAME,true);}/*** Direct交换机 起名:DirectExchange** @return*/@BeanDirectExchange directExchange() {return new DirectExchange(EXCHANGE_NAME,true,false);}/*** 绑定 将队列和交换机绑定, 并设置⽤于匹配键:DirectRouting** @return*/@BeanBinding bindingDirect() {return BindingBuilder.bind(directQueue()).to(directExchange()).with(ROUTING);}@Beanpublic MessageConverter jsonMessageConverter(){return new Jackson2JsonMessageConverter();}
}

总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/80860.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dense 与 MoE 系列模型架构的全面对比与应用策略

0. 简介 人工智能领域正经历着一场架构革命&#xff0c;从传统的密集连接模型&#xff08;Dense&#xff09;向混合专家模型&#xff08;Mixture of Experts, MoE&#xff09;的转变。本文将全面剖析这两种模型架构的本质差异、各自优势与挑战&#xff0c;并提供战略性的选择框…

代码随想录算法训练营第四十天

LeetCode题目: 647. 回文子串516. 最长回文子序列 其他: 今日总结 往期打卡 647. 回文子串 跳转: 647. 回文子串 学习: 代码随想录公开讲解 问题: 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。…

Supabase 的入门详细介绍

Supabase 是什么&#xff1f; 简单来说&#xff0c;Supabase 是一个开源的 Firebase 替代品。它提供了一整套后端即服务 (BaaS - Backend as a Service) 的工具&#xff0c;让你能够快速构建应用程序的后端&#xff0c;而无需自己从头搭建和管理服务器、数据库等基础设施。 S…

【MySQL】mysql/bin目录下程序介绍

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【MySQL】探秘&#xff1a;数据库世界的瑞士军刀 MySQL在安装完成后&#xff0c;通常会包含以下程序&#xff0c;以Ubuntu上的mysql为例&#xff0c;我们可以查看到 以下是常用的mysql程序 程序名作用…

IDEA+git将分支合并到主分支、IDEA合并分支

文章目录 一、合并分支二、可能遇到的问题2.1、代码冲突 开发过程中我们可能在开发分支(dev)中进行开发&#xff0c;等上线后将代码合并到主分支(master)中&#xff0c;本文讲解如何在IDEA中将dev分支的代码合并到master分支中。 一、合并分支 功能说明&#xff1a;将dev分支的…

通过Ollama读取模型

通过Ollama读取模型 前言一、查看本地Ollama上有哪些模型二、调用bge-m3模型1、调用模型2、使用bge-m3进行相似度比较 三、调用大模型 前言 手动下载和加载大模型通常需要复杂的环境配置&#xff0c;而使用Ollama可以避免这一问题。本文将介绍如何调用Ollama上的模型。 一、查…

JS 中 Object.keys() 和 Object.values() 的深度解析与应用

文章目录 前言一、Object.keys() 和 Object.values() 基础1. Object.keys()2. Object.values() 二、与 Object.entries() 的对比三、实际应用场景1. 遍历对象属性2. 判断对象是否为空3. 对象与数组的转换4. 动态属性操作5. 过滤对象属性 总结 前言 在 JavaScript 开发中&#…

HCIP(BFD)

一、前言 随着网络应用的广泛部署,网络发生故障极大可能导致业务异常。为了减小链路、设备故障对业 务的影响,提高网络的可靠性,网络设备需要尽快检测到与相邻设备间的通信故障,以便及时采取措施,保证业务正常进行。BFD(Bidirectional Forwarding Detection,双向转发检测)提供…

Webpack其他插件

安装html打包插件 const path require(path); const HtmlWebpackPlugin require(html-webpack-plugin) module.exports {entry: path.resolve(__dirname,src/login/index.js),output: {path: path.resolve(__dirname, dist),filename: ./login/index.js,clean:true},Plugin:…

【Linux网络】网络层

网络层 在复杂的网络环境中确定一个合适的路径 IP 协议 IPV4 点分十进制[0,255].[0,255].[0,255].[0,255]IPV6 IP地址目标网格目标主机 基本概念 主机:配有IP地址,但是不进行路由控制的设备;路由器:即配有IP地址,又能进行路由控制;节点:主机和路由器的统称。 两个问题 路…

跨域的几种方案

因为浏览器出于安全考虑&#xff0c;有同源策略。也就是说&#xff0c;如果协议、域名、端口有一个不同就是跨域&#xff0c;Ajax 请求会失败。 我们可以通过以下几种常用方法解决跨域的问题 JSONP JSONP 的原理很简单&#xff0c;就是利用 <script> 标签没有跨域限制…

基于EFISH-SCB-RK3576/SAIL-RK3576的智能安检机技术方案‌

&#xff08;国产化替代J1900的全场景技术解析&#xff09; 一、硬件架构设计‌ ‌核心处理模块‌ ‌异构计算架构‌&#xff1a; ‌四核Cortex-A72&#xff08;2.3GHz&#xff09;‌&#xff1a;运行X光图像重建算法&#xff08;FDK反投影&#xff09;&#xff0c;支持双能谱…

MQ防重复消费----去重表结合 Spring AOP 切面编程,抽象封装成通用幂等注解

以下内容包含针对 NoMQDuplicateConsumeAspect 的深度面试问答、消息队列重投递触发场景、AOP 切面编程扩展&#xff0c;以及基于已有实现的关键要点与步骤总结。文中所有论断均引用多源资料&#xff0c;以助于您在面试与实战中全面展示对幂等消费切面及消息重投的理解。 一、深…

[:, :, 1]和[:, :, 0] 的区别; `prompt_vector` 和 `embedding_matrix`的作用

prompt_vector = torch.sum(prompt_embedding * attention_weights.unsqueeze(-1), dim=1) # [1, hidden_dim] prompt_vector = torch.sum(prompt_embedding * attention_weights.unsqueeze(-1), dim=1) 主要作用是通过将 prompt_embedding 与 attention_weights 相乘后再按指…

Dinky 安装部署并配置提交 Flink Yarn 任务

官方文档 https://www.dinky.org.cn/docs/1.1/deploy_guide/normal_deploy 版本 dinky 1.1.0、1.2.3 当前最新发布版本为 1.2.3 &#xff0c;但是官方文档最新稳定版为 1.1 &#xff0c;所以先选择 1.1.0&#xff0c;验证通过后&#xff0c;再尝试 1.2.3 &#xff0c;发现 1…

java连数据库

一、准备工作 ​​安装MySQL数据库​​ 确保已安装MySQL服务器并启动服务 ​​下载JDBC驱动​​ 官方驱动&#xff1a;MySQL Connector/JMaven依赖&#xff1a; <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactI…

【生态信息】开源软件全方位解析

开源软件(0pen Source Software&#xff0c;0ss)是指其源代码可以公开发布、查看、使用和修改的软件。这一概念的核心在于开放性和共享性&#xff0c;允许开发者自由地使用、修改、分发以及改进软件。开源软件通常遵循特定的开源许可证&#xff0c;这些许可证确保了软件的自由使…

探秘 DeerFlow:字节跳动开源的科研创作魔法盒!

1.前言 字节跳动于2025年5月9日开源了名为DeerFlow的全新Deep Research项目&#xff0c;该项目基于LangStack框架&#xff0c;旨在通过人工智能技术简化科研和内容创作流程。DeerFlow整合了语言模型、网络搜索、爬虫和Python代码执行等多种工具&#xff0c;支持深度研究、MCP集…

机器学习第十一讲:标准化 → 把厘米和公斤单位统一成标准值

机器学习第十一讲&#xff1a;标准化 → 把厘米和公斤单位统一成标准值 资料取自《零基础学机器学习》。 查看总目录&#xff1a;学习大纲 关于DeepSeek本地部署指南可以看下我之前写的文章&#xff1a;DeepSeek R1本地与线上满血版部署&#xff1a;超详细手把手指南 一、买菜…

less中使用 @supports

在Less中使用supports supports 是CSS的条件规则&#xff0c;用于检测浏览器是否支持特定的CSS属性或值。在Less中&#xff0c;你可以像在普通CSS中一样使用supports&#xff0c;同时还能利用Less的特性来增强它。 基本用法 /* 检测浏览器是否支持display: flex */ supports …