资讯详情

Python+Django+uniapp打造微信小程序汉服租赁平台全流程实战

📅 2026/10/4 2:20:09 | 华诺云谱 👁 阅读
Python+Django+uniapp打造微信小程序汉服租赁平台全流程实战
开篇做商城类小程序的教程一抓一大把但租赁类小程序的完整方案其实很少。我刚做完一个基于微信小程序的汉服服装租赁平台技术栈是 Python uniapp 微信小程序整个过程踩了不少坑这篇把从设计到实现、从后端接口到前端页面、从支付回调到发布审核的关键细节一次性写明白。如果你正在做毕业设计、接外包或者想自己搞一个能跑起来的租赁类小程序可以把这篇文章当成一份实操笔记来看。汉服租赁和普通电商商城最大的区别在于电商卖出去就结束了租赁却要在“租期、押金、库存档期、归还、逾期”这些环节上反复折腾。标题里写的“设计与实现”不是套话任何一个环节没想清楚做出来的东西都只能叫演示稿不能叫平台。下面我会以实际项目为例把整条链路拆开讲清楚。1. 项目整体设计与技术选型拆解1.1 汉服租赁平台的需求和电商商城有什么不同先看业务侧。汉服的特点是单价高、使用频次低、穿着场景相对集中比如漫展、毕业照、传统节日活动。所以租赁是比购买更符合用户心理的形态但这也带来三个核心业务问题。第一是“时间段”而不是“数量”。电商库存只要判断还有没有货租赁必须判断某一件汉服在用户选择的日期区间内是否空闲。一件汉服被租出去5月1日到5月3日那5月2日就不能再租给别人但5月4日可以。第二是押金和归还。汉服面料容易勾丝、染色清洗成本也高所以平台必须要押金。订单状态不是付款后就结束而是要经过发货、使用中、归还检查、押金退还甚至逾期罚款这些后续动作。第三是尺码、款式和配饰的组合。汉服不是单纯一件衣服一套可能包括上衣、下裙、披帛还要分尺码。所以数据库不能只建一张简单的“服装表”要把“服装SPU”和“尺码库存SKU”分开设计。我的做法是用户端提供首页推荐、分类筛选、搜索、服装详情、选择租期、购物车、确认订单、在线支付、订单跟踪、个人中心管理端先不单独开发App直接利用Django Admin来实现服装录入、库存档期管理、订单审核、归还登记和押金退放。一个独立开发者完全能撑起这套流程。1.2 为什么选 Python uniapp 微信小程序这个组合这个技术选型我在项目里反复权衡过。先说前端uniapp 是基于 Vue 语法的跨端框架一套代码能编译到微信小程序、H5、Android/iOS App开发语言和开发习惯都更贴近普通前端工程师。单看微信小程序项目原生也可以但如果你后续想上架 App 或做一个 H5 宣传页uniapp 的迁移成本就低得多。后端选 Python主要是考虑开发效率和生态。用 Django Django REST Framework有现成的用户认证、Admin后台、ORM单个开发者在三五天内能把整套管理后台和 API 搭出来。如果用 Java 那一套多人协作工程上更规范但对个人项目和毕业设计来说太笨重。当然 Flask 也很轻适合接口量少的场景只不过最后做管理后台时要自己拼轮子所以我更推荐 Django。微信小程序则是获客和体验权衡后的结果。小程序不用下载安装微信内直接打开天然适合“临时租一套汉服参加活动”这种低频需求。用户不需要为此专门装一个App。最终我的技术栈定为前端uniappVue3语法组件库用 uview-plus编译到微信小程序端后端Python 3.10 Django 4.x Django REST Framework数据库MySQL 8.0字符集 utf8mb4认证微信登录 code2session JWTdjangorestframework-simplejwt文件存储开发环境用本地 media 目录生产环境可以换对象存储这里补充一个经验如果项目只做演示SQLite 也够用但涉及订单并发和档期锁定的时候SQLite 的事务机制远不如 MySQL 可靠所以哪怕本地开发我也建议直接上 MySQL。2. 核心业务逻辑与数据库设计2.1 数据表建模不要把衣服和订单揉在一张表里我设计数据库时踩过最大的坑就是试图把“服装图片、尺码、库存、价格”全部塞进一张表。第一次跑通Demo后每加一个颜色就要复制一整行数据订单引用也很混乱。后来参考电商的 SPU/SKU 思路重新建模整个逻辑就清晰了。核心表我分成这么几张表作用关键字段user小程序用户openid、昵称、手机号、押金信用状态dress汉服商品SPU名称、分类、主图、详情图、描述、每日租金、押金dress_sku尺码颜色库存SKU所属dress、尺码、颜色、总库存dress_sku_stockSKU档期库存SKU、日期、是否可租order订单主表订单号、用户、状态、起租日、结束日、租金、押金、实付金额order_item订单明细SKU、数量、单日租金、小计payment支付流水订单号、支付渠道、支付金额、回调状态address收货/归还地址用户、联系人、电话、详细地址有些教程会把 dress_sku_stock 省略直接去查订单表判断某天是否被占用。这个方案在数据量小的时候没毛病但一旦订单状态复杂待支付、已取消、已归还筛选逻辑会非常痛苦。我建议还是单独维护一张按日期的库存表每天一条记录下单占用、取消释放、归还释放都显式更新这张表。为什么强调“归还后释放”汉服归还之后还要清洗清洗需要时间不能归还当天就立刻释放库存。我实际项目里给服装加了一个“可租状态”字段已下架、可租、清洗中、已租出。归还登记后先进入清洗中后台确认清洗完成再改为可租这样能避免把没洗好的衣服租出去。2.2 租赁状态机与金额计算租赁订单的状态流转我建议设计得比电商更细电商可能只有“待付款、已付款、已发货、已完成”这几个节点租赁至少要包含状态值含义流转条件pending_pay等待支付用户提交订单paid_ready已支付待取衣/待发货支付回调成功renting租赁中用户取衣或管理员确认发货returned已归还待退押金用户归还/管理员登记completed已完成押金已退款cancelled已取消支付前取消或超时未付逾期不用单独做状态而是归还时计算超期费用。比如原定5月3日归还实际5月5日归还超期2天按每日租金的1.5倍补收。这个倍率我在后台配置里做成可调项方便运营人员调整。金额计算逻辑租赁天数 结束日期 - 开始日期按自然日算。例如5月1日到5月3日算2天。租金 SKU单日租金 × 租赁天数 × 数量押金 服装押金 × 数量实付金额 租金 押金押金走“可退余额”不参与服装收入统计这里有个细节日期边界要按照“当天归还当天可再租”来处理。订单冲突判断应该是新订单开始日期 已有订单结束日期 且 新订单结束日期 已有订单开始日期而不是两边都带等号。否则同一件衣服会在归还当天被两边占用或者出现空档。2.3 档期冲突与并发锁定这是租赁平台和电商平台最本质的区别。我第一版用普通的“先查后插”方式判断可租日期结果联调时用两个账号同时下单同一件衣服出现了数据库里两条订单重叠的情况。原因很简单两个请求同时查到“该日期空闲”然后同时插入订单。解决方案是事务加行锁。在 Django 里用select_for_update()锁住 SKU 记录比如把当天对应 dress_sku_stock 的is_available改为 False然后再创建订单。注意这个方法只在 MySQL 的 InnoDB 引擎下生效SQLite 不支持开发和生产要保持同一个数据库类型。伪代码大概是with transaction.atomic(): sku DressSku.objects.select_for_update().get(idsku_id) stock DressSkuStock.objects.select_for_update().filter( skusku, date__gtestart_date, date__ltend_date ) # 校验 stock 全部可用 # 标记占用 # 创建订单发布订阅逻辑也最好统一在订单状态变更时做。比如支付成功后把档期标记为已占用取消后把档期标记回可租。不要在多个视图里各写一套库存变更代码后面维护会疯。3. uniapp 微信小程序端的实现要点3.1 工程创建与 uview-plus 引入实战里我用 HBuilderX 创建了 uniapp 项目选择 Vue3 Vite 模板。如果你之前只写过 Vue2迁到 Vue3 时注意组合式API和生命周期差异但在 uniapp 里页面逻辑大体不变。组件库选了 uview-plus这是 uView 针对 Vue3 的升级版。在 HBuilderX 的插件市场直接导入即可但导入后有几个配置容易被忽略项目根目录要安装sassuview-plus 很多样式依赖 scss在pages.json里配置 easycom 规则否则组件不会被自动引入在uni.scss里引入 uview-plus 的主题变量具体来说pages.json加这段easycom: { autoscan: true, custom: { ^u--(.*): uview-plus/components/u-$1/u-$1.vue, ^up-(.*): uview-plus/components/u-$1/u-$1.vue, ^u-([^-].*): uview-plus/components/u-$1/u-$1.vue } }我当时导入后页面一直空白后来发现是把 easycom 写在了pages.json里的某个页面配置下面导致全局没有生效。这个配置必须放在第一层而不是页面级配置。3.2 导航栏高度和顶部适配微信小程序的导航栏在不同机型上高度不一致尤其是带刘海的设备。如果你用自定义导航栏不能写死一个44px。正确做法是读取胶囊按钮的位置和状态栏高度const menuButtonInfo uni.getMenuButtonBoundingClientRect() const systemInfo uni.getSystemInfoSync() const statusBarHeight systemInfo.statusBarHeight const navBarHeight (menuButtonInfo.top - statusBarHeight) * 2 menuButtonInfo.height计算出的navBarHeight建议在App.vue的onLaunch里存入本地缓存所有页面通过公共方法获取。不要每个页面各自计算一次既重复又容易出不一致。自定义导航栏还会带来一个麻烦页面滚动时标题栏需要吸顶。我用的是position: sticky配padding-top变量实测在微信开发者工具和真机上表现一致。如果你处理不好可以退一步用微信原生导航栏把标题放里面省去适配。只是原生导航栏样式定制空间小和整套设计风格不一定搭。3.3 请求封装、微信登录与状态管理小程序端所有请求必须走统一的封装不能在页面里直接写uni.request。我维护了一个utils/request.js核心就是把 baseURL、token 注入、错误拦截集中处理// utils/request.js const BASE_URL https://api.example.com export function request({ url, method GET, data {} }) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, Authorization: uni.getStorageSync(token) || }, success: (res) { if (res.data.code 0) { resolve(res.data.data) } else if (res.statusCode 401) { uni.navigateTo({ url: /pages/login/login }) reject(res.data) } else { uni.showToast({ title: res.data.msg, icon: none }) reject(res.data) } }, fail: (err) { uni.showToast({ title: 网络异常, icon: none }) reject(err) } }) }) }登录逻辑微信登录本质是用uni.login获取临时 code再由后端拿 code 去微信接口换 openid 和 session_key。前端不要直接处理 openid后端返回一个自定义 token 和用户信息即可。这里有个体验问题如果用户一进小程序就弹登录框转化率会很低。我实际项目里的策略是“游览不需要登录加入购物车或提交订单时才引导登录”按钮上做一个open-typegetPhoneNumber或统一走uni.login。状态管理我用 Pinia。本来项目小不引入也完全可以但购物车和用户信息在多页面共享不用状态管理就得反复读缓存、写缓存出了 bug 很难排查。Pinia 的使用方式和 Vuex 类似在 uniapp 中直接兼容不用担心跨端。3.4 列表加载更多、筛选、单选和图片预览首页和列表页最常用的交互是“页面列表加载更多”。我总结出的可靠写法是let page 1 let isLoading false let hasMore true function loadList(reset false) { if (isLoading) return if (reset) { page 1 hasMore true } if (!hasMore) return isLoading true request({ url: /api/dresses, data: { page, size: 10 } }).then((res) { isLoading false if (res.list.length 10) hasMore false page 1 // 追加或重置列表 }) } onReachBottom(() { loadList() })关键是isLoading标志。没有这个标志用户快速下拉时可能同时发出多个相同请求翻页数据会重复。尺码选择在 uniapp 里直接用单选框控件和微信原生 radio-group 基本一致radio-group changeonSizeChange radio valueS checkedsize S /S radio valueM checkedsize M /M /radio-group款式图、上身图建议用uni.previewImage做点击预览。这个 API 接收一个 URLs 数组并支持当前索引体验比普通跳转详情页强很多。3.5 订单确认页的日期选择和金额展示订单确认页是租赁平台的关键页面用户需要选择“开始日期”和“结束日期”。uniapp 生态里比较省事的方案是 uview-plus 的u-datetime-picker或使用原生 picker 的 modedate。开始日期不能早于今天结束日期不能早于开始日期这个校验要在前端和后端都做一遍。一个细节日期的选择粒度要按天而不是按时间戳。前后端传递建议直接用2025-05-01字符串不要用时间戳。因为时区不同可能导致租期差一天这种 bug 在支付场景下非常坑。金额可以在前端根据日期实时计算展示给用户但最终以后端计算和支付结果为准。前端计算只是体验不能作为可信数据。4. Python 后端与核心 API 实现4.1 工程结构与开发环境准备后端工程我拆成 config、apps、common 三层hanfu_rental/ ├── config/ │ ├── settings.py │ └── urls.py ├── apps/ │ ├── users/ │ ├── dresses/ │ ├── orders/ │ └── payments/ ├── common/ │ ├── permissions.py │ └── response.py ├── manage.py └── requirements.txt开发环境建议python -m venv venv source venv/bin/activate pip install django djangorestframework djangorestframework-simplejwt django-cors-headers mysqlclient pillowPython 版本我用的 3.10。如果你用新版 Django 5.x也记得保证配套的 mysqlclient 有对应版本的 wheel减少编译麻烦。MySQL 配置里四个点容易踩坑字符集要写utf8mb4避免存表情符号失败连接参数加上CONN_MAX_AGE减少重复连接时区设为Asia/Shanghai调试时CORS_ALLOW_ALL_ORIGINS True发布前收紧。4.2 微信登录与 JWT 鉴权微信小程序登录流程是标准化的。后端接收前端的 code调用微信接口换取 openid然后签发自己的 token。封装一个微信服务# apps/users/services.py import requests from django.conf import settings def code2session(code): url https://api.weixin.qq.com/sns/jscode2session params { appid: settings.WECHAT_APPID, secret: settings.WECHAT_SECRET, js_code: code, grant_type: authorization_code, } resp requests.get(url, paramsparams, timeout5) data resp.json() if openid not in data: raise ValueError(f微信登录失败: {data}) return data[openid], data.get(session_key, )拿到 openid 后查 user 表不存在就创建。然后用 djangorestframework-simplejwt 签发 tokenfrom rest_framework_simplejwt.tokens import RefreshToken def get_tokens_for_user(user): refresh RefreshToken.for_user(user) return { token: str(refresh.access_token), refresh: str(refresh), }微信的session_key绝不能返回给前端敏感会话信息只能留在后端。appsecret 同样只能配置在服务端任何前端代码和配置文件都不能出现。JWT 的有效期建议设短一点比如两个小时。 refresh token 的有效期设七天。小程序端每次请求带 token401 时跳转登录页重新静默登录。4.3 商品列表、详情与档期查询接口商品相关的接口并不复杂核心是列表要支持分类筛选、关键词搜索、分页。Django REST Framework 的GenericAPIView加PageNumberPagination很合适class DressListView(generics.ListAPIView): serializer_class DressListSerializer pagination_class StandardResultsSetPagination def get_queryset(self): queryset Dress.objects.filter(is_activeTrue) category self.request.query_params.get(category) keyword self.request.query_params.get(keyword) if category: queryset queryset.filter(categorycategory) if keyword: queryset queryset.filter(name__icontainskeyword) return queryset档期查询接口比较特殊。我设计的是传入 sku_id、开始日期、结束日期返回每一天是否可租。前端在订单确认页用这个接口动态展示不可选日期。后端判断时只需要查dress_sku_stock表当天库存状态为可租则返回 true。这个接口要注意性能和边界不要循环调用几十次数据库查询。更好的办法是查出日期范围内的所有库存记录用日期做索引组织数据再返回给前端。4.4 下单接口的事务实现下单接口是整个项目的核心必须做到强一致。我建议用户端只提交 sku_id、数量、开始日期、结束日期、收货地址ID后端统一计算价格和生成订单号。核心代码我用事务包起来transaction.atomic def create_order(request): serializer OrderCreateSerializer(datarequest.data) serializer.is_valid(raise_exceptionTrue) data serializer.validated_data sku DressSku.objects.select_for_update().get( iddata[sku_id] ) days (data[end_date] - data[start_date]).days if days 0: return error_response(租期不正确) stock_list DressSkuStock.objects.select_for_update().filter( skusku, date__gtedata[start_date], date__ltdata[end_date] ) if stock_list.count() days: return error_response(库存不足) # 检查是否全部可用 unavailable stock_list.filter(is_availableFalse) if unavailable.exists(): return error_response(所选日期已有订单请更换时间) stock_list.update(is_availableFalse) rent_amount sku.daily_price * days * data[quantity] deposit_amount sku.dress.deposit * data[quantity] order Order.objects.create( order_nogenerate_order_no(), userrequest.user, start_datedata[start_date], end_datedata[end_date], rent_amountrent_amount, deposit_amountdeposit_amount, total_amountrent_amount deposit_amount, statuspending_pay ) return success_response({order_id: order.id, amount: order.total_amount})这里用select_for_update将 SKU 记录锁住直到事务提交后续请求必须等待。这样能在根上避免两个用户同时租同一件衣服。4.5 支付回调与模拟支付真实微信支付需要商户号个人项目和毕设不一定申请下来。我的做法是做一个“模拟支付开关”。开发环境里把MOCK_PAY True用户点击支付后直接调用本地的模拟回调接口把订单状态置为待发货上线时再把开关关掉接入微信支付。支付回调的安全性必须强调回调地址不能简单设为免登录。微信回调没有登录态需要验证签名。无论是真实支付还是模拟回调回调接口都应该只做状态更新不要在回调里接收前端传来的订单金额金额和商户订单号必须从数据库查出来再和支付结果比对。用户支付成功后订单状态变成paid_ready这时再更新档期库存状态。注意我前文的下单接口在创建订单时就已经把档期标记为占用这里会有一个疑问如果用户最终不支付怎么办我的处理是超过30分钟未支付系统定时任务把订单取消并释放档期。这也是为什么下单时锁档期是必要的避免用户选择日期后档期被别人抢走。5. 发布、调试与常见问题5.1 从开发到体验版的联调过程日常开发我用 HBuilderX 运行到微信开发者工具前提是在manifest.json里填写正确的 appid并在微信开发者工具中开启服务端口。联调时有几点容易被忽略微信公众平台的 request 合法域名必须配置为你的后端域名否则真机预览会报url not in domain list开发时可勾选“不校验合法域名”但上线前一定要取消后端接口必须是 HTTPS微信小程序不认 HTTP需要给产品经理或其他人试用时在小程序后台把上传的代码设为体验版再生成体验版二维码体验版二维码的有效期和权限可以在公众平台配置。收集试用反馈是另一个话题但至少要让测试的人知道当前版本是哪一天上传的避免提了旧版本的 bug。5.2 我踩过的几个典型坑uniapp 不打印日志信息。这个问题我在真机调试时遇到过。代码里明明写了console.log但控制台没有输出。常见原因是打包后 console 被裁剪或者开发版和体验版日志输出策略不同。排查思路是先在微信开发者工具的 Network 里看请求是否成功然后在onLoad第一行写一个console.log(page load)确认是否是页面没加载。实在不行用uni.showToast验证代码是否执行到。uview-plus 导入后组件不生效。大多数情况是 easycom 配置错了位置或者没有安装 scss 依赖。另一个容易被忽略的点是uview-plus 的样式依赖全局uni.scss如果项目自定义了主题变量要注意覆盖顺序。自定义导航栏在不同机型上高度不一致。一定要用uni.getMenuButtonBoundingClientRect()计算不要在微信开发者工具里看着没问题就交付。开发者工具默认模拟机型没有刘海真机上状态栏高度可能多出20多像素。页面列表加载更多时数据重复。原因是翻页前没有清空旧数组或者page递增在请求失败时也执行了。我的建议是只有成功返回后递增页码并在加载第一页时用reset标志清空列表。H5 页面唤起微信小程序链接无法访问。这是微信限制不像普通网页那样随便跳转。H5 要唤起小程序必须走微信官方 URL Link 能力且通常要求订单类、服务类场景个人开发者还未必能申请下来。如果项目只需要在小程序内闭环不要在这个能力上浪费太多时间。5.3 Manifest 配置与上架安卓应用市场如果后续要把 uniapp 项目打包成 Appmanifest.json里的 App 模块配置很重要。用 HBuilderX 云打包时需要准备 Android 证书宝证书生成后要妥善管理。现在安卓市场审核普遍要求隐私政策小程序或 App 里只要涉及手机号、位置、订单信息就必须有显眼的隐私政策入口。微信小程序本身也有隐私协议配置。第一次开发时我没有配置__usePrivacyCheck__相关设置结果调用uni.getUserProfile时被拒绝。建议提前在公众平台的“用户隐私保护指引”里列清楚收集哪些信息比如头像、昵称、手机号、订单信息不用的权限不要声明。打包后还要做自测。很多坑只在正式包出现比如网络请求域名不合法、图片域名不校验、部分组件在 App 端样式错乱。我习惯在上传市场前用 uniapp 的“运行到手机”先完整走一遍浏览、下单、支付模拟、取消订单的流程。5.4 抓包调试建议一些新手喜欢直接上抓包工具看小程序请求。我的经验是绝大多数问题在微信开发者工具的 Network 面板里就能看明白不需要额外抓包。真机调试时HBuilderX 和微信开发者工具都有真机调试功能日志和请求记录会回到开发者工具比抓包工具更直观。假如真要抓包分析小程序请求需要 HTTPS 解密和安装根证书过程繁琐且容易被客户端校验拦截。不是排查不到问题时不建议第一反应就上抓包工具。优先检查三个地方request 合法域名、HTTPS 证书、后端日志。把这三个都确认过后问题基本已经定位了。写在最后的经验整套流程走下来我最大的体会是技术难点不在 Python也不在 uniapp而在把租赁业务规则翻译成代码状态机。电商的逻辑可以像流水一样一路流下去租赁的逻辑却存在大量“折返”——支付后要核销租期结束要归还归还后要检查检查后要退押金逾期还要补钱。每一步都要有对应的状态字段或者日志记录。如果让我重做一次我会先把状态流转图和日期冲突规则写死在纸上再动手写接口。没有这些约束页面越写越复杂接口越写越像补丁。另外演示项目里加入“模拟支付”这个开关是我觉得性价比最高的决定。它让整个流程在开发和答辩时都可以完整体验又不会引入商户号申请的麻烦。最后提醒一点如果在生产环境正式接入微信支付回调接口的幂等处理一定要考虑——同一个支付结果微信可能会通知多次不能因为重复回调就把押金退两次。基于上面这些实践照着这个思路从数据库到接口、从页面到发布一步步走这个项目是完全可以落地的。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑