ARTICLE DETAIL

资讯详情

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

基于Python+uniapp的微信小程序汉服租赁平台开发实践

基于Python+uniapp的微信小程序汉服租赁平台开发实践 这两年穿汉服出门的人肉眼可见地多了节假日公园里全是披着斗篷、提着裙摆的姑娘小伙。但租衣服这件事线下汉服馆的体验依然停留在几年前排期靠纸笔记押金靠口头约定档期撞车了只能来回打电话协调。我接这个“基于微信小程序的汉服服装租赁平台”项目时第一反应就是——这玩意儿太适合小程序了。低频但有明确需求的使用场景用户扫开就能下单还完就离开根本不需要为了租衣服专门装一个App。整个项目最终落地成三条线后端用 Python 写接口前端用 uniapp 写跨端页面最终编译发布到微信小程序。这篇文章就把从技术选型、需求梳理、后端到前端、最后上线的完整过程讲一遍适合正在做毕设的计算机专业学生、想给线下门店做线上化的店主以及想用一套代码练全栈的开发者。1. 为什么这个项目我坚持Python写后端、uniapp写前端技术选型不是越高级越好而是匹配项目体量。汉服租赁平台的核心是“管理服装、管理订单、处理押金”本质是一套带状态流转的业务系统没有高并发、没有海量数据对性能的要求很低。这个前提直接决定了选型方向。1.1 后端选Python不是偷懒是匹配业务迭代速度后端框架我在 Flask 和 Django 之间犹豫过一阵最终选了 Flask SQLAlchemy PyMySQL 这套组合。原因很简单Flask 足够轻量项目结构完全由自己掌控路由、模型、服务层怎么组织一目了然。Django 自带 Admin 后台确实诱人首次接触 ORM、迁移、中间件这些概念时容易绕进去而且 Django 自带的东西很多我们用不到反而是负担。Python 版本用的 3.10依赖隔离用 venvrequirements.txt 锁住 Django 爸爸的包就行。对比一下后端方案的取舍方案上手曲线后台管理异步支持对这个项目的适配度Flask平缓需自己搭可加适合代码透明Django较陡自带Admin需改造也行但偏重FastAPI平缓需自己搭原生异步适合生态较年轻有人会问为什么不用 Java 或者 NodeJava 在这个业务体量下配置和部署的成本都偏重Spring Boot 一套初始化项目就得加载一堆依赖。Node 写接口本身没问题但团队里熟悉 Python 的成员更多而且 Python 在微信支付、OSS 上传这些场景下都有非常成熟的 SDK拿来就能用。技术选型是团队协作的产物不是技术秀。1.2 前端选uniapp一份代码、五个端真正省下的时间前端用 uniapp 的理由只有一个核心一份 Vue 语法的代码能同时编译到微信小程序、H5、App。对租赁平台这种重业务、轻交互的项目跨端能力直接省掉一半工作量。很多人纠结要不要用微信原生小程序开发我的看法是原生小程序适合产品形态已经很稳定、团队有专职小程序开发的情况。但独立开发或者小组作战时uniapp 的效率优势很明显。比如页面里的轮播图、宫格导航、时间选择器、表单校验用 uview-plus 组件库直接拖进来就能用。uview-plus 在 HBuilderX 插件市场里一键导入然后在 pages.json 里配置 easycom 规则页面模板里就能直接写组件标签不用手动 import。uniapp 编译到微信小程序后底层产物还是 WXML微信原生 API 通过uni.xxx封装后照常调用同时也支持条件编译处理各端差异。需要注意的一点uniapp 并不能完全屏蔽端差异导航栏高度、分享参数、支付回调这些地方仍然要做小程序端特判。但对租赁平台这种没有复杂原生功能的项目适配成本很低。1.3 微信小程序端起量快、转化顺天然适合线下场景为什么最终落地端落在微信小程序而不是 H5 或者独立 App因为汉服租赁是典型“低频刚需”业务用户一年可能就租三五次让他下载一个 App 几乎不可能。小程序扫开即用还完就走使用成本极低。更关键的是微信生态给的完整闭环微信登录直接拿到用户身份微信支付完成租金和押金结算订阅消息发送归还提醒。线下汉服馆的典型场景是店门口立一个二维码牌子用户扫一下就能查看店内可租的服装和档期不需要关注公众号、不需要注册账号密码。这个转化路径比任何投放渠道都直接。2. 汉服租赁业务的流程图背后需求拆解比写代码更重要很多第一次做项目的同学上来就建表、写接口做到一半发现订单状态对不上、押金退不了、库存超卖了回头来改数据结构整个推倒重来。我的经验是业务需求拆解先于代码尤其是订单状态机必须在一开始就定义清楚。2.1 核心是一个订单状态机不是一堆页面汉服租赁平台有两个角色用户和管理员门店。页面看起来不少但所有逻辑都围绕订单状态机展开。我最终定义的订单状态如下状态含义可流转到触发动作待付款下单成功但未支付已取消、待发货支付、超时自动取消已取消用户取消或超时未付无释放库存待发货/待取货支付完成等待商家发货或到店取衣租赁中商家确认、用户取衣租赁中租期开始待归还用户提交归还待归还验证用户已归还等待商家验收已完成、租赁中商家确认验收、验收不合格退回已完成验收通过、押金已退无自动或手动退押金为什么“租赁中”不能直接跳到“已完成”因为汉服需要检查污渍、破损、超时没有验收环节的话后续纠纷完全无法追溯。哪怕线下门店当面交接也要拍照留档线上化之后这个步骤更不能省。2.2 汉服租赁特有的几个坑押金、租期、尺码、库存这些是普通电商平台不会遇到的规则也是这个项目的难点所在。第一是押金怎么收。很多第一次做的人想用微信支付的“冻结金额”功能实际上普通商户很难申请到预授权接口。实操中最常见的方案是下单时把“租金押金”合为一笔订单支付归还验收通过后再用微信支付退款接口把押金原路退回。订单表里必须同时记录rent_fee和deposit两个字段退款时才知道退多少、扣多少。第二是租期怎么算。汉服租赁不是按“租几天”笼统算而是要精确到小时。订单记录start_time和expect_return_time比如周六上午10点取衣周一上午10点前必须归还。超时费规则也要提前定好我采用的是超过约定时间6小时以内不计费超过6小时按一天加收日租金之后每满24小时再加一天。第三是尺码问题。汉服版型偏差比日常服装大得多同一个尺码在不同商家的版型上能差出一个号。解决方法是详情页做身高体重尺码对照表下单备注栏允许用户填身高体重店主根据实际情况在后台备注建议尺码。评价模块也鼓励用户上传上身图帮后来人做选择参考。第四是库存锁定。同一款衣服可能只有一两件下单必须锁库存支付成功才真正占用库存超时未支付要自动释放。我用stock和locked_stock两个字段配合解决下文会写具体实现。2.3 我最后定的模块清单与页面地图对照原始需求我最终砍掉了购物车。原因很直接汉服租赁不是多选凑单的电商场景用户一次基本只租一套在详情页选定租期后直接下单更顺畅。购物车只是增加一次点击却要多维护一张表、一个页面没有实际价值。最终模块清单用户与登录、服装分类、服装管理、订单与租期、归还验收、押金退款、评价、公告。小程序端页面包括首页、分类列表、服装详情、下单确认、订单列表、订单详情、个人中心底部 TabBar 用四个首页、分类、订单、我的。管理端我没有单独做 App而是用 Flask 的 Jinja2 模板搭了一个简易后台功能只有三个服装上下架、订单发货/验收、押金退款处理。管理员大概率是门店老板在电脑上操作比在手机上快得多。3. 后端API和数据表把“租期、押金、库存”变成可运行的逻辑业务规则理清之后后端反而是最顺手的部分。核心就是几张表、一组接口和一个状态机。3.1 数据库表设计核心五张表数据库我用 MySQL 8.0字符集统一utf8mb4别问为什么用这个等你存用户昵称里出现表情符号乱码的时候就知道疼了。用户表useropenid唯一索引存微信登录凭证session_key只在需要解密手机号时用不建议明文长期存储可以放缓存nickname、avatar从微信头像昵称填写能力拿。分类表categoryname、sort排序字段控制首页展示顺序这个表很小但别省。服装表dresscategory_id、name、cover、images、size、color、daily_price、deposit、stock、locked_stock、status、description。cover和images分开存列表页只需要一张封面详情页要相册图片 URL 存数据库图片文件放 OSS不要打进小程序包里。订单表orderorder_sn唯一业务单号、user_id、dress_id、dress_name、cover、start_time、expect_return_time、actual_return_time、rent_fee、deposit、penalty、status、pay_time、refund_time、remark。字段里冗余dress_name和cover是个容易被忽略的点如果服装之后下架、改名历史订单仍然能正确显示不会变成一堆残缺记录。评价表evaluationorder_id唯一约束保证一处订单只能评价一次user_id、content、images、star。没完成的订单不允许评价规则在后端判断。3.2 核心接口与关键实现接口设计不是越多越好而是每个接口对应一个业务动作。核心接口清单如下接口方法说明/api/auth/loginPOSTwx.login 换 code 后换取 openid返回自签 token/api/dressesGET分页、分类筛选、关键字搜索/api/dresses/{id}GET详情、库存、评价列表/api/ordersPOST创建订单锁库存/api/orders/{id}/payPOST微信支付统一下单/api/orders/{id}/cancelPOST取消订单释放库存/api/orders/{id}/returnPOST提交归还/api/admin/orders/{id}/verifyPOST管理员验收并退押金/api/evaluationsPOST/GET提交、查看评价创建订单时库存扣减是整个并发安全的关键。不能先查出库存再在代码里判断够不够那样两个用户同时下单会把同一件衣服卖出去。正确做法是直接执行原子 SQL# Flask SQLAlchemy 创建订单并锁库存 order_bp.route(/api/orders, methods[POST]) def create_order(): data request.get_json() dress_id data.get(dress_id) user_id g.user_id start_time datetime.fromisoformat(data.get(start_time)) expect_return_time datetime.fromisoformat(data.get(expect_return_time)) # 原子扣减可用库存 result db.session.execute( text(UPDATE dress SET locked_stock locked_stock 1 WHERE id :id AND stock - locked_stock 0), {id: dress_id} ) db.session.commit() if result.rowcount 0: return jsonify(code400, msg库存不足) days calc_days(start_time, expect_return_time) rent_fee Decimal(dress.daily_price) * days deposit Decimal(dress.deposit) order Order( order_sngenerate_order_sn(), user_iduser_id, dress_iddress_id, dress_namedress.name, coverdress.cover, start_timestart_time, expect_return_timeexpect_return_time, rent_feerent_fee, depositdeposit, statuspending_payment ) db.session.add(order) db.session.commit() return jsonify(code0, dataorder.to_dict())这个WHERE stock - locked_stock 0是行级条件判断数据库本身会保证并发下的原子性比SELECT FOR UPDATE的写法更轻量也不会死锁。超时未支付释放库存时反向执行UPDATE dress SET locked_stock locked_stock - 1 WHERE id :id AND locked_stock 0即可。3.3 押金退回与超时费最容易出错的环节押金退款走微信支付 v3 的退款接口路径是/v3/refund/domestic/refunds需要商户号、证书序列号、私钥。用 requests 直接调import requests def refund_deposit(order, refund_amount): url https://api.mch.weixin.qq.com/v3/refund/domestic/refunds payload { out_trade_no: order.order_sn, out_refund_no: fREFUND_{order.order_sn}, amount: { refund: int(refund_amount * 100), # 单位是分 total: int((order.rent_fee order.deposit) * 100), currency: CNY } } # 构造签名头并请求代码省略 resp requests.post(url, jsonpayload, headersauth_headers) resp_data resp.json() if resp_data.get(status) PROCESSING: order.refund_status pending db.session.commit() return resp_data这里有个非常经典的坑金额单位是分不是元。租一天 128 元传给微信支付必须写成 12800。我见过有人传了128导致用户只退了 1.28 元的线下事故。超时费的计算逻辑也要放在后端统一实现def calc_penalty(expect_return_time, actual_return_time, daily_price): diff actual_return_time - expect_return_time if diff timedelta(hours6): return Decimal(0) days (diff.days 1) if (diff.seconds 0 or diff.days 0) else 0 return Decimal(daily_price) * days验收通过后实际退款金额refund_amount order.deposit - penalty如果押金不够扣超时费就记录欠款线下追收。订单表里要把penalty字段存下来退款之后用户问为什么少退了直接拿数据说话。3.4 定时任务后台的隐形员工订单状态机上有些自动流转是靠定时任务驱动的。我在 Flask 里集成了 APScheduler起了三个任务每 10 分钟扫描超过 15 分钟未支付的订单状态改为已取消恢复锁定库存。每天早上 9 点扫一遍租期即将到期或已经超时的订单通过微信订阅消息提醒用户归还。对退款状态为pending的订单查微信支付结果未成功的隔一段时间重试。定时任务逻辑不复杂但没它系统会漏掉大量状态更新。尤其“超时未支付自动取消”这个行为用户不一定主动取消没有定时扫描的话库存会被长时间占死。4. 前端页面与微信小程序适配从H5原型到真机预览的差异后端稳定之后前端就是典型的 uniapp 开发。这里单独讲一下从 H5 页面调试到小程序真机的差异因为很多问题只在真机上才会暴露。4.1 项目创建与目录规划我用 HBuilderX 创建 uniapp 项目选择 Vue 3 版本。目录结构按惯例走pages放页面、components放公共组件、static放本地静态资源、utils/request.js放请求封装、uni_modules放组件库。pages.json里的tabBar配四个入口navigationBarTitleText设置每页标题。组件库选了 uview-plusHBuilderX 插件市场直接导入然后在pages.json配置 easycom{ easycom: { autoscan: true, custom: { ^u-(.*): uview-plus/components/u-$1/u-$1.vue } } }配置完之后页面里直接写u-button、u-swiper不需要手动 import。这一套组合在开发效率上非常舒服但要注意版本一致我见过有人升级组件库之后表单组件 API 变了页面大面积报错。4.2 request封装token、baseURL 和环境切换小程序里请求后端必须走uni.request封装统一的request.js能省大量重复代码// utils/request.js const BASE_URL https://api.example.com export function request(path, { method GET, data {}, needAuth true } {}) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL path, method, data, header: { Content-Type: application/json, ...(needAuth ? { Authorization: uni.getStorageSync(token) } : {}) }, success: res { if (res.data.code 401) { uni.navigateTo({ url: /pages/login/login }) return } resolve(res.data) }, fail: reject }) }) }开发阶段的 baseURL 是个大坑。小程序开发者工具里可以勾选“不校验合法域名”用局域网 IP 访问 Flask 服务一旦切到真机预览localhost就失效了必须换成电脑的局域网 IP。发布上线前再切到正式 HTTPS 域名并把开发者工具的“不校验合法域名”关掉防止线上出现请求直接失败的情况。4.3 列表分页与核心页面交互首页结构是轮播图 分类宫格 推荐服装列表数据从/api/dresses接口拉取。列表页的分页加载是每个小程序都会遇到的功能核心在于状态控制let page 1 let hasMore true let loading false async function loadList(reset false) { if (loading || (!reset !hasMore)) return loading true if (reset) { page 1 hasMore true } const res await request(/api/dresses?page${page}) const list res.data.list if (reset) { this.dressList list } else { this.dressList.push(...list) } hasMore list.length 20 page 1 loading false }在页面里用onReachBottom触底时调用loadList(false)用onPullDownRefresh下拉时调用loadList(true)。有个细节如果没有loading标志位用户快速上下滑动会发出大量重复请求接口和页面双双卡死。服装详情页要展示封面大图、轮播图、尺码表、租金押金、租期选择。图片预览用uni.previewImage租期选择用picker的multiSelector模式选起止日期。下单页把rent_fee deposit明细展示出来同时提示超时规则把丑话说在前面能减少很多售后纠纷。订单列表按状态分 Tab每个订单卡片显示状态标签点击进入订单详情。4.4 微信登录、手机号与订阅消息登录流程已经不需要弹窗授权了直接在页面uni.login拿到 code传给后端后端调用微信code2Session接口换 openid返回自签 token前端存进uni.setStorageSync。手机号字段通过button open-typegetPhoneNumber获取需要在小程序后台申请对应权限。订阅消息值得重点做因为汉服租赁天然适合“归还提醒”场景。用户下单后弹一次uni.requestSubscribeMessage同意后后端就能在归还日前一天给他发模板消息。注意一件事小程序的一次性订阅用户点一次只能发一条所以不能只在首页弹一次就完事最好在归还成功、评价完成这些关键时刻再次引导订阅。4.5 H5和小程序端的适配差异最明显的差异是顶部导航栏高度。H5 页面用系统导航栏很统一到小程序就乱了刘海屏、胶囊按钮高度不固定。如果自定义导航栏需要用uni.getSystemInfoSync()拿到状态栏高度再根据胶囊按钮的边界把导航栏高度算出来const systemInfo uni.getSystemInfoSync() const menu uni.getMenuButtonBoundingClientRect() const navHeight menu.bottom (menu.top - systemInfo.statusBarHeight)分享功能也必须自定义在onShareAppMessage里配置title和pathpath 要带参数比如/pages/detail/detail?id12别写成不带 query 的路径否则用户从分享点进来永远看到第一件衣服。涉及微信特有 API 的地方用条件编译// #ifdef MP-WEIXIN包住保持 H5 端也能正常编译。5. 小程序打包上线避坑2MB限制、导航栏与审核边界写完之后最折磨人的不是写代码而是上传发布。第一次打包上传就撞上了经典的source size 2612kb exceed max limit 2mb这一节把排错过程完整讲一遍。5.1 2612KB 超过 2MB 怎么办报错原因很好猜uview-plus 全量组件进了主包加上 static 目录里本地图片占了空间几个页面也全塞在主包里直接把主包撑爆。解决思路是“主包精简 分包加载 资源外置”。第一步把不常访问的页面放进分包。pages.json里配置subPackages{ pages: [ { path: pages/index/index }, { path: pages/category/category }, { path: pages/order/order }, { path: pages/my/my } ], subPackages: [ { root: pagesDress, pages: [ { path: detail/detail }, { path: checkout/checkout }, { path: evaluate/evaluate } ] }, { root: pagesOrder, pages: [ { path: list/list }, { path: detail/detail } ] } ] }主包只保留 TabBar 页面和公共组件分包页面按功能域拆分。第二步把 static 里超过几十 KB 的图片全部传到 OSS数据库存 URL。第三步uview-plus 也支持按需引入只引入用到的组件模块能进一步压缩体积。改完之后我的心跳终于正常了体积从 2.6MB 降到了 1.2MB 左右。5.2 域名、HTTPS 与微信支付商户号小程序 request 合法域名必须是已备案的 HTTPS 域名自签名证书不行。开发阶段可以在开发者工具里勾选“不校验合法域名”来调试但发布前必须在微信公众平台后台配置好正式域名。我当时因为忘配域名真机扫码打开全是请求超时排查了大半天才发现是域名没加白名单。微信支付需要在服务商申请商户号。这里提醒一下资质问题个人主体的小程序基本做不了微信支付至少需要个体工商户资质平台类目还要额外提供资质证明。商户号申请下来后后端统一下单接口返回prepay_id前端调uni.requestPayment把支付参数传进去。金额单位是分签名算法要用 v3 的 SHA256-RSA别用旧版 v2 的 MD5 签名微信已经逐步淘汰了。5.3 审核被拒的常见理由与应对小程序审核被拒基本是这几个原因提前准备能省好几轮提审类目不符合。服装租赁可以选“生活服务 租赁”或“电商平台”不同类目要求不同资质。个人主体很多类目直接不给过建议从一开始就用企业或个体工商户主体注册。审核人员无法体验完整流程。提供测试账号或者在备注里写明完整的操作路径如果有微信支付环节准备一个可用的测试金额说明。隐私协议缺失。小程序后台必须配置《用户隐私保护指引》页面首次启动弹隐私协议弹窗用户同意之后才能调uni.login、getPhoneNumber这类接口。这个现在审核必查别等驳回再补。素材版权。服装图片、详情页 banner 不使用网络搬运图用商家自己拍的实物图或购买版权的图。汉服圈的图片版权纠纷特别多一旦被举报下架更麻烦。明显 bug 和空状态。审核人员会真实点一遍每个页面接口报错、白屏、无数据状态不处理直接打回。每个列表页都做好空态展示至少不会因为难看被拒。5.4 上线后的运维清单发布不等于结束。Flask 后端用logging模块把关键动作全打日志支付回调、退款请求、订单状态变化每一条都要有时间戳和订单号。MySQL 的慢查询日志开着一个月看一次给order.user_id、order.status、dress.category_id这些高频查询字段加索引。小程序后台自带的错误监控打开uni.request的 fail 回调里把错误堆栈上报。版本更新也不要骚操作微信小程序没有热更新通道有违规风险老老实实走体验版→正式版提审流程。体验版先发给店主实测一遍没问题再提审正式版。最后再分享一个我个人的体会做完这个项目后回头想技术上的难点并不在 Python、uniapp、微信小程序这些工具本身而在于订单状态机、押金退款策略、库存一致性这些业务规则的严密程度。把业务规则写清楚框架和语言都只是顺手工具。如果后面想继续扩展可以考虑接入芝麻信用做免押、支持多门店入驻、把摄影师约拍作为增值服务加入订单但前提一定是先把手上的退款闭环跑得足够稳再谈增长。
返回列表