ARTICLE DETAIL

资讯详情

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

微信小程序宠物美容预约系统开发实践

微信小程序宠物美容预约系统开发实践 1. 项目背景与核心需求宠物美容行业近年来呈现爆发式增长根据2023年宠物行业白皮书数据显示我国宠物美容服务市场规模已突破200亿元年增长率保持在25%以上。传统电话预约方式存在效率低下、容易出错、客户体验差等问题开发一套基于微信小程序的预约系统具有明确的市场需求。这个系统需要解决三个核心痛点宠物主人可以随时查看美容师档期并完成预约避免电话占线或沟通不清美容店需要清晰管理每日预约订单合理安排美容师工作系统需要支持服务项目、价格、评价等完整业务流程选择微信小程序作为前端主要基于以下考虑微信生态用户覆盖广无需单独安装APP小程序开发成本低迭代速度快支持微信支付等原生能力用户使用门槛极低后端采用PythonFlask组合主要因为Python开发效率高适合快速原型验证Flask轻量灵活适合中小型Web服务丰富的第三方库支持如数据库ORM、微信接口等2. 技术架构设计2.1 整体架构方案系统采用经典的三层架构微信小程序前端 → Flask RESTful API → MySQL数据库前端与后端完全分离通过HTTPS协议进行数据交互。这种架构的优势在于前后端可以独立开发和部署便于后期扩展多端应用如APP、网页版API接口可复用性高2.2 数据库设计核心数据表包括用户表(users)CREATE TABLE users ( id int(11) NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信openid, nickname varchar(64) DEFAULT NULL, avatar varchar(255) DEFAULT NULL, phone varchar(20) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY idx_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;宠物表(pets)CREATE TABLE pets ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL, name varchar(32) NOT NULL, type tinyint(4) NOT NULL COMMENT 1-狗 2-猫 3-其他, breed varchar(32) DEFAULT NULL, weight decimal(5,2) DEFAULT NULL COMMENT 公斤, birthday date DEFAULT NULL, avatar varchar(255) DEFAULT NULL, PRIMARY KEY (id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;服务项目表(services)CREATE TABLE services ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(64) NOT NULL, description text, price decimal(10,2) NOT NULL, duration int(11) NOT NULL COMMENT 分钟, status tinyint(4) DEFAULT 1 COMMENT 1-上架 0-下架, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;预约表(appointments)CREATE TABLE appointments ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL, pet_id int(11) NOT NULL, service_id int(11) NOT NULL, staff_id int(11) DEFAULT NULL COMMENT 美容师ID, appoint_time datetime NOT NULL, end_time datetime NOT NULL, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0-待确认 1-已确认 2-已完成 3-已取消, remark varchar(255) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_time (appoint_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.3 API接口设计采用RESTful风格设计主要API用户相关GET /api/user/info - 获取用户信息POST /api/user/update - 更新用户信息宠物相关GET /api/pets - 获取用户宠物列表POST /api/pets - 添加宠物PUT /api/pets/{id} - 更新宠物信息DELETE /api/pets/{id} - 删除宠物预约相关GET /api/appointments - 获取预约列表POST /api/appointments - 创建预约PUT /api/appointments/{id}/cancel - 取消预约GET /api/appointments/available - 获取可预约时段服务项目GET /api/services - 获取服务列表3. 关键功能实现3.1 微信登录集成小程序端调用wx.login获取code后端通过code向微信服务器换取openidfrom flask import request, jsonify import requests app.route(/api/wxlogin, methods[POST]) def wx_login(): code request.json.get(code) if not code: return jsonify({error: 缺少code参数}), 400 # 微信接口配置 appid 你的小程序appid secret 你的小程序secret url fhttps://api.weixin.qq.com/sns/jscode2session?appid{appid}secret{secret}js_code{code}grant_typeauthorization_code try: resp requests.get(url) data resp.json() openid data.get(openid) if not openid: return jsonify({error: 微信登录失败, detail: data}), 400 # 查询或创建用户 user User.query.filter_by(openidopenid).first() if not user: user User(openidopenid) db.session.add(user) db.session.commit() # 生成JWT token token generate_token(user.id) return jsonify({token: token, user_id: user.id}) except Exception as e: return jsonify({error: 服务器异常, detail: str(e)}), 5003.2 预约时段管理实现美容师工作时段管理的关键算法from datetime import datetime, timedelta def get_available_slots(staff_id, date): 获取某美容师某天的可预约时段 # 1. 获取美容师工作时间配置 work_time StaffWorkTime.query.filter_by( staff_idstaff_id, week_daydate.weekday() ).first() if not work_time or not work_time.is_working: return [] start datetime.combine(date, work_time.start_time) end datetime.combine(date, work_time.end_time) # 2. 获取已有预约 appointments Appointment.query.filter( Appointment.staff_id staff_id, Appointment.appoint_time start, Appointment.appoint_time end, Appointment.status.in_([0, 1]) # 待确认和已确认 ).order_by(Appointment.appoint_time).all() # 3. 生成时间槽 slot_duration timedelta(minutes30) # 每个时段30分钟 available_slots [] current start while current slot_duration end: slot_end current slot_duration # 检查该时段是否被占用 conflict False for app in appointments: if not (app.end_time current or app.appoint_time slot_end): conflict True break if not conflict: available_slots.append({ start: current.strftime(%H:%M), end: slot_end.strftime(%H:%M) }) current slot_duration return available_slots3.3 微信支付集成实现预约支付的完整流程import time import hashlib import xml.etree.ElementTree as ET app.route(/api/payment/create, methods[POST]) login_required def create_payment(): order_id request.json.get(order_id) order Appointment.query.get(order_id) if not order: return jsonify({error: 订单不存在}), 404 # 微信支付配置 mch_id 商户号 api_key API密钥 notify_url https://yourdomain.com/api/payment/notify # 生成随机字符串 nonce_str hashlib.md5(str(time.time()).encode()).hexdigest() # 构造参数 params { appid: 小程序appid, mch_id: mch_id, nonce_str: nonce_str, body: f宠物美容服务-{order.service.name}, out_trade_no: fPAY{order.id}{int(time.time())}, total_fee: int(order.service.price * 100), # 单位分 spbill_create_ip: request.remote_addr, notify_url: notify_url, trade_type: JSAPI, openid: current_user.openid } # 生成签名 sign generate_sign(params, api_key) params[sign] sign # 转换为XML xml dict_to_xml(params) # 调用微信统一下单接口 resp requests.post( https://api.mch.weixin.qq.com/pay/unifiedorder, dataxml, headers{Content-Type: application/xml} ) # 解析返回结果 result xml_to_dict(resp.content) if result.get(return_code) ! SUCCESS: return jsonify({error: 支付创建失败, detail: result}), 400 # 返回小程序支付所需参数 prepay_id result[prepay_id] pay_params { timeStamp: str(int(time.time())), nonceStr: nonce_str, package: fprepay_id{prepay_id}, signType: MD5 } pay_sign generate_sign(pay_params, api_key) pay_params[paySign] pay_sign return jsonify(pay_params)4. 部署与优化4.1 生产环境部署推荐使用Nginx Gunicorn部署Flask应用安装依赖pip install gunicorn gevent创建Gunicorn配置文件gunicorn_conf.pyworkers 4 worker_class gevent bind 0.0.0.0:8000 accesslog /var/log/gunicorn/access.log errorlog /var/log/gunicorn/error.logNginx配置示例server { listen 80; server_name yourdomain.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /static { alias /path/to/your/static/files; expires 30d; } }4.2 性能优化建议数据库优化为常用查询字段添加索引使用SQLAlchemy的懒加载策略考虑读写分离架构缓存策略使用Redis缓存热点数据from flask_caching import Cache cache Cache(config{CACHE_TYPE: Redis, CACHE_REDIS_URL: redis://localhost:6379/0}) cache.init_app(app) app.route(/api/services) cache.cached(timeout3600) # 缓存1小时 def get_services(): return jsonify([s.to_dict() for s in Service.query.all()])异步任务使用Celery处理耗时操作如发送通知from celery import Celery celery Celery(tasks, brokerredis://localhost:6379/1) celery.task def send_appointment_reminder(appointment_id): appointment Appointment.query.get(appointment_id) # 发送微信模板消息5. 常见问题与解决方案5.1 微信登录失败排查常见错误及解决方法40029 - invalid code检查code是否过期code有效期5分钟确认appid和secret是否正确40163 - code been used确保每个code只使用一次检查是否有重复请求系统繁忙稍后重试检查微信服务器状态5.2 预约时间冲突处理实现乐观锁防止并发冲突from sqlalchemy import and_ app.route(/api/appointments, methods[POST]) login_required def create_appointment(): data request.json # 检查时段是否可用 staff_id data[staff_id] start_time datetime.fromisoformat(data[start_time]) end_time start_time timedelta(minutesdata[duration]) # 使用数据库事务和行锁 try: db.session.begin() # 检查冲突 conflict db.session.query( Appointment.query.filter( and_( Appointment.staff_id staff_id, Appointment.status.in_([0, 1]), not (Appointment.end_time start_time), not (Appointment.appoint_time end_time) ) ).exists() ).scalar() if conflict: db.session.rollback() return jsonify({error: 该时段已被预约}), 400 # 创建预约 appointment Appointment( user_idcurrent_user.id, pet_iddata[pet_id], service_iddata[service_id], staff_idstaff_id, appoint_timestart_time, end_timeend_time, remarkdata.get(remark) ) db.session.add(appointment) db.session.commit() return jsonify({id: appointment.id}) except Exception as e: db.session.rollback() return jsonify({error: 创建预约失败, detail: str(e)}), 5005.3 微信支付回调验证确保支付回调的安全性app.route(/api/payment/notify, methods[POST]) def payment_notify(): xml_data request.data data xml_to_dict(xml_data) # 验证签名 sign data.pop(sign) calculated_sign generate_sign(data, api_key) if sign ! calculated_sign: return xmlreturn_code![CDATA[FAIL]]/return_codereturn_msg![CDATA[签名失败]]/return_msg/xml # 处理业务逻辑 if data[return_code] SUCCESS and data[result_code] SUCCESS: out_trade_no data[out_trade_no] # 更新订单状态 order Order.query.filter_by(out_trade_noout_trade_no).first() if order and order.status unpaid: order.status paid order.transaction_id data[transaction_id] order.pay_time datetime.now() db.session.commit() return xmlreturn_code![CDATA[SUCCESS]]/return_codereturn_msg![CDATA[OK]]/return_msg/xml在实际开发中我遇到最棘手的问题是微信支付的回调处理。有几点经验值得分享一定要做好签名验证防止伪造请求处理逻辑要幂等因为微信可能会多次回调记录完整的回调日志便于排查问题响应必须符合微信要求的XML格式否则会被视为失败
返回列表