ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于微信小程序的马拉松报名系统设计与实现

基于微信小程序的马拉松报名系统设计与实现 马拉松报名这种场景放在微信小程序里做其实非常典型。跑团需要报名、赛事方需要管理报名数据、选手要查号码簿和比赛信息一个报名系统要同时面对这三类角色。我最近刚陪一个做毕设的朋友把这个项目从0到1完整走了一遍从需求梳理到数据库设计从前端页面到后端接口中间踩了不少坑也总结出一套可以复用的实现思路。这篇就围绕“基于微信小程序的马拉松报名系统”这个题目把项目里真正值得花时间的部分拆开讲清楚产品流程、表结构、支付对接、号码簿生成、论文写法以及那些不实际做一遍根本发现不了的问题。如果你是计算机相关专业的学生正准备做这类“小程序报名”方向的毕设或课设这篇可以直接当项目实施参考。如果只是对小程序开发感兴趣想了解一个完整业务系统长什么样也可以看到从用户点击报名到订单支付的完整链路。1. 报名系统到底在解决什么问题先说业务。马拉松报名和普通的电商下单有本质区别它卖的不是实物商品而是一个参赛名额还附带参赛项目选择、参赛者信息填报、支付、号码簿分配、报名数据统计这一整条链路。这就决定了系统必须有两个核心身份普通跑者和赛事管理员。1.1 业务角色与基本流程梳理普通跑者端的典型流程是打开小程序看赛事列表进入赛事详情页了解比赛时间、地点、项目和费用选择自己报名的项目填写姓名、身份证号、手机号、紧急联系人、衣服尺码等信息提交订单并支付支付成功后在小程序里查看报名状态和号码簿。管理员端则是另一套逻辑创建赛事、配置参赛项目全马、半马、欢乐跑、维护赛事介绍和公告、查看报名人数、导出报名名单、发布比赛相关通知。把这两个角色的流程画出来整个系统的主线就清楚了。后端核心是处理“报名订单”这条数据流前端核心是让跑者用尽可能少的步骤完成报名。做需求分析时最容易漏掉的是“名额限制”和“未支付订单超时释放”这两个隐性需求后面我会专门讲实现方案。1.2 功能模块划分与小程序页面地图我最终把功能分成四个模块赛事模块展示赛事列表、赛事详情、参赛项目列表支持分页加载和赛事公告。报名模块报名表单、参赛项目选择、尺码选择、订单创建、在线支付。订单模块订单列表、订单详情、支付状态、取消订单、号码簿查看。个人中心模块用户信息展示、头像昵称修改、我的报名记录、关于赛事的信息。对应的微信小程序页面结构大致是pages/index/index赛事列表首页pages/event/detail赛事详情页pages/apply/apply报名表单页pages/order/list我的报名记录列表pages/order/detail订单详情页pages/user/user个人中心页页面不多但每个页面之间的数据流转关系需要注意。比如赛事详情页要能直接跳转报名页并把赛事ID和项目ID带过去报名成功后要能跳转订单详情页而不是简单弹一个“报名成功”的提示。这些交互细节做得顺畅整体体验才会加分。1.3 技术选型我为什么选原生小程序而不是uni-app现在做微信小程序绕不开一个选择用原生开发者工具写还是用uni-app这类跨端框架。很多项目都会在这上面纠结。uni-app的优势是“一套代码多端复用”同一套代码可以编译到微信小程序、支付宝小程序还能打包成Android和iOS的App。如果你的项目有后续多端上线的计划选uni-app是合理的。但代价是调试链路变长很多微信原生能力比如某些隐私接口、实时日志需要通过条件编译去兼容遇到问题排查成本会高不少。我做这个项目用的是原生小程序开发。原因很简单这个项目只看微信一个端用原生框架能直接吃到微信开发者工具的全部调试能力和最新的API支持比如头像昵称填写能力、wx.requestPayment支付、onReachBottom分页加载等。更关键的是项目的论文里在写“技术选型”时原生开发能给出更直接的论据不需要引入额外框架开发调试路径短后续维护成本低。如果是在校生做毕设我更建议选原生。答辩时老师大概率会问“为什么不用uni-app”这时候可以回答uniapp适合跨端需求本项目只面向微信生态采用原生开发可以充分利用微信提供的原生能力和调试工具降低依赖复杂度。这个回答既客观又能体现你的思考。2. 数据库设计与核心接口约定数据库设计决定这个项目能做多深。很多人的报名系统做出来像“玩具项目”很大程度上是因为表设计太简单只有一张用户表加一张订单表赛事信息写死在页面里。但一个真正能用的马拉松报名系统赛事、参赛项目、订单必须是分表的还要考虑号码簿、公告这些扩展能力。2.1 核心数据表设计我最终设计了六张核心表用户表、赛事表、参赛项目表、报名订单表、号码簿表、公告表。用户表主要字段字段类型说明idbigint主键openidvarchar微信openid唯一nicknamevarchar用户昵称avatarvarchar头像URLmobilevarchar手机号created_atdatetime创建时间updated_atdatetime更新时间openid是用户在小程序体系里的唯一身份标识后端登录时通过wx.login拿到code再调用微信的接口换成openid。这里有个容易被坑的地方一个微信用户在不同小程序里openid不同如果后续要跨小程序共享用户数据需要引入unionid普通报名场景用openid就够了。赛事表和参赛项目表是我特别想强调的部分因为很多第一次做的人会把“马拉松赛事”和“参赛项目”混在一起直接在赛事表里放一个“全程马拉松”“半程马拉松”的字符串字段。这会导致后面按项目统计报名人数、按项目分配号码簿时非常痛苦。我的做法是拆成两张表。赛事表存比赛的基本信息比赛名称、封面图、介绍、举办时间、举办地点、赛事状态、总名额限制。参赛项目表存每个赛事下的具体项目与赛事是主从关系字段包括项目名称全马/半马/欢乐跑、距离、报名费、报名开始时间、报名截止时间、项目名额限制、当前已报名人数。订单表的字段是重头戏直接关系到支付流程能否走通。我的订单表核心字段包括订单号、用户ID、赛事ID、参赛项目ID、报名联系人姓名、身份证号、手机号、紧急联系人、衣服尺码、订单状态、报名费金额单位分、支付时间、创建时间、更新时间。这里强调一个字段设计细节金额一律用“分”存储用整数类型。很多新手用decimal(10,2)存微信支付的金额结果回调金额比较时因为精度问题对不上很折磨人。微信支付的金额单位本身就是分后台存储保持一致转成元做展示就行。2.2 报名订单状态机订单状态是整个系统的核心每个状态转换都要有明确触发条件。我的状态定义如下PENDING_PAY待支付。用户提交报名信息后生成。PAID已支付。支付回调成功后进入。CANCELLED已取消。用户主动取消或超时未支付自动取消。REFUNDING退款中。用户需要退赛时申请。REFUNDED已退款。退款完成后进入。状态机图如果用文字描述就是待支付可以走向已支付和已取消已支付可以走向退款中退款中最终走向已退款。已退款和已取消都是终态不允许再回到可支付状态。业务逻辑上要特别注意“超时未支付释放名额”这个动作。用户在报名页填完信息、提交订单后如果一直不支付订单不能一直占着名额。常用的策略是15分钟或30分钟未支付自动取消订单并释放报名名额。这个动作可以用后端定时任务扫描实现后面我会讲具体实现。2.3 后端核心接口设计后端接口采用RESTful风格返回统一的JSON结构格式大致是{code, message, data}。接口按模块划分赛事模块GET /api/event/list赛事分页列表入参page和sizeGET /api/event/{id}赛事详情同时返回该赛事下的参赛项目列表报名模块POST /api/order/create创建报名订单POST /api/pay/params/{orderNo}获取微信支付参数GET /api/order/detail/{orderNo}订单详情GET /api/order/list我的报名订单列表POST /api/order/cancel取消订单用户模块GET /api/user/info获取用户信息PUT /api/user/info更新用户信息号码簿模块GET /api/user/bib/{orderNo}查看订单对应号码簿接口设计里有个关键点需要用户登录态的接口后端在请求头里约定一个Authorization字段存放登录凭证。小程序端每次请求时带上这个凭证后端通过拦截器统一校验。不要在每个接口里去判断登录状态那样代码会散得到处都是。3. 小程序端核心功能的实现细节小程序端的实现质量直接影响用户对系统的第一印象。这一节挑几个最有代表性的功能点展开都是可以真正落地到代码里的经验。3.1 赛事列表分页加载与缓存策略赛事列表页是用户打开小程序看到的第一屏需要同时处理两个问题列表数据量大了之后不能一次性加载全部以及用户反复进入页面时不能每次都重新请求接口。分页加载用小程序原生的onReachBottom配合页码实现。初始page1每次请求返回数据和hasMore标记。当页面滚动到底部触发onReachBottom时判断hasMore且当前不在加载中就把page1再去请求。这里有个小细节请求期间要加一个loading标志防止用户快速滚动时重复触发请求。等请求回来再拼接到原有列表后面。缓存策略上我对赛事列表做了5分钟缓存。缓存的实现不是简单的wx.setStorageSync存数据而是存一个带过期时间的对象const CACHE_KEY event_list_cache const EXPIRE_TIME 5 * 60 * 1000 function getEventListCache() { const cache wx.getStorageSync(CACHE_KEY) if (!cache || !cache.expireTime) return null if (Date.now() cache.expireTime) { wx.removeStorageSync(CACHE_KEY) return null } return cache.data } function setEventListCache(data) { wx.setStorageSync(CACHE_KEY, { data, expireTime: Date.now() EXPIRE_TIME }) }这个做法在论文里能当一个小亮点写上通过本地缓存减少不必要的网络请求优化用户体验。3.2 报名表单信息校验与尺码选择报名表单是收集用户信息的关键页面。马拉松报名必须收集的信息包括姓名、身份证号、手机号、紧急联系人、参赛项目、衣服尺码。我还在表单里加了“本人已阅读并同意《参赛声明》”的勾选这个是线下赛事报名的基本要求。身份证号校验这里要提一句不要只做正则的长度和格式判断真正有校验能力的是身份证第18位校验码算法。简单说就是前17位乘上对应权重求和再对11取模得到校验码。这个算法网上有公开的标准实现用一小段函数就能校验身份证号真伪。答辩时如果老师问起身份证校验怎么做能说出这个算法是很大的加分项。参赛项目在报名页用radio-group实现每个项目显示名称、距离和价格。衣服尺码用按钮组实现让用户点选。这里有个交互细节进入页面时先通过赛事详情接口把项目列表拉下来缓存到页面data里用户选好项目后价格自动联动显示。注意不能把项目列表写死在代码里因为不同赛事的项目配置完全不同。在用户点击“提交订单”时前端要做一次完整的表单校验。校验规则至少包括姓名不能为空、身份证号格式正确、手机号符合11位规则、紧急联系人和手机号不能为空、已勾选同意声明。校验通过后再调用创建订单接口。后端接口里也要再校验一遍因为前端校验可以被绕过后端必须作为安全边界。3.3 支付流程从下单到支付回调支付是整个系统里最容易被卡住的环节。完整流程是这样的第一步用户在前端提交报名信息前端调用POST /api/order/create后端生成订单状态为PENDING_PAY返回订单号。第二步前端拿到订单号后请求POST /api/pay/params/{orderNo}。后端调用微信支付的“统一下单”接口传入appid、商户号、openid、订单号、金额单位分、商品描述、回调通知地址等参数。微信返回prepay_id后后端再按微信规范生成二次签名把timeStamp、nonceStr、package、signType、paySign这几个参数返回给前端。第三步前端拿到参数后调用wx.requestPayment拉起收银台。第四步用户支付成功后微信服务器会异步通知你配置的回调地址。后端在回调接口里对签名做验签验签通过后更新订单状态为PAID同时在同一个事务里生成号码簿记录。这里有一个极容易踩坑的点支付回调不是只通知一次微信会重试多次而且回调的到达顺序不一定和用户支付顺序一致。所以回调处理必须是幂等的。我的实现方式是在回调里先查订单当前状态如果已经是PAID就直接返回成功不再重复更新。还可以在订单表上加一个唯一索引来兜底防止并发重复更新。另一个容易踩坑的点是前端不能用wx.requestPayment的返回值判断支付结果因为用户可以在收银台里直接关闭页面这时候requestPayment会报错但订单可能已经支付成功了。正确的做法是支付完成后前端主动跳到订单详情页由后端根据订单状态决定展示“待支付”还是“已支付”。我在项目里做的是支付回调发起后延迟一秒前端轮询订单详情接口拿到最新状态再刷新页面体验比较稳。3.4 我的报名订单列表与号码簿展示“我的报名”本质上是一个订单列表按时间倒序展示用户的所有报名记录。每条记录显示赛事名称、参赛项目、订单状态、报名费。点击可以进入订单详情页。订单详情页除了显示订单信息外还有一个关键功能已支付订单要展示号码簿。号码簿样式模拟线下马博会领取的参赛号码布上面显示选手姓名、参赛项目和号码。号码的生成逻辑放在后端生成时机是支付成功的回调里这样可以保证号码在用户支付成功的瞬间就已经分配好用户刷新订单详情就能看到。订单状态在页面上的展示需要做状态文案映射。比如待支付显示“去支付”按钮已支付显示“已报名”状态标签已取消显示“已取消”。这里不要在前端写死状态判断可以把状态码和文案的映射关系放在一个统一的工具文件里后面维护起来省事。4. 后端能力与管理员侧实现很多毕设项目把后端写成“接口转发器”这是很吃亏的。一个报名系统如果要有竞争力必须在后端体现业务逻辑比如名额管理、订单超时处理、号码簿生成、报名数据统计。这些都可以成为论文里的核心章节。4.1 后端工程结构与技术选择我个人做这个项目用的后端是Spring Boot MyBatis-Plus数据库是MySQLRedis用作缓存和分布式锁。微信支付相关逻辑用官方SDK封装成WechatPayService。工程目录按模块分包src/main/java/com/example/marathon ├── controller // 接口层 ├── service // 业务逻辑层 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 请求和响应对象 ├── config // 全局配置、微信支付配置 ├── common // 统一返回、异常处理 ├── task // 定时任务 └── utils // 工具类分包清晰至少有两个好处一是写论文时可以画工程架构图二是改代码时不会出现“所有代码都在一个类里”的窘境。4.2 心血管级并发名额扣减与超时释放马拉松报名有一个很现实的并发问题热门赛事名额有限大量跑者同时报名系统不能超卖。常规做法有两种。第一种是数据库乐观锁思路在参赛项目表上维护一个current_enrolled字段更新时带上名额判断UPDATE event_item SET current_enrolled current_enrolled 1 WHERE id #{itemId} AND current_enrolled max_enrolled如果更新返回的影响行数为1说明扣减成功如果影响行数为0说明名额已经被抢完。这个SQL本身就是原子操作不需要额外加锁。第二种是Redis Lua脚本扣减适合更大并发场景。但一个毕设项目用数据库的原子更新其实已经足够了把current_enrolled max_enrolled这个条件写在SQL里是性价比最高的方案。我在代码实现里用的就是第一种。超时未支付释放名额的做法是订单表在创建时记录expire_time字段值等于当前时间加15分钟。后端用定时任务每两分钟扫描一次找出所有状态为PENDING_PAY且expire_time小于当前时间的订单将其置为CANCELLED同时把参赛项目的current_enrolled扣回去。扣回去的SQL是反向操作UPDATE event_item SET current_enrolled current_enrolled - 1 WHERE id #{itemId} AND current_enrolled 0这里要注意释放名额和取消订单需要放在同一个事务里避免出现订单取消了但名额没释放的数据不一致问题。4.3 号码簿生成规则号码簿是一个有仪式感的功能实现起来也不复杂。我的生成规则是赛事编码 项目代码 四位序列号。比如赛事编码是M2025全马项目代码是A第一位通过报名的全马选手号码就是M2025A0001。具体实现是在支付回调成功的事务里查出当前项目和赛事的编码信息查该项目下已有的报名人数然后加1生成新的号码。号码生成后写入号码簿表以后用户查询直接读这张表就行。号码簿还有一个细节在生成时顺带生成一个二维码链接内容是查号码的页面地址。线下赛事时工作人员扫码可以核对选手信息。这个功能可以写进论文作为系统亮点比单纯的“增删改查”项目高级不少。4.4 管理员统计与导出管理员端的核心价值是数据统计和导出。统计包括总报名人数、各项目报名人数、每日新增报名趋势、订单支付率。导出功能可以把报名名单导出为Excel方便赛事方线下使用字段包括姓名、身份证号、手机号、紧急联系人、参赛项目、衣服尺码、号码簿号码。导出用EasyExcel或者POI都可以。实现逻辑很简单查询订单列表把数据映射成Excel的每一行输出到流。关键是导出条件要给够管理员可以按赛事、按项目、按支付状态筛选后导出。5. 论文怎么写才像“自己的”项目源码做完以后论文说明是另一个大头。很多人代码跑通了论文却写得像“说明书”或者“百度百科词条”答辩直接翻车。这里分享一些写论文的经验。5.1 论文的章节框架我建议按这个结构写第一章绪论。写选题背景和意义、国内外研究现状、主要研究内容和工作安排。注意“研究现状”一定要结合具体文献写不要空谈。第二章相关技术介绍。介绍微信小程序、Spring Boot、MySQL、微信支付相关技术但要结合本项目说明为什么用这些技术不要写成教科书。第三章需求分析。写业务需求、功能需求、非功能需求配上用例图。第四章系统设计。写总体架构设计、功能模块设计、数据库设计、界面设计。数据库设计要包含ER图和核心表结构。第五章系统实现。写客户端各功能模块的实现、服务端的实现、微信支付集成。这一章要配界面截图和核心代码片段。第六章系统测试。写测试环境、功能测试用例、测试结果分析。最后一章总结与展望。总结项目成果和不足展望未来可以扩展的功能比如人脸识别检录入场、成绩实时查询等。这个框架是标准的软件工程论文框架老师看了不会挑大问题。5.2 图表和代码排版经验论文里图的力量远大于文字。你至少需要准备这些图系统总体架构图、功能模块图、业务流程图报名流程、数据库ER图、支付时序图、每个核心页面的截图。这些图全部自己画、自己截图别从网上随便找。时序图这里不用专业的绘图工具画在Word里用文本框和箭头就能完成。支付时序图要着重表现用户、小程序前端、后端服务、微信支付平台四个角色之间的消息传递顺序。这张图画好了答辩时讲支付流程就非常省力。代码部分不要大段贴在描述核心算法、关键业务逻辑时贴10到20行就够了完整源码可以放附录。特别注意排版时中文标点的一致性以及代码块的字号和等宽字体设置。很多老师会直接翻论文排版格式不统一的印象分很低。6. 常见问题与避坑记录做这个项目时踩了不少坑这里整理一个排查手册供你直接参考。6.1 真机调试、合法域名与HTTPS问题小程序开发工具里请求接口默认会报“url not in domain list”之类的错误。本地开发阶段可以在开发者工具右上角“详情-本地设置”里勾选“不校验合法域名”这样能用HTTP的本地IP调试。但真机预览时如果不校验会连不上而且一旦正式上线必须配置登录微信公众平台在小程序后台的“开发管理-开发设置-服务器域名”里配置request合法域名域名必须是已备案的HTTPS域名。我遇到过一种情况配置了合法域名但真机还是请求失败排查后发现是SSL证书是自签发的微信不认。正式环境一定要用正规CA机构签发的证书云厂商提供的免费证书也够用。6.2 微信支付相关的问题最经典的坑有三个。第一个是金额单位。微信支付接口里所有金额都是“分”比如报名费50元传参时是5000不是0.01。我在联调时就因为前端传了元、后端按分处理导致金额少了100倍还好及时发现。第二个是回调处理不幂等。回调接口被微信多次调用后如果每次都把状态从“已支付”变成“已完成”页面状态会错乱。实现里必须加状态判断。第三个是微信支付要求商户号要求小程序主体是企业或个体工商户。个人主体的小程序没法直接开通微信支付。毕设阶段可以做一个“模拟支付”开关后端配置里增加mockPaytrue当处于模拟模式时前端不调wx.requestPayment而是直接请求接口把订单置为已支付。这个开关保留在代码里答辩演示时也不会尴尬。6.3 小程序审核与类目如果这个系统真的要上线要提前确认小程序的类目。运动和赛事报名通常对应“生活服务-体育”类目可能需要提供相应的资质文件。审核时特别容易被拒的内容包括诱导分享比如分享得奖品、收集身份证号但没有隐私保护说明。解决办法是在小程序后台填写用户隐私保护指引声明收集身份证号、手机号等信息的用途并在用户首次使用时弹出隐私授权弹窗。还有年审的问题。认证的小程序每年需要做一次年审认证费用一般是300元。如果你只是做毕设不需要管这些但论文里如果写上“系统上线需要考虑合规认证与年审”会显得你考虑得很全面。6.4 报名并发下的数据一致性第4节讲过名额扣减SQL这里再补充一个真实场景。我在测试时用两台手机同时提交最后一个名额结果两个订单都显示待支付但SQL扣减只成功一个。这说明“创建订单成功”和“名额扣减成功”必须放在同一个后端事务里。正确流程是后端收到创建订单请求时先执行名额扣减SQL扣减成功再插入订单记录整个操作在Transactional事务内。如果扣减失败直接返回“名额已满”不生成订单。6.5 小程序端的体验细节坑几个看起来不起眼但影响体验的细节自定义顶部导航栏时状态栏高度获取接口已从wx.getSystemInfoSync逐步迁移到wx.getWindowInfo旧接口在基础库新版本上会告警。报名表单里的单选框点击区域要大避免用户误触。列表加载更多时在底部放一个“加载中”提示没有更多数据时显示“没有更多了”不然用户会一直往下滑。我个人在实际操作中的体会是做这类“小程序报名支付”的项目最值得先花时间的是把订单状态机和数据库字段定清楚。状态机理清楚前后端联调会顺利很多字段设计好后面做统计和导出都不会返工。还有一个容易被忽略的点身份证号是敏感信息数据库里不能明文存储至少要做加密答辩时把这个点讲出来老师会认为你有安全意识。最后给个特别实在的建议代码从第一天开始就放到Git仓库里随时提交我见过不止一个同学在答辩前一周发现代码没了那种绝望是真的不想再体验第二次。
返回列表