合同录入
设计表结构
- 01.1 合理设计表结构至少包括合同表,合同付款计划表,采用spring cloud搭建微服务架构至少包括注册中心,配置中心,网关,合同服务。(要求表及字段命名规范,注释清楚,满足三大范式要求,可以适当运用反范式设计) ??怎么创建付款计划表
- 02.1 合理设计表结构,用户表,患者表,数据字典表等(要求性别,学历,职业统一用数据字典表处理)
- 03.1 合理设计表结构,用户表,企业表,数据字典表等(要求所属行业:来自数据字典表)
- 04.1 合理设计表结构至少包括合同表,合同付款计划表,采用spring cloud搭建微服务架构至少包括注册中心,配置中心,网关,合同服务(要求:Spring Cloud - Eureka注册中心实现高可用功能)。 ??怎么创建付款计划表
服务搭建
- 01.2 Spring Cloud - Eureka注册中心实现高可用功能
- 01.3 Spring Cloud - Gateway网关路由
- 02.2 搭建spring cloud微服务架构,包括注册中心,配置中心,网关,用户服务,患者服务等.(要求:注册中心实现高可用,服务正常启动)
- 02.3 Spring Cloud - Gateway网关路由(要求:前端对服务访问均通过gateway访问;gateway网关配置路由;Gateway网关解决跨域问题)
- 03.2 搭建spring cloud微服务架构,包括注册中心,配置中心,网关,用户服务,企业服务等(注册中心实现高可用,服务正常启动)
- 03.3 Spring Cloud - Gateway网关路由(前端对服务访问均通过gateway访问;gateway网关配置路由;Gateway网关解决跨域问题)
- 03.4 服务监考(使用Admin查看用户服务的准实时日志,并演示实时调整日志等级)
- 04.2 Spring Cloud - Gateway网关路由(前端对服务访问均通过gateway访问;gateway网关配置路由;Gateway网关解决跨域问题)
- 05.6 Spring Cloud - Eureka注册中心(安装Eureka Server组件,并进行配置;启动Eureka Server并验证服务可用性)
- 05.8 Spring Cloud - Gateway网关路由(前端对用户中心,费用等系统的访问均通过gateway进行)
- 06.4 搭建Eureka服务,并将用户,商品等服务注册到Eureka中
- 06.5 使用Spring Cloud Gateway作为服务网关,所有对接口的调用都需要走网关,正确实现跨域配置
- 07.1 完成Eureka服务的搭建和配置,并将用户中心、服务管理系统接入Eureka
- 07.8 搭建网关服务,并合理配置路由,确保用户服务、服务管理服务等
链路追踪
- 01.4 使用Zipkin作为链路追踪的框架,将所有的应用都集成到Zipkin上,并演示应用之间的调用链路和延迟时间等信息。
- 02.4 使用Zipkin作为链路追踪的框架,将所有的应用都集成到Zipkin上,并演示应用之间的调用链路和延迟时间等信息
- 04.3 使用Zipkin作为链路追踪的框架,将所有的应用都集成到Zipkin上,并演示应用之间的调用链路和延迟时间等信息
- 06.6 使用Zipkin作为链路追踪的框架,将所有的应用都集成到Zipkin上,并演示应用之间的调用链路和延迟时间等信息
合同列表
- 01.5 正确分页,要求能够查看每个合同的付款计划列表。
- 02.6 患者列表正确展示(必须包含患者编号),分页正确
- 03.6 企业列表页面(列表正确展示(必须包含企业编号),分页正确)
- 04.5 合同列表(正确分页;用户登录只能查看自己录入的合同信息,非自己录入的不能查看;要求切换不同用户进行展示)
- 04.6 合同列表-搜索功能(根据合同编号精确查询;根据租客名称模糊查询;根据签约和到期日期精确查询)
- 05.1 费用管理 - 列表展示(按照生成日期降序排序;点击某一行可以进入该费用的详情页)
- 06.1 列表展示 使用Vue和ElementUI实现一个商品列表页面,要求包含商品名称、价格、单位、库存等信息,并支持分页
- 07.3 实现服务管理中的列表页,包括列表展示、搜索、分页等功能(完成根据服务类型(精确查询)和服务名称(模糊查询)的查询)
- 08.2 用户可以输入房屋名称进行搜索,后端根据名称过滤查询数据库中的房屋数据,并返回给前端。(缓存搜索列表首页数据,提高页面的响应能力)
- 10.2 2. 在后端实现分页功能,通过接口参数传递页码和每页显示的数量,后端根据这些参数查询数据库,返回相应的结果给前端。(使用Redis缓存第一页的数据,提升系统性能)
@GetMapping("/page/{page}/{size}")
public Result<IPage<Orders>> page(@PathVariable int page, @PathVariable int size) {
IPage<Orders> ipage = new Page(page, size);
if(page == 1) {
System.out.println("=========第一页");
String key = key(page, size);
IPage<Orders> orders = (IPage<Orders>)redisTemplate.opsForValue().get(key);
if(orders == null) {
System.out.println("第一页走数据库");
ordersService.page(ipage); //从数据库中分页读取数据
redisTemplate.opsForValue().set(key,ipage,30, TimeUnit.MINUTES);
return Result.ok(ipage);
}else {
System.out.println("第一页走缓存");
return Result.ok(orders);
}
}else {
System.out.println("其他页走数据库");
ordersService.page(ipage);
return Result.ok(ipage);
}
}
- 09.2 提供一个房屋列表页面,前端可以通过API接口获取所有房屋的数据,并实现分页功能。(确保大数量的情况下,分页性能不受影响)
格式验证:
- 01.6 合同录入 -完成页面原型前端校验(签订日期小于结束日期;签订日期小于结束日期)
<template>
<div class="about">
<el-form ref="form" :model="contract" :rules="rules">
<el-form-item label="开始时间" prop="start">
<el-date-picker v-model="contract.start" type="date"></el-date-picker>
</el-form-item>
<el-form-item label="开始时间" prop="end">
<el-date-picker v-model="contract.end" type="date"></el-date-picker>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: "AboutView",
data() {
let validateStart = (rule, value, callback) => {
if(this.contract.end == null) {
callback();
}
if(value >= this.contract.end ) {
callback(new Error("开始不能大于结束"));
}else {
callback();
}
};
let validateEnd = (rule, value, callback) => {
if(this.contract.start == null) {
callback();
}
if(value <= this.contract.start) {
callback(new Error("结束不能大于开始"));
}else {
callback();
}
}
return {
contract: {
start: null,
end: null
},
rules: {
start: [
{required: true, message: "不能为空", trigger: 'blur'},
{validator: validateStart, trigger: 'blur'}
],
end:[
{required: true, message: "不能为空", trigger: 'blur'},
{validator: validateEnd, trigger: 'blur'}
]
},
}
}
}
</script>
-
01.7 合同录入 -完成页面原型后端业务功能(解决合同编号重复问题;合同业务类型来自redis数据缓存;要考虑表单重复提交的解决方案)
-
02.7 患者添加功能(手机号和证件号码(身份证)进行格式校验;年龄必须在1-120岁区间)
//后端判断年龄
@PostMapping("/testAge")
public Result<Boolean> testAge(@RequestBody Contract contract) {
int age = IdcardUtil.getAgeByIdCard(contract.getIdcard());
if(age>=1 && age<=120) {
return Result.ok(true);
}else {
return Result.ok(false);
}
}
- 03.7 企业添加功能-校验功能(手机号进行格式校验;数据字典表中的数据要求采用redis缓存;企业logo只能上传图片功能)
<el-upload
class="avatar-uploader"
action="https://jsonplaceholder.typicode.com/posts/"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"> //在上传之前做一些处理
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
methods: {
beforeAvatarUpload(file) {
let type = file.type;
let canUpload = type === "image/jpeg" || type === "image/png";
if(!canUpload) {//如果不是图片则提醒用户
this.$message.error("只能上传图片");
}
return canUpload;
}
}
- 04.7 合同录入 -完成页面原型前端校验(租约开始日期小于结束日期;租约信息进行非空校验;提交合同录入后端要对用户是否登录进行校验,如果未登录不准录入合同)
// 参见 01.7 解决方案
- 09.2 完成房屋列表,实现分页和根据名称查找功能
用户登录:
- 02.5 用户登录功能(用户名+密码登录;密码必须采用md5加密技术处理;考虑用户1分钟登录三次不正确进行锁定账号功能)
@PostMapping("/login")
public Result<String> login(@RequestBody User user, HttpServletResponse response) {
//因为要判断某用户某分钟输错了几次,所以key要包含用户名称(这里是邮箱)和分钟数
String key = user.getMail();
Object obj = redisTemplate.opsForValue().get(key);
if(obj != null) {
int times = (int)obj;//检查某用户在这一分钟错误尝试了几次
if(times >= 3) {// 如果大于三次,则报错
return Result.err(-3, "错误尝试次数太多");
}
}
User dbUser = userService.findByMail(user.getMail());
if(dbUser == null) {
return Result.err(-1, "用户不存在");
}
if(dbUser.getPassword().equals(encode(dbUser.getSalt(), user.getPassword()))) {
String token = createToken(dbUser);
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
response.addCookie(cookie); //添加cookie
redisTemplate.opsForValue().set(token, dbUser, 30, TimeUnit.MINUTES);
return Result.ok(token);
}else {
// 密码错误,增加一次
long result = redisTemplate.opsForValue().increment(key);
if(result == 1){// 第一次尝试时设置缓存的生命周期为1分钟。
redisTemplate.expire(key, 1,TimeUnit.MINUTES);
}
return Result.err(-2, "密码不正确");
}
}
- 03.5 用户登录功能(用户名+密码登录;密码必须采用md5加密技术处理;考虑用户登录登录成功后采用jwt生成token返回給前端)
- 04.4 用户登录功能(用户名+密码登录;密码必须采用md5加密技术处理;考虑用户登录登录成功后采用jwt生成token返回給前端)
- 05.4 用户服务 - 用户登录(使用Vue和Element UI实现一个用户登录页面;前后端分离cookie跨域,页面跨域,登录成功,把认证标识写入cookie中)
login() {
this.axios.post("/user/user/login", this.user)
.then(res => {
console.log(res.data);
if(res.data.state === 0) {
document.cookie = "token="+res.data.data; //注意 token后面的'='是必须的!!!!
this.$router.push("/");
}
})
}
为了保证前端带cookie到后端需要确保main.js中有下面这行代码:
//它指示了是否该使用类似 cookie、Authorization 标头或者 TLS 客户端证书等凭据进行跨站点访问控制(Acess-Control)请求
axios.defaults.withCredentials=true;
- 07.2 用户登录功能实现,登录成功写入Cookie(前后端分离cookie跨域,登录成功,把用户信息写入cookie中;将用户信息放入redis中,供后面验证用户是否登录)
- 08.1 用户登录(前后端分离cookie跨域,页面跨域,登录成功后把必要信息写入cookie中)
- 09.1 用户登录,后端在登录成功后,将必要的用户信息(如用户ID)写入cookie中,并设置有效期。(设置Cookie跨域,携带Cookie信息到后台)
let now = new Date();
now.setMinutes(now.getMinutes()+ 30);
document.cookie = "token="+res.data.data+";expires="+now.toUTCString();
- 10.1 用户登录在前后端分离的架构中,使用 JWT(JSON Web Token)实现用户认证和授权。用户登录成功后,后端生成一个 JWT 并返回给前端,前端将 JWT 存储在 Cookie 中(保证JWT和Cookie的过期时间一致,Cookie信息携带到后台进行校验)
合同添加:
//引入Hutool依赖,使用其中的雪花算法实现;同时最好数据库中对应字段加上唯一索引。
@GetMapping("/no")
public Result<String> no() {
String no = IdUtil.getSnowflakeNextIdStr();
return Result.ok(no);
}
- 02.8 患者添加功能(性别,学历,职业统一来自数据字典表,且采用redis缓存;患者添加功能要考虑防止表单重复提交功能;每个患者要求生成一个唯一的患者编号)
- 03.8 企业添加功能(添加功能要考虑防止表单重复提交功能;每个企业要求生成一个唯一的患者编号;添加功能考虑用户是否登录进行校验(可以采用网关过滤器实现))
@Component
public class JWTFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1. 哪些url需要进行安全检查
String currentUrl = exchange.getRequest().getPath().value();
if(!"/user/user/login".equals(currentUrl)) {
MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
if(cookies != null && cookies.getFirst("token") != null) {
HttpCookie token = cookies.getFirst("token");
String jwt = token.getValue();
boolean verify = JWTUtil.verify(jwt, "1234".getBytes());
if(!verify) {
return needLogin(exchange);
}
}else {
return needLogin(exchange);
}
}
//2, 对于需要安全检查的url进行检验。
//3,合法通过,不合法拒绝。
return chain.filter(exchange);
}
private Mono<Void> needLogin(ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return 0;
}
}
- 04.8 合同录入 -完成页面原型后端业务功能(解决合同编号重复问题;要考虑表单重复提交的解决方案;合同录入成功合同录入人员为当前登陆用户;确保金额精度,使用BigDecimal数据类型)
- 05.2 费用管理 - 添加费用(输入客户名称、生成日期、合同金额等字段,选择费用类型信息,并进行表单验证; 点击添加按钮后,将费用信息提交到后端服务保存,并刷新费用列表页 ;解决合同编号重复问题;后台合同金额使用BigDecimal,防止精度丢失)
- 05.3 费用管理 – 事务 (保存费用时,如果费用类型不存在则添加费用类型后再保存费用,否则直接添加费用即可;添加事务防止费用保存失败,导致数据不完整)
- 05.5 用户服务 - 用户注册(校验密码规则,必须包含数字、字母,长度大于6位)
/(?=.*\d)(?=.*[a-zA-Z]).{6,}/
- 06.2 添加商品功能- 要求用户可以输入商品的名称、价格、单位、库存等信息,并能够上传商品图片(确保价格金额精度,使用BigDecimal数据类型;防止保存失败,数据不完整,配置事务;添加商品编号,防止商品编号重复)
- 06.3 用户服务 - 实现一个简单的用户注册页面,要求用户可以输入用户名、密码等信息,并能够进行表单校验(前后端分离cookie跨域,页面跨域,登录成功,把认证标识写入cookie中)
- 06.8 创建商品时要防止表单重复提交
- 07.4 完成服务管理中的创建功能(适当处理防止重复预约;服务编号使用雪花算法防止重复)
- 08.3 前端发送请求到后端API接口,传递选中的房屋ID等必要信息。 ??
- 08.4 创建租赁订单页面中,可以修改租赁时间区间,选择时间后即时获得租赁价格(价格计算必须是后端计算后返回给前端,主要使用BigDecimal计算)
- 08.6 创建租赁订单需要防止用户手误导致创建重复订单(使用Redis实现表单重复提交,代码逻辑正确)
- 08.7 创建租赁订单时,后端生成唯一的订单号,可以使用UUID或者其他唯一标识符生成算法。
- 09.3 在房屋列表页面中,提供一个按钮或链接,当用户点击选中某个房屋时,触发生成租赁订单的操作
- 09.4 后端根据租赁时间区间和房屋信息进行计算,使用BigDecimal进行精确计算,并返回计算后的租赁价格给前端
- 09.6 后端在接收到创建租赁订单的请求时,先进行重复订单的判断(使用Redis实现表单重复提交,代码逻辑正确)
- 09.7 在创建租赁订单时,后端生成唯一的订单号,可以使用UUID或者其他唯一标识符生成算法。
- 10.3 前端选择房屋并提交订单请求,后端接收请求后先进行用户身份验证(通过校验 JWT),确保是登录用户才能创建订单。(JWT工具类校验Token是否合法,然后在创建订单)
- 10.4 创建租赁订单页面中,可以修改租赁时间区间,选择时间后即时获得租赁价格(价格计算必须是后端计算后返回给前端,主要使用BigDecimal计算)
- 10.6 当用户提交订单时,后端先查询数据库判断该用户是否已经存在相同的订单。如果有重复订单,则返回错误提示给前端,阻止创建重复订单(使用Redis实现表单重复提交,代码逻辑正确) ??
- 10.7 在创建租赁订单时,后端可以生成唯一的订单号,确保订单号的唯一性。
Feign
- 05.7 Spring Cloud - OpenFeign服务调用(添加费用时,远程(OpenFeign)调用用户中心,确认当前操作人已经登录系统:服务间调用配置Hystrix熔断防止系统雪崩)
- 07.5 创建服务时需要远程调用(Feign)用户服务,确保只有登录的用户才可以创建服务.
- 07.6 远程调用实现服务降级,当降级时任何用户都可以创建服务.
- 08.8 创建租赁订单时需要首先远程调用用户服务,确保只有登录用户才可以创建订单(校验用户状态,避免系统攻击)
- 09.8 后端通过远程调用用户服务,验证当前用户是否已登录,可以通过检查cookie中的用户信息来进行验证(校验用户状态,避免系统攻击)
- 10.8 创建租赁订单时,前端携带 JWT 发起请求到用户服务接口,后端通过解析 JWT 获取用户信息,验证用户是否登录。
文件上传
- 06.7 实现一个简单的文件上传功能,要求用户可以选择本地的图片进行上传,并在后台进行保存。(商品图片上传失败,提示“上传失败”)
验证身份:
- 08.5 创建租赁订单中需要填入租赁人的身份证和名称并验证是否真实
- 09.5 前端发送请求到后端API接口,传递身份证和姓名等参数
- 10.5 在创建租赁订单页面中,前端通过表单输入租赁人的身份证和名称等信息
@PostMapping("/verify")
public Result<String> verify(@RequestBody Contract contract) {
Map<String,Object> map = new HashMap<>();
map.put("idNo", contract.getOwnerNo());
map.put("name", contract.getOwnerName());
String body = HttpRequest.post("https://idenauthen.market.alicloudapi.com/idenAuthentication")
.header("Authorization", "APPCODE 你的appcode")
.form(map)
.execute()
.body();
System.out.println(body);
return Result.ok(body);
}
Admin
- 07.7 使用Admin查看用户服务的准实时日志,并演示实时调整日志等级.
总体其他:
- 01.8 合同录入 – 事务(合同录入提交后,要求根据分期付款方式完成付款计划的录入;确保付款金额精度,使用BigDecimal数据类型)
支付中心
订单列表:
- 11.1 页面展示:订单列表页面展示正常,包含金额字段和支付状态,使用element-ui实现
- 12.1 完成页面开发:包含必要字段:金额字段和支付状态(设计订单表,满足三大范式和业务要求,模拟模拟数据要尽量真实。)
- 13.1 数据库设计和页面开发:订单信息表结构设计规范,完成服务列表页面开发(设计订单表,满足三大范式和业务要求,模拟模拟数据要尽量真实。)
- 14.1 支付页面设计符合用户习惯,能快速完成支付操作
- 15.1 设计订单表,满足三大范式和业务要求,并使用真实数据进行模拟.
- 16.1 使用Java编程语言生成虚拟订单数据,包括订单号、商品信息、金额等。(设计订单表,满足三大范式和业务要求,模拟模拟数据要尽量真实。)
- 17.1 可以使用随机数或者测试框架来生成模拟数据,确保数据的真实性和合理性
- 18.1 使用Java编程语言生成虚拟订单数据,可以使用随机数或测试框架来生成模拟数据(设计订单表,满足三大范式和业务要求,模拟模拟数据要尽量真实)
- 19.1 设计订单表,模拟数据。(设计订单表,满足三大范式和业务要求,模拟模拟数据要尽量真实)
- 20.1 设计订单表,模拟数据 (设计订单表,满足三大范式和业务要求,模拟模拟数据要尽量真实。)
支付宝集成:
- 11.2 支付宝SDK集成:参考官方文档进行集成,支付请求正常,能够跳转到支付宝页面
- 12.2 支付集成:支付宝支付接口正常集成,在订单页生成合法的支付链接。
- 13.2 支付集成:能够无缝对接支付宝支付
- 14.2 支付集成:集成支付宝支付接口,完成支付功能
- 15.2 Spring Boot集成支付宝,实现支付功能的接入和配置.
- 16.2 使用支付宝提供的SDK或API,结合Spring Boot框架,实现支付宝支付功能的集成
- 17.2 配置支付宝的商户信息、密钥等参数,并使用HTTPS协议与支付宝服务器进行交互
- 18.2 在应用的配置文件中配置支付宝的商户信息、密钥等参数,如商户号、应用ID、私钥等
- 19.2 在支付请求方法中,调用支付宝SDK或API提供的接口,将订单信息发送给支付宝服务器进行支付
- 20.2 创建一个支付服务类或控制器,实现支付功能的相关方法,如订单创建、支付请求发送和支付结果查询等
回调验签:
- 11.3 支付宝回调处理:支付回调接口正确验签,对验签通过的请求,及时更新本地订单状态
- 12.3 回调处理:支付宝回调接口验证测试通过,接收支付宝回调,处理回调成功且正确
- 13.3 支付回调:回调接口能够接收支付宝的回调,验证签名安全,及时更新支付状态
- 14.3 支付回调:回调接口接收支付宝支付信息,验证确实为支付宝回调后再更新订单状态,确保支付信息的准确性
- 15.3 处理支付宝回调验签成功后的后续业务逻辑
- 16.3 在支付宝支付完成后,支付宝会向指定的回调URL发送支付结果通知(回调接口,完成验签功能,并能够修改业务状态)
- 17.3 在Spring Boot应用中配置回调URL,并实现接收和解析支付宝回调数据的逻辑
- 18.3 在接收回调通知的方法中,解析支付宝回调数据,可以使用支付宝SDK或API提供的工具类来解析。
- 18.4 如果支付成功,更新订单状态为已支付,并记录相关支付信息
- 19.3 获取支付宝回调通知中的签名和其他参数,使用支付宝提供的验签规则对回调数据进行验签
- 20.3 验签成功后,验证订单信息的正确性,并处理支付结果。可以更新订单状态、生成支付记录、发送通知等操作
- 20.4 需要注意处理并发情况下的幂等性问题,确保多次回调不会重复处理订单。
掉单处理:
- 11.4 对于‘未支付’订单,系统能够定时进行处理,根据支付宝接口返回的状态更新本地订单状态
- 12.4 对于未能及时处理回调的订单,能够定时查询支付宝,并根据查询结果更新本地订单状态
- 15.4 实现调单处理功能,修改订单状态以确保数据准确性和一致性
- 16.4 检查支付宝回调通知中的支付状态,根据实际需求,处理支付成功或支付失败的订单
- 17.4 可以更新订单状态、生成支付记录、发送通知等操作,确保订单状态与支付宝同步 ??
- 18.4 如果支付成功,更新订单状态为已支付,并记录相关支付信息
关闭订单:
- 11.5 支付关闭处理:对于超过30分钟没有支付成功的订单,及时关闭
- 14.5 支付通知:支付成功后,可通过短信或邮件通知用户支付成功,确保用户体验
- 15.5 使用RocketMQ异步消息通知机制,在订单未支付关闭时发送通知消息。
- 16.5 根据业务需求设置订单的有效支付时间,超过该时间未支付的订单将自动关闭
- 18.5 订单创建时,设置订单的有效支付时间,超过该时间未支付的订单将自动关闭
- 19.5 当订单被关闭时,将相关订单信息发送到RocketMQ的消息队列中
退款:
- 13.5 退款功能:已支付的订单,完成退款功能,退款成功修改订单状态
支付成功通知:
- 12.5 支付成功通知:对于成功支付的客户,及时向客户发送支付成功通知。
- 13.4 支付状态通知:在支付成功的情况下,能够用短信及时通知客户。
- 14.4 支付通知:支付成功后,可通过短信或邮件通知用户支付成功,确保用户体验
其他:
- 17.5 使用RocketMQ作为消息队列,将未支付关闭的订单信息发送到消息队列中
- 19.4 如果支付失败,根据需要进行相应的处理操作,如取消订单、释放库存等
- 20.5 在消费者中实现相应的逻辑,如记录日志、发送提醒等操作,以便及时处理关闭的订单信息 ??
预约看房
集成RocketMQ
- 30.1 集成、配置RocketMQ作为支付模块的消息推送机制,确保消息的可靠传递和处理
消息消费
- 30.2 配置RocketMQ的消费者,订阅"payment_topic"主题,并实现相应的业务逻辑来处理支付相关消息
- 29.2 配置RocketMQ的消费者,订阅"payment_topic"主题,确保消费者能够接收到支付消息,并实现负载均衡和消费者组管理以提高系统的容错性和扩展性 ??
- 28.4 将消息的处理过程异步化,并利用RocketMQ的特性实现并行处理,以提高系统的消息处理速度和吞吐量,优化系统性能和资源利用率 ???
- 22.2 消息消费者从RocketMQ中异步获取消息,并进行处理,例如发送短信、邮件等通知操作。 ???
- 21.2 接收方的消息消费者从RocketMQ中获取消息,之后通过发送短信、邮件通知用户
消息发送
- 30.3 使用RocketMQ的异步发送方式,将支付消息以消息对象的形式发送到"payment_topic"主题,提高系统的并发能力和响应速度
- 29.1 配置RocketMQ的生产者,设置主题为"payment_topic",确保消息的可靠传递,并采用可靠性保证机制增加重试次数和错误处理机制
rocketmq:
name-server: 127.0.0.1:9876 # rocketMQ 名称服务器
producer:
group: test # 发送组
topic: test-topic # topic 相当于一个地址
retry-times-when-send-failed: 5 # 同步发送失败要重试几次
retry-times-when-send-async-failed: 3 # 异步发送失败要重试几次
retry-next-server: true # 发送错误后重试下一台服务
send-message-timeout: 3000 # 发送超时
- 29.4 将用户下单成功、支付成功或取消订单等事件封装成消息对象,并通过异步发送消息的方式减轻直接同步发送短信或邮件对系统性能的影响,提升系统的稳定性和并发能力
- 28.1 配置系统以确保用户下单成功、支付成功或取消订单等事件能够触发相应的短信或邮件通知,提升用户体验和服务效果 ????
- 27.1 通过将相关信息封装成消息对象,并利用RocketMQ的生产者将消息发送到指定的消息主题,实现消息的可靠传递和分发
- 26.1 确保支付模块中用户下单成功、支付成功或取消订单等事件能够触发消息发送,并在消息消费者端正确处理 ?? 都要发送这些消息吗?
- 25.1 当用户下单成功、支付成功或取消订单时,调用RocketMQ的生产者发送相应的支付消息到"payment_topic"
- 25.4 使用RocketMQ的异步发送方式,将消息发送到"payment_topic"主题
- 24.1 在用户下单或支付时,将通知信息封装成消息并通过RocketMQ发送至中间件
- 23.1 用户完成购物行为后,将通知信息封装成消息并通过RocketMQ发送至中间件。
- 21.1 使用RocketMQ中间件实现业务解耦,发送短信和邮件通知
- 30.5 添加消息推送的异常处理机制,如消息发送失败时的错误重试机制和错误日志记录,以保证消息的可靠性和系统稳定性
消息发送配置:
- 22.1 使用RocketMQ进行同步发送消息,并设置合适的重试次数和超时时间,避免网络波动导致消息重复发送
防止重复消费
- 30.4 为避免重复消费,使用Redis或其他缓存工具记录已经处理过的消息的唯一标识符,以实现幂等性,确保数据处理的准确性和一致性
@Data
public class PaySuccessMsg {
private String phone;
private BigDecimal price;
private int orderId;
}
@Service
@RocketMQMessageListener(topic="pay_succes_0704", consumerGroup = "pay_succes_0704_group")
@Slf4j
public class PaySuccessConsumer implements RocketMQListener<PaySuccessMsg> {
@Autowired
RedisTemplate redisTemplate;
@Override
public void onMessage(PaySuccessMsg paySuccessMsg) {
//做法一,在进来的时候设置。这种方法更简单
//做法二,进来时不做判断,发送成功以后再做设置。
String key = key(paySuccessMsg);
if(redisTemplate.opsForValue().setIfAbsent(key,"1",30, TimeUnit.MINUTES)) {
System.out.println("我收到了消息:" + paySuccessMsg.toString());
ZhenziSmsClient client = new ZhenziSmsClient("https://sms_developer.zhenzikj.com", "112605", "6a987156-398b-4c3c-9d84-ce6b3921bd18");
sendMsg(paySuccessMsg, client);
}else {
System.out.println("处理消息重复");
}
}
private static void sendMsg(PaySuccessMsg paySuccessMsg, ZhenziSmsClient client) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("number", paySuccessMsg.getPhone());
params.put("templateId", "10759");
String[] templateParams = new String[2];
templateParams[0] = ""+ paySuccessMsg.getPrice().floatValue();
templateParams[1] = "5分钟";
params.put("templateParams", templateParams);
try {
String result = client.send(params);
System.out.println(result);
} catch (Exception e) {
log.error("发送短信出现异常", e);
}
}
private String key(PaySuccessMsg paySuccessMsg) {
return DigestUtil.md5Hex(paySuccessMsg.toString());
}
}
- 29.3 为避免重复消费,消息体中添加唯一标识符如订单号或支付流水号,并在消费端进行去重处理,同时使用幂等性机制确保数据处理的准确性和一致性
- 28.3 利用RocketMQ内置的消息去重机制和消费者端的幂等性处理功能,避免同一条消息被重复消费,确保数据处理的准确性和一致性 ??内置消息去重
- 27.3 在消费者处理消息之前,通过检查消息的唯一标识符或状态,确保同一条消息不会被重复消费,保证数据的一致性和正确性。
- 26.3 利用RocketMQ内置的消息去重机制,结合消费者端的幂等性处理,确保同一条消息不会被重复消费,保证消息的准确性和一致性 ?? 内置消息去重
- 25.3 可以使用Redis等缓存工具记录已经处理过的消息的唯一标识符,以防止重复消费
- 23.3 在消息消费过程中,注意避免重复消费问题,以避免引发业务异常
- 22.4 为消息添加唯一标识如订单号、支付流水号作为消息ID,确保消息仅被处理一次,避免重复消费
防止消息丢失
- 28.2 正确配置RocketMQ的生产者和消费者实例,以确保消息不会在传递过程中丢失,增强系统的消息可靠性和稳定性 ??
- 27.2 RocketMQ采用高可靠的消息传递机制,确保消息能够准确地传递,且不会丢失,提供可靠性保障 ???
- 26.2 通过将消息发送到RocketMQ消息队列中,确保消息不会丢失,并能够在网络异常或其他问题后恢复正常消费
异步?
- 27.4 将短信发送或邮件发送的动作解耦至异步线程中,将发送操作与主线程分离,提高系统的响应速度和并发能力,优化用户体验和系统性能 ??
- 26.4 利用RocketMQ的异步发送特性,将短信发送或邮件发送的动作从主线程中解耦,提高系统的响应速度和并发能力,优化用户体验和系统性能
- 24.2 消息接收的设计。 消息消费者从RocketMQ中获取消息,将消息异步处理,例如发送短信、邮件通知用户
- 23.2 消息消费者从RocketMQ中异步获取消息,并进行相应的处理,如发送短信、邮件通知用户。???
消息堆积
- 24.3 消息堆积解决。 增加消息消费线程数:通过增加消息消费线程数,可以提高消息消费速度,减少消息堆积的可能性
@RocketMQMessageListener(consumeThreadMax = 128,topic="pay_succes_0704", consumerGroup = "pay_succes_0704_group")
- 22.3 增加消费者数量以提高消息消费速度,降低消息堆积的潜在风险
- 21.3 通过启用多个消息消费者实例进行并行处理,解决消息积压问题
消息补偿
- 24.4 消息补偿。:在消息消费过程中,对于处理失败的消息,进行一定次数的重试操作,并记录错误日志。
private void sendSMS(ZhenziSmsClient client, String phone, String orderId, int num) {
if(num <= 0){
log.error("发送短信重试仍旧失败。");
return;
}
Map<String, Object> params = new HashMap<String, Object>();
params.put("number", phone);
params.put("templateId", "10759");
String[] templateParams = new String[2];
templateParams[0] = orderId;
templateParams[1] = "5分钟";
params.put("templateParams", templateParams);
try {
String result = client.send(params);
System.out.println(result);
if(!(result.indexOf("发送成功") > -1)){
sendSMS(client, phone, orderId, num-1);
}
} catch (Exception e) {
log.error("发送短信失败",e);
sendSMS(client,phone, orderId,num-1);
}
}
- 23.4 实现消息消费补偿机制,用于在消息消费过程中出现异常时进行重试操作,确保消息的可靠性
- 21.4 在消息消费过程中出现异常时,通过重试机制保证业务正确性
消费者集群
- 25.2 将RocketMQ的消费者消费模式设置为集群模式,确保多个消费者能够同时消费消息,提高可靠性 ??
@RocketMQMessageListener(messageModel = MessageModel.BROADCASTING,topic="pay_succes_0704", consumerGroup = "pay_succes_0704_group")
其他
消息持久化:
重试策略:
故障恢复机制
监控手段
顺序消息
报警机制
消费者均衡消费
- 21.5 消息推送系统可靠性设计(4分):包括消息持久化、重试策略和故障恢复机制,确保系统的可靠性 ???
- 22.5 消息可靠性保障(3分):使用持久化机制存储消息,结合监控手段和故障恢复机制,保证消息推送系统的可靠性
- 23.5 对于有序消息,考虑实现消息的顺序处理,确保消息按照特定顺序被消费 ???
- 24.5 建立消息监控机制,实时监测消息堆积情况,并设置相应的报警机制,及时发现和处理异常情况 ???
- 25.5 设置RocketMQ的消息可靠性保障机制,如开启同步刷盘和持久化,确保消息不会丢失。
- 26.5 配置RocketMQ的消费者端负载均衡策略,确保多个消费者能够均衡消费消息,提高系统的可伸缩性和稳定性
- 27.5 利用RocketMQ的顺序消息传递特性,确保同一个消息队列中的消息按照发送顺序进行消费,保证消息的顺序性,适用于有序处理的场景
- 28.5 结合RocketMQ提供的错误消息处理机制和重试策略,及时检测和处理消息处理过程中的异常情况,并进行恰当的重试,以确保系统的稳定运行和消息的可靠发送
- 29.5 在RocketMQ中配置适当的消息存储策略、消息投递超时时间以及消息重试次数,以确保消息的持久化存储、可靠性投递和容错处理,防止消息丢失或延迟。
外卖抢单
线程池的使用
- 40.1 通过合理的线程池参数配置,控制线程数量,避免资源耗尽和性能下降(请自定义线程池,并合理设置核心参数,以控制线程数量,避免线程过多而导致系统整体性能下降)
- 39.1 使用线程池管理和调度线程,确保外卖抢单系统能够高效处理大量请求(请自定义线程池,并设置适当的核心参数,避免线程数量过多导致系统整体性能下降)
//初始化线程池
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(2);//线程池最多有2个线程
executor.setCorePoolSize(1);//核心线程数为1
executor.setThreadNamePrefix("*_*");//线程名称前缀
executor.setQueueCapacity(100);//线程池等候队列
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//拒绝策略
executor.initialize();//初始化
return executor;
}
}
- 38.1 使用Java多线程技术处理外卖抢单模块的并发请求(请自定义线程池,并设置适当的核心参数,以避免线程数量过多对系统整体性能造成负面影响)
- 35.1 基于Spring Boot框架构建外卖抢单模块,使用Java多线程技术处理并发请求(自定义线程池,设置核心参数,避免线程过多,影响系统整体性能)
@RequestMapping("/process")
public Result<Boolean> createOrder(Book book) {
executor.execute(() -> {//使用多线程处理创建订单请求
Order order = createOrder(book, 1,1);
orderService.save(order);
});
return Result.ok(true);
}
- 34.2 使用线程池生成外卖订单(使用自定义线程池创建多个线程,并在每个线程中生成多个外卖订单。请设置合理的线程池参数,避免线程数量过多导致系统整体性能下降)
- 33.3 模拟用户产生大量订单 (使用Java多线程技术实现模拟用户并发产生大量订单。在实现过程中,请自定义线程池,并设置合理的核心参数,以避免线程数量过多导致系统性能下降)
- 31.2 创建多个线程,并在每个线程中生成多个外卖订单
@RequestMapping("/creat_order")
public Result<Boolean> creatOrder() {
Book book = new Book();
book.setId(1);
book.setPrice(BigDecimal.TEN);
for( int i=0;i<3; i++) {
executor.execute(() -> {
while(true) {
Order order = createOrder(book,1, 1);
orderService.save(order);
System.out.println("create order: " + order.toString());
try {
Thread.sleep(RandomUtil.randomInt(100,2000));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
return Result.ok(true);
}
限流
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: order
uri: lb://order # 指定服务器注册实例名
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/badthinghappend
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 30 # 放令牌的速率
redis-rate-limiter.burstCapacity: 200 # 令牌桶最大放多少个
redis-rate-limiter.requestedTokens: 1 #每次请求消耗几个令牌
key-resolver: "#{@userKeyResolver}" # 根据userName来限流
statusCode: MOVED_TEMPORARILY # 更改错误状态码
predicates: #断言
- Path=/order/** # 如果访问路径以services开头,则执行本路由规则
- 40.2 当达到限流阈值时,返回合适的错误码或页面提示用户稍后再试 (请使用Hystrix或Gateway网关等技术进行服务降级,以避免系统崩溃,并返回适当的错误码或页面提示,告知用户稍后再试) ?? 怎么实现
- 39.2 设置适当的限流策略,例如限制API接口每秒请求数或并发请求数(使用Gateway网关对外卖抢单系统进行服务降级,以防止系统崩溃,并确保在高负载情况下系统的正常运行)
- 38.2 基于网关做限流处理,以防止系统负载过高(通过配置网关进行限流,以确保系统在高负载情况下不会受到过多请求的影响,保持系统的稳定性)
- 37.2 自定义线程池和设置核心参数,在外卖抢单系统中避免线程过多,影响系统整体性能(通过自定义线程池并设置核心参数来管理外卖抢单系统中的线程,以避免线程过多对系统整体性能造成影响) ???
- 36.2 设置合理的限流策略,如限制API接口每秒请求数或并发请求数 (请描述如何设置合理的限流策略,例如通过Hystrix或Gateway网关进行服务降级,以避免系统崩溃)
- 35.2 使用分布式限流对外卖抢单接口进行访问频率的限制,防止系统负载过高(使用Hystrix或Gateway网关进行服务降级,避免系统崩溃)
- 33.2 限制订单服务的访问频率 (为了控制订单服务的访问频率,需要设定最大的访问次数和并发数量。请确保订单服务每分钟最多处理1800次访问请求,并发数量不超过200)
- 32.4 针对高峰时期流量过大可能引起的系统瘫痪问题实现相应解决方案 ???
- 31.4 针对高峰时期流量过大可能引起的系统瘫痪问题实现相应解决方案
超卖
- 40.3 根据订单状态和库存信息,使用Redis防止商品超卖 (请根据订单状态和库存信息,使用Redis等缓存技术,实现商品超卖的防止机制,确保每个订单都有足够的库存进行抢购)
- 39.3 使用Redis的原子操作,实现防止超卖问题的解决方案 (探索并实施基于Redis的原子操作,确保每个订单的抢单过程中不会出现超卖的情况)
- 38.3 使用Redis缓存热点数据,提高读取速度,减轻数据库压力 (使用Redis作为缓存,存储外卖抢单系统的热点数据,以加快数据的读取速度,减少对数据库的访问,从而减轻数据库的压力)
- 37.3 当外卖抢单系统达到限流阈值时,如何返回适当的错误码或页面提示用户稍后再试(在外卖抢单系统中如何通过Hystrix或Gateway网关进行服务降级,以避免系统崩溃,并给出相应的错误码或页面提示,在达到限流阈值时引导用户稍后重试)
- 36.3 在高并发场景下,使用Redis的原子操作(如incr/decr)实现防止超卖问题的解决方案(请详细说明在高并发场景下,如何利用Redis的原子操作(如incr/decr)来有效解决外卖抢单系统中可能出现的超卖问题)
@GetMapping("qiang_book")
public Result<Boolean> qiangBook(int num) {
Long book_total = redisTemplate.opsForValue().decrement("book_total", num);
System.out.println("还剩"+book_total+"本");
if(book_total >=0) {
//creatOrder();
Product product = productService.getById(1);
product.setStock(product.getStock()-num);
productService.updateById(product);
return Result.ok(true);
}else {
return Result.ok(false);
}
}
- 34.3 保证订单抢单的线程安全(为了防止多个用户同时抢购同一个订单,需要实现线程安全机制。请说明你打算如何保证订单抢单的安全性,确保每个订单只能被一个用户抢到) ??? 怎么说明
- 31.3 考虑多个外卖员同时抢某订单的情况,防止某订单被多人抢到
@RequestMapping("/qiang_order")
public Result<Boolean> qiangOrder(int pid, int uid) {
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(pid, "1", 3, TimeUnit.DAYS);
if(aBoolean) {
Orders orders = ordersService.getById(pid);
orders.setStatus("被抢单");
orders.setUserId(uid);
//设置订单状态
ordersService.updateById(orders);
}
return Result.ok(true);
}
JMeter
- 40.4 使用JMeter工具,分析测试结果(请使用JMeter工具对系统进行性能测试,分析测试结果,评估系统的吞吐量、并发能力和响应时间等指标,以便进一步优化系统性能)
- 39.4 评估外卖抢单系统的接口性能和稳定性,包括响应时间、吞吐量和错误率等指标(通过性能测试工具Jmetter,对外卖抢单系统进行接口性能和稳定性测试,分析关键指标包括响应时间、吞吐量和错误率等,以评估系统的性能和稳定性)
- 38.4 使用JMeter工具创建模拟请求,配置合理的线程数和并发用户数来模拟高峰期访(使用JMeter工具,配置合理的线程数和并发用户数,模拟外卖抢单系统在高峰期的访问情况,以评估系统的性能和可靠性)
- 37.4 根据订单状态和库存信息,使用Redis维护每个商品的可售数量,并在抢单时进行原子操作,保证库存的准确性(如何利用Redis缓存热点数据,在外卖抢单系统中根据订单状态和库存信息维护每个商品的可售数量,并确保在抢单过程中进行原子操作,以保证库存的准确性)
- 36.4 使用JMeter测试外卖抢单系统的接口性能 (请介绍如何使用JMeter工具对外卖抢单系统的接口性能进行测试,包括测试的目标、方法和常见指标等) ??
- 35.4 使用JMeter工具创建模拟请求,配置合理的线程数和并发用户数来模拟高峰期访问
- 37.5 使用JMeter工具对外卖抢单系统进行性能分析和优化(请阐述如何使用JMeter工具分析外卖抢单系统的性能,并提出相应的优化方案,例如通过调整线程池大小、数据库连接池参数等来提升系统的处理能力)
事务
- 37.1 使用数据库事务机制确保数据的一致性和并发操作的正确性 (利用数据库事务机制来管理外卖抢单系统中的数据操作,以确保数据的一致性和并发操作的正确性)
缓存
- 35.3 商品加载到Redis缓存中,提高读取速度,减轻数据库压力
- 32.2 完成产品信息的缓存代码 只需要完成后端代码,缓存信息存放在redis中
缓存一致性
- 32.3 完成产品更新功能。考虑到产品运行在并发请求中,请实现一种方案,确保缓存和数据库中的数据保持一致
订单关闭
- 34.4 编写定时任务自动删除过期订单(编写定时任务,用于自动删除过期的订单。请设定合适的时间间隔,确保系统能够及时清理过期的订单数据,并释放相关资源)
- 33.1 为了保证订单系统的数据一致性,需要及时关闭未支付订单。请自行设计数据库表结构,满足三大范式要求,并确保数据一致性。订单创建后,如果30分钟内未完成支付,则需要将该订单状态设置为已关闭
建表
- 36.1 设计合适的数据库表结构,准备用于保存订单的数据表(设计合适的数据库表结构,满足三大范式要求,并解释如何确保数据的一致性)???
- 34.1 创建外卖订单表(请使用Java代码生成外卖订单表,包含以下字段:订单ID、价格、送餐地址、目的地、创建时间和状态等。你可以自行建表,也可以参考提供的takeaway.sql文件,确保满足三大范式要求并保证数据一致性)
- 32.1 创建产品表和相应的代码 参考上文中的“product.sql”创建订单表。并生成相应的java代码以实现对该表操作(自行建表,可以参考product.sql,满足三大范式要求,保证数据一致性)
- 31.1 创建外卖订单表和相应的代码 (5分)参考上文中的“takeaway.sql”创建订单表。并生成相应的java代码以实现对该表操作(自行建表,可以参考takeaway.sql,满足三大范式要求,保证数据一致性)
其他
- 33.4 使用Postman实现创建订单,对订单创建请求进行安全验证 检验是否携带正确的token,只有带有正确token的请求才能创建成功
说明
- 33.5 在使用支付宝接口进行订单处理时,为了确保数据的完整性和安全性,需要进行验签操作。请说明验证签名的步骤,并确保只有通过验签的请求才能被认为是有效的 ???说明
- 34.5 在使用支付宝接口进行订单处理时,为了确保数据的完整性和安全性,需要进行验签操作。请说明验证签名的步骤,并确保只有通过验签的请求才能被认为是有效的
- 36.5 请说明如何使用JMeter工具对外卖抢单系统的稳定性进行评估,包括模拟高负载、长时间运行和异常情况等测试场景。 ??? 异常情况是指??
- 38.5 优化外卖抢单系统的性能和用户体验(MQ异步处理订单,以提高外卖抢单系统的性能和用户体验) ???
- 39.5 优化外卖抢单系统的性能和用户体验 (使用Redis缓存热点数据,以提高外卖抢单系统的性能和用户体验)
- 40.5 如何提高系统性能和用户体验(使用缓存技术或异步处理或负载均衡等手段)