校内人员聚合支付
更新: 2025/3/23 21:32:41
一、项目背景和目标
- 项目背景:老版聚合支付银行卡圈存慢,小程序刷新功能异常等。
- 项目目标:新增微信圈存功能,修复小程序刷新不了的问题,优化现有界面。
二、项目计划和管理
时间安排:2023 年 9 月------2024 年 6 月
任务分配:完成前后端开发,主要是设计微信支付功能。
项目管理工具和方法:该项目前后端代码统一使用 Git 进行管理,选用 Gitee 为代码仓库
三、需求分析
需求分析:
1.现有的聚合支付小程序只有银行卡圈存功能,选择单一,圈存速度慢,越来越多的用户希望通过微信进行圈存操作,以提高使用本小程序的便利性
2.现有小程序虽然简约,但色彩单一,并不美观,因此需要在保留系统界面简约的前提下,对一些界面进行优化,使得色彩更丰富,突出关键信息
系统设计:包括系统架构设计、数据库设计、API 设计等。
技术选型:说明选择的技术栈和框架(如 React、Node.js、Django)。
四、数据库设计
序号 | 数据表名 | 中文名称 |
---|---|---|
1 | order_info | 正常订单表 |
2 | temporary_order | 临时订单表 |
3 | bill_account | 账单表(对账表) |
4 | total_bill | 总账表 |
5 | error_order | 错误订单表 |
1. order_info
order_info 表为正常订单表,用于存储用户通过微信圈存的订单信息。
2. temporary_order
temporary_order 表为临时订单表,用于用户支付前存储个人信息。
3. bill_account
bill_account 表为账单表,用于存储在微信下载下来的账单信息。
4. total_bill
total_bill 表为总账表,用于存储总交易单数等信息。
5. error_order
error_order 表为错误订单表,用于存储支付成功回调时发现临时订单表不存在该订单等情况。
五、系统功能概述
模块 | 描述 |
---|---|
登录/退出 | 用户登录和退出操作 |
圈存 | 用户可以在选定圈存金额后可以选定圈存方式,包含工商银行圈存和微信圈存,圈存到校园卡的资金可用于二维码消费和日常生活缴费 |
消费 | 展示消费的二维码,可用于食堂或超市扫码消费 |
记录查询 | 查询近 10 笔操作记录,包括消费,圈存,缴费 |
设置消费 | 可以设置日消费额度,如果消费超过额度,需要在 pos 机上输入密码进行验证,也可以开启或关闭免密支付模式 |
查卡余额 | 查询当前校园卡余额 |
门禁扫码 | 展示二维码,可用于寝室门禁系统进行扫码进入寝室 |
卡挂失 | 将校园卡进行挂失 |
寝室缴费 | 设置楼栋号和寝室号和选定充值金额后就可以充值寝室电费 |
六、系统技术选型
1. 原生微信小程序
- 无需安装:用户可以通过微信直接访问小程序,节省下载和安装的时间。
- 轻量化:小程序的加载速度快,用户体验良好。
- 丰富的 API:微信提供了大量的 API,可以方便地实现支付、地图、社交分享等功能。
- 良好的用户触达:可以通过微信消息、群聊、公众号等多种渠道触达用户。
2. Spring Boot
- 快速开发:通过约定优于配置的理念,简化了 Spring 应用的配置。
- 独立运行:可以打包成独立的 JAR 包,内置 Tomcat 服务器,运行时不需要外部的 Web 服务器。
- 易于测试:内置支持多种测试框架,便于单元测试和集成测试。
- 丰富的生态系统:集成了 Spring 全家桶,可以方便地使用 Spring Security、Spring Data 等子项目。
3. Vue.js
- 渐进式框架:可以逐步采用 Vue 的功能,从简单的视图层开发到复杂的单页应用开发。
- 双向数据绑定:通过简单的语法实现数据和视图的同步,开发体验良好。
- 组件化:可以将 UI 分解成独立的、可复用的组件,便于开发和维护。
- 丰富的生态系统:有 Vue Router、Vuex 等配套工具,支持构建复杂的单页应用。
4. WebSocket
- 全双工通信:客户端和服务器之间可以互相发送消息,而无需请求-响应模式。
- 低延迟:数据传输的延迟低,适用于实时应用。
- 节省资源:相比于 HTTP 轮询,WebSocket 的连接数少,节省了带宽和服务器资源。
5. MySQL
- 开源和免费:MySQL 是开源的,并且有丰富的社区支持。
- 高性能:MySQL 在查询处理、事务处理等方面表现出色,适用于高并发的环境。
- 可扩展性:支持大规模数据库,可以方便地扩展和缩放。
- 广泛支持:支持多种操作系统和编程语言,并与多种工具和应用无缝集成。
- 安全性:提供用户管理和权限控制,支持数据加密和传输加密。
- 备份和恢复:提供多种数据备份和恢复的解决方案,确保数据安全。
6. Mina
- 高性能:提供了异步的、事件驱动的网络通信模型,适合处理大量并发连接。
- 灵活性:支持多种传输协议,如 TCP、UDP,可以用于多种网络应用场景。
- 易于使用:提供了简单易用的 API,使开发者能够快速构建网络应用程序。
- 扩展性:通过可插拔的过滤器链机制,可以轻松扩展功能,如加密、压缩等。
七、系统模块介绍
1.登录流程(Token 管理)
当用户进入程序,会调用刷新方法给后端发送 checkLogin 请求,这个请求会被拦截器拦截,
拦截器检验是否有 Token,如果没有则返回 5000,在前端获取 code 发送 againLogin 请求;
如果有 Token,则开始校验 Token,Token 有误:除了发现 Token 中 ryid 有误,其他一律返回 5000,在前端获取 code 发送 againLogin 请求;
如果发现是 ryid 有误,则返回 5500,在前端让用户重新输入账号密码进行登录;
Token 过期:返回 5401,在前端获取 code 发送 againLogin 请求;
Token 正常:拦截器检验完成,进入 checkLogin 业务层,判断 openid 是否为空或者 Token 是否为空
openid 为空或者 Token 为空:返回 5001,在前端让用户重新输入账号密码进行登录;
openid 和 Token 都不为空:继续判断 Token 的已使用时间:
即将过期(Token 有效时间是 2 小时,如果使用时间是 1.5 小时之内都算正常,如果超过 1.5 小时,则返回 5401,通知前端 Token 即将过期,在前端获取 code 发送 againLogin 请求,这个请求的返回结果附带了一块新的令牌,即令牌刷新算法):
Token 使用时间正常:想中间件服务器发送包,将得到的包返回到前端进行缓存。
2.微信支付
1.采用新版Apiv3支付
出现背景:为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,微信推出了全新的微信支付 API v3。
相较于之前的微信支付 API,主要区别是:
- 遵循统一的 Restful 的设计风格
- 使用 JSON 作为数据交互的格式,不再使用 XML
- 使用基于非对称密钥的 SHA256-RSA 的数字签名算法,不再使用 MD5 或 HMAC-SHA256
- 不再要求 HTTPS 客户端证书
- 使用 AES-256-GCM,对回调中的关键信息进行加密保护
v2 与 v3 的区别
V3 规则差异 V2 JSON 参数格式 XML POST、GET 或 DELETE 提交方式 POST AES-256-GCM 加密 回调加密 无需加密 RSA 加密 敏感加密 无需加密 UTF-8 编码方式 UTF-8 非对称密钥 SHA256-RSA 签名方式 MD5 或 HMAC-SHA256
2.聚合支付微信支付流程
3.一卡通体系架构图
八、系统设计亮点
1.数据库锁
遇到的问题:有时候微信回调还没到,页面已经来查询这个订单,这时候发现还没有订单则进行补登操作,进行到插入这个订单时发现微信回调已经到了,并且已经把数据写入数据库了,导致这时候插入同一个数据出问题;
初期解决方法:在插入订单之前会执行查询订单,查询订单的时候对这行数据加锁,使得其他操作必须等到插入成功之后才能执行(事物必须提交或回滚),但是发现如果这行数据还不存在是无法对这行数据进行加锁的;
最终解决方法:在查询订单之前先执行一次查询临时订单的操作,把这个操作放在事物里面,在这个查询的时候加上锁,锁住临时订单表,由于补登也是走这个逻辑,所以补登操作此时必须等到事物提交或回滚,这就使得不会造成重复写入;
2.事物控制
事物的四个特点:原子性,一致性,隔离性,持久性
本系统使用的是 TransactionTemplate 模版
return transactionTemplate.execute(result -> {
TemporaryOrder temporaryOrder = temporaryOrderMapper.getTemporaryOrder(orderInfo.getOutTradeNo());
if(temporaryOrder==null){
log.error("二次查询临时订单表不存在");
return Msg.fail("临时订单不存在");
}
//处理重复的通知
//接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的
OrderInfo order = orderMapper.getOrder(orderInfo.getOutTradeNo());
if (order != null) {
log.info("该订单已存在");
return Msg.success();
}
if (save(orderInfo)) {
int id = orderInfo.getId();
Md5Order md5Order = new Md5Order();
md5Order.setId(id);
md5Order.setTotal(orderInfo.getTotal());
md5Order.setCardId(orderInfo.getCardId());
md5Order.setSalt("aa;'.12");
String concatenatedFields = ConcatenationFieldUtils.concatenateFields(md5Order);
String calculateMD5 = Md5Util.encode(concatenatedFields);
boolean success = update(new UpdateWrapper<OrderInfo>().eq("id", id)
.set("md5", calculateMD5));
if (success) {
log.info("order表数据库md5字段更新成功");
return Msg.success("订单新增成功");
} else {
result.setRollbackOnly();
return Msg.fail("订单更新MD5字段失败");
}
} else {
// 保存失败的处理逻辑
result.setRollbackOnly();
return Msg.fail("订单新增失败");
}
});
代码涉及到数据库的查询,插入,更新操作,因此开启事物,保证全部操作必须一起成功,否则回滚
不考虑隔离性会发生的问题 在实际应用中,数据库的事务有两种,读事务(select),修改事务(update,insert)。在没有事务控制的时候,多个用户同时操作相同的数据时,可能会产生并发问题,基本上归结为 4 种问题。
1.数据丢失:两个更新事务同时修改一条数据时,会造成数据的丢失。
2.脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
3.不可重复读:不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。例如事务 T1 在读取某一数据,而事务 T2 立马修改了这个数据并且提交事务给数据库,事务 T1 再次读取该数据就得到了不同的结果,发送了不可重复读。
4.幻读/虚读:幻读是事务非独立执行时发生的一种现象。例如事务 T1 对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务 T2 又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务 T1 的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务 T2 中添加的,就好像产生幻觉一样,这就是发生了幻读。
3.1 区别
1.不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
2.幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同)。所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数);不可重复读是指读到了已经提交的事务的更改数据(修改),幻读是指读到了其他已经提交事务的新增或删除数据。
3.解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
九、系统架构
1.系统架构图
十、系统部分截图
1.登录页面
2.首页
3.圈存页面
4.二维码消费页面
5.记录查询页面
反思
1.为什么数据库要搞个预支付订单表?
答:预先存放用户的信息,比如 ryid,姓名,因为支付成功后的微信支付回调并不会携带这些信息,
2.那我能不能先把人员信息存到订单表里面,等到回调到达之后再更新这条数据?
答:可以的,但是这样如果用户取消了支付,那这一行数据就出现了空白部分,导致同步信息到中间件不方便
3.为什么要开发微信支付这个功能呢
答:因为工商银行设备过旧,圈存高峰期圈存时速度太慢;
4.为什么要错误订单表
答:因为没有日志系统,这样方便看到错误