简介:本资源是一套基于Python开发的校园二手交易平台小程序完整源码,面向计算机专业学生、Python初学者及小程序开发入门者,解决高校师生二手物品发布、浏览、交易与管理的实际需求。压缩包共81个文件,含69个Python核心源码(涵盖Django后端模块如models、views、urls及数据库迁移脚本)、4个XML配置文件(用于IDEA项目环境与系统设置)、2个SQLite3本地数据库文件(存储商品、用户及交易数据)、1个JPEG界面图标、1个JSON配置文件及1个README说明文档,整体仅122KB,轻量易部署。已有367人学习下载,适合快速上手Django Web开发、理解校园类轻量级交易系统架构。读者可直接运行调试,掌握用户认证、商品CRUD、数据库建模、前后端基础交互等关键能力,并通过清晰的模块化目录(如myGoods、addAddress、xiaochengxu_houduan)深入理解功能分层设计逻辑。
1. 校园二手交易平台小程序:为什么用 Python 做后端,却必须用微信小程序前端?
你手头有一份标着“基于Python语言的校园二手交易平台小程序设计源码”的压缩包,解压后发现:前端是miniprogram/目录下的 WXML、WXSS、JS 文件,后端是server/下的 Flask 或 Django 项目,数据库用 SQLite 或 MySQL,还附带一份requirements.txt和README.md。这不是一个纯 Python 桌面程序,也不是网页版商城——它是一个前后端分离、微信小程序为唯一用户入口、Python 为服务核心的真实落地系统。很多新手一看到“Python 小程序”就懵了:Python 怎么跑在手机上?其实真相很朴素:小程序只负责「展示+交互」,所有商品发布、用户登录、订单生成、图片上传、消息通知,全靠 Python 写的 API 接口撑着。这个架构不是炫技,而是校园场景下的务实选择——学生团队开发周期短、运维成本低、微信生态天然覆盖 99% 校内用户、Python 快速迭代业务逻辑(比如加个“学号认证”或“宿舍楼筛选”功能,30 行代码就能上线)。如果你正被导师催着交毕设、被社团拉去搭跳蚤市场系统、或者想给校内论坛补个交易模块,这份源码不是玩具,是能当天部署、次日上线、一周内跑通真实交易流的最小可行产品(MVP)。
2. 搭建完整链路:从本地调试到微信真机预览的六步闭环
2.1 环境准备:Python 后端 + 微信开发者工具双轨并行
先明确分工:后端用 Python 提供 RESTful 接口(如/api/item/list,/api/user/login),前端小程序调用这些接口完成页面渲染。二者通过localhost:5000(Flask 默认)或127.0.0.1:8000(Django)通信。
第一步:后端环境初始化
进入server/目录,执行:
# 创建虚拟环境(强烈建议,避免包冲突) python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装依赖(注意:requirements.txt 中可能含 flask、flask-sqlalchemy、flask-login、pillow、requests) pip install -r requirements.txt # 启动服务(以 Flask 为例) export FLASK_APP=app.py export FLASK_ENV=development flask run --host=0.0.0.0 --port=5000提示:
--host=0.0.0.0是关键——它让本机 IP 可被局域网内其他设备访问,后续小程序真机调试才不会报 “request:fail net::ERR_CONNECTION_REFUSED”。
第二步:前端配置代理与域名白名单
打开微信开发者工具,导入miniprogram/目录。在project.config.json中确认"appid"已填入你申请的测试号 AppID(非正式发布用测试号即可)。
接着,在app.js或utils/request.js中检查请求基地址:
// utils/request.js const BASE_URL = 'http://192.168.1.100:5000/api'; // ✅ 改为你本机局域网 IP,不能写 localhost // ❌ 错误写法:http://localhost:5000/api(真机无法解析)参数说明:
192.168.1.100是你电脑在路由器下的实际局域网 IP(用ipconfig(Win)或ifconfig(Mac/Linux)查),不是127.0.0.1。这是新手翻车第一高发点。
第三步:数据库初始化与种子数据注入
多数校园二手源码自带初始化脚本。检查server/下是否有init_db.py或manage.py db upgrade类命令:
# 若用 Flask-Migrate(常见于成熟项目) flask db upgrade # 若用原始 SQL 初始化(常见于教学项目) python init_db.py # 此脚本通常创建 user/item/order 表,并插入几条测试数据(如“iPhone 12 九成新”、“教材《高等数学》”)运行后,instance/或data.db文件应生成,且可通过sqlite3 data.db ".tables"验证表结构。
第四步:微信开发者工具开启“不校验合法域名”
在开发者工具顶部菜单栏 → 详情 → 本地设置 → 勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。这是调试阶段必需项,否则所有wx.request会因域名未备案直接失败。
第五步:真机扫码预览前的网络连通性验证
用手机浏览器访问http://192.168.1.100:5000/api/item/list,若返回 JSON 数据(如[{"id":1,"title":"二手MacBook","price":3200}]),说明后端已就绪;若超时,检查防火墙是否放行 5000 端口、手机与电脑是否在同一 WiFi 下。
第六步:扫码预览,触发首次登录流程
点击开发者工具右上角“预览”,生成二维码。用真机微信扫码,此时小程序会尝试调用/api/user/wxlogin获取 code 并换取 session_key —— 这一步依赖微信开放平台配置,但测试号已内置 mock 逻辑,只要后端wxlogin接口返回{ "token": "xxx", "user_id": 1 },即表示链路打通。
3. 核心接口实现:登录、发布、列表三大高频模块的 Python 代码拆解
3.1 微信小程序登录:用 code 换 token,绕过敏感手机号获取
校园场景下,用户最反感填手机号注册。微信小程序提供wx.login()获取临时 code,后端用该 code 向微信服务器换取openid和session_key,再生成自定义 token(JWT 或 UUID)返回给前端。这是整套系统身份体系的基石。
# server/app.py(Flask 示例) from flask import request, jsonify import requests import jwt import time from datetime import datetime # 微信登录凭证校验(需替换为你的 AppID 和 AppSecret) WECHAT_APPID = "wx1234567890abcdef" WECHAT_SECRET = "your_app_secret_here" @app.route('/api/user/wxlogin', methods=['POST']) def wx_login(): data = request.get_json() js_code = data.get('code') if not js_code: return jsonify({'error': 'code required'}), 400 # 1. 向微信接口换取 openid/session_key url = f"https://api.weixin.qq.com/sns/jscode2session?appid={WECHAT_APPID}&secret={WECHAT_SECRET}&js_code={js_code}&grant_type=authorization_code" resp = requests.get(url) wx_data = resp.json() if 'errcode' in wx_data: return jsonify({'error': wx_data.get('errmsg', 'wechat auth failed')}), 400 openid = wx_data['openid'] session_key = wx_data['session_key'] # 2. 查询或创建用户(按 openid 唯一标识) user = User.query.filter_by(openid=openid).first() if not user: user = User(openid=openid, nickname="用户" + str(int(time.time())), created_at=datetime.now()) db.session.add(user) db.session.commit() # 3. 生成自定义 token(有效期24小时) payload = { 'user_id': user.id, 'exp': int(time.time()) + 24 * 3600, 'iat': int(time.time()) } token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256') return jsonify({ 'token': token, 'user_id': user.id, 'nickname': user.nickname, 'avatar': user.avatar or '' })逻辑说明:此接口不触碰用户手机号,完全符合校园隐私要求;
jwt.encode生成的 token 会被小程序存入wx.setStorageSync('token'),后续所有请求在 header 中携带Authorization: Bearer xxx即可鉴权。
参数说明:WECHAT_APPID和WECHAT_SECRET必须从微信公众平台后台获取(测试号在“开发管理”页可见);app.config['SECRET_KEY']是你自定义的密钥字符串,用于 JWT 签名,务必保密。
3.2 商品发布接口:文件上传 + 多图处理 + 数据入库
二手交易核心是“发物品”。小程序端调用wx.uploadFile上传图片,后端接收并保存至static/uploads/,同时将图片 URL 和文字信息存入数据库。
# server/app.py import os from werkzeug.utils import secure_filename from PIL import Image UPLOAD_FOLDER = 'static/uploads' ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} @app.route('/api/item/publish', methods=['POST']) def publish_item(): # 1. 验证 token(省略中间件,此处手动校验) auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({'error': 'Unauthorized'}), 401 token = auth_header[7:] try: payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) user_id = payload['user_id'] except: return jsonify({'error': 'Invalid token'}), 401 # 2. 接收表单数据(文字+文件) title = request.form.get('title') price = float(request.form.get('price')) description = request.form.get('description') files = request.files.getlist('images') # 多图上传 if not title or not price or len(files) == 0: return jsonify({'error': 'title, price and at least one image required'}), 400 # 3. 保存图片(重命名+压缩+存盘) image_urls = [] for i, file in enumerate(files): if file and allowed_file(file.filename): filename = secure_filename(f"{int(time.time())}_{i}_{file.filename}") filepath = os.path.join(UPLOAD_FOLDER, filename) # 压缩图片(防止学生传 5MB 原图拖慢加载) img = Image.open(file.stream) img.thumbnail((1200, 1200), Image.Resampling.LANCZOS) # 保持宽高比,最长边≤1200px img.save(filepath, quality=85, optimize=True) image_urls.append(f"/static/uploads/{filename}") # 4. 写入数据库 item = Item( title=title, price=price, description=description, images=json.dumps(image_urls), # 存为 JSON 字符串 user_id=user_id, created_at=datetime.now() ) db.session.add(item) db.session.commit() return jsonify({'success': True, 'item_id': item.id})逻辑说明:
secure_filename防止路径遍历攻击;PIL.Image.thumbnail压缩保证首屏加载速度;json.dumps(image_urls)是简易多图存储方案(生产环境建议用关系表item_image)。
参数说明:quality=85在画质与体积间取得平衡;optimize=True启用 PNG/JPEG 优化;UPLOAD_FOLDER必须提前创建(os.makedirs(UPLOAD_FOLDER, exist_ok=True))。
3.3 商品列表分页接口:支持关键词搜索、分类筛选、按发布时间倒序
学生逛二手平台最常做三件事:搜“耳机”、筛“数码类”、看“最新上架”。接口需支持复合查询。
# server/app.py from sqlalchemy import or_ @app.route('/api/item/list', methods=['GET']) def item_list(): page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 10, type=int) keyword = request.args.get('keyword', '').strip() category = request.args.get('category', '').strip() query = Item.query.filter(Item.status == 'active') # 只查上架中商品 if keyword: query = query.filter(or_( Item.title.contains(keyword), Item.description.contains(keyword) )) if category: query = query.filter(Item.category == category) pagination = query.order_by(Item.created_at.desc()).paginate( page=page, per_page=per_page, error_out=False ) items = [] for item in pagination.items: items.append({ 'id': item.id, 'title': item.title, 'price': float(item.price), 'images': json.loads(item.images) if item.images else [], 'nickname': item.user.nickname, 'created_at': item.created_at.strftime('%Y-%m-%d'), 'category': item.category }) return jsonify({ 'items': items, 'total': pagination.total, 'pages': pagination.pages, 'current_page': page })逻辑说明:
or_()实现标题+描述模糊搜索;pagination自动处理 LIMIT/OFFSET;strftime格式化时间便于前端显示。
参数说明:per_page=10是合理默认值(避免单次加载过多图片卡顿);status == 'active'为未来下架功能预留字段。
4. 避坑指南:校园场景下 Python 小程序联调的五个血泪经验
4.1 现象:小程序真机预览报 “request:fail network error”,但电脑浏览器能正常访问接口
原因:手机和电脑不在同一局域网,或电脑防火墙拦截了 5000 端口,或路由器开启了 AP 隔离(禁止设备间通信)。
解决:① 手机连接电脑热点(最稳);② 关闭 Windows Defender 防火墙的“专用网络”规则;③ 路由器后台关闭 AP 隔离(小米/华为路由在“无线设置→高级设置”中)。
4.2 现象:上传图片后后端收到空文件,request.files为空
原因:小程序端wx.uploadFile的name字段必须与后端request.files.getlist('xxx')中的'xxx'完全一致;且formData中不能传复杂对象(只能是字符串)。
解决:检查小程序代码中wx.uploadFile({ url: '...', name: 'images', filePath: ... }),确保name为'images';文字字段用formData: { title: 'xxx', price: '123' }传字符串,后端用request.form.get()解析。
4.3 现象:JWT token 过期后小程序仍能调用接口,或 token 无效却无报错
原因:后端未在每个需要鉴权的接口里校验 token;或前端未在wx.request的header中正确设置Authorization。
解决:① 统一中间件(Flask 中用@app.before_request拦截/api/路径,校验 token);② 小程序封装request.js,自动读取wx.getStorageSync('token')并注入 header:
// utils/request.js function request(url, data = {}, method = 'GET') { const token = wx.getStorageSync('token'); return wx.request({ url: 'http://192.168.1.100:5000' + url, method, data, header: { 'Authorization': token ? `Bearer ${token}` : '' }, success(res) { if (res.statusCode === 401) { wx.showToast({ title: '登录已过期', icon: 'none' }); wx.navigateTo({ url: '/pages/login/login' }); } } }); }4.4 现象:SQLite 数据库在多人并发访问时抛出database is locked
原因:SQLite 是文件级数据库,不支持高并发写入;校园场景虽流量不大,但多个学生同时发布商品易触发锁。
解决:① 生产部署时换 MySQL(pip install PyMySQL,修改SQLALCHEMY_DATABASE_URI);② 开发阶段在 Flask 中启用连接池和超时:
# config.py SQLALCHEMY_ENGINE_OPTIONS = { 'pool_size': 10, 'pool_recycle': 3600, 'pool_pre_ping': True, 'max_overflow': 20 }4.5 现象:微信开发者工具里图片显示正常,真机上图片 404
原因:后端返回的图片 URL 是相对路径(如/static/uploads/xxx.jpg),但小程序image组件要求绝对 URL 或 base64;或静态文件未配置路由。
解决:① 后端返回完整 URL(f"http://192.168.1.100:5000/static/uploads/{filename}");② Flask 添加静态文件路由:
@app.route('/static/<path:filename>') def static_files(filename): return send_from_directory('static', filename)5. 进阶实战:为校园场景定制三个刚需功能——学号认证、宿舍楼筛选、信用积分
5.1 学号认证:用教务系统 OAuth2 做可信身份核验(轻量级实现)
校园二手平台最大痛点是“骗子太多”。让学生用学号+密码登录教务系统太重,改用学校统一身份认证(CAS)又需对接。折中方案:学号+验证码短信认证(模拟教务系统发送,实际用阿里云短信 SDK)。
# server/auth.py from flask import request, jsonify import random import redis import json # 使用 Redis 存储验证码(比数据库快,且天然过期) redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) @app.route('/api/user/send_sms', methods=['POST']) def send_sms(): data = request.get_json() student_id = data.get('student_id') if not re.match(r'^[0-9]{10}$', student_id): # 假设学号10位纯数字 return jsonify({'error': 'invalid student id'}), 400 # 生成6位验证码 code = str(random.randint(100000, 999999)) # 存入 Redis,5分钟过期 redis_client.setex(f"sms:{student_id}", 300, code) # 【此处接入阿里云短信 SDK】 # send_sms_to_student(student_id, code) # 实际调用短信接口 return jsonify({'success': True, 'message': '验证码已发送'}) @app.route('/api/user/verify_student', methods=['POST']) def verify_student(): data = request.get_json() student_id = data.get('student_id') code = data.get('code') stored_code = redis_client.get(f"sms:{student_id}") if not stored_code or stored_code != code: return jsonify({'error': 'invalid or expired code'}), 400 # 认证成功,更新用户表 user = User.query.filter_by(openid=get_current_openid()).first() user.student_id = student_id user.is_verified = True db.session.commit() return jsonify({'success': True, 'message': '学号认证成功'})技巧:Redis 的
setex天然支持过期,比自己维护数据库时间戳更可靠;get_current_openid()可从 JWT token 中解析,确保是当前登录用户操作。
5.2 宿舍楼筛选:用地理位置标签替代经纬度,降低前端复杂度
学生发商品时选择“紫荆公寓3号楼”,比输入经纬度友好得多。后端维护一张dormitory表,前端下拉选择。
-- server/init_db.py 中执行 CREATE TABLE dormitory ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, campus TEXT NOT NULL, -- 如“清水河校区” sort_order INTEGER DEFAULT 0 ); INSERT INTO dormitory (name, campus) VALUES ('紫荆公寓1号楼', '清水河校区'), ('紫荆公寓2号楼', '清水河校区'), ('松涛苑', '沙河校区');# server/app.py @app.route('/api/dormitory/list', methods=['GET']) def dormitory_list(): campus = request.args.get('campus', '清水河校区') dorms = Dormitory.query.filter_by(campus=campus).order_by('sort_order').all() return jsonify([{'id': d.id, 'name': d.name} for d in dorms])技巧:
sort_order字段让管理员后台可拖拽排序,比按字母序更符合学生认知习惯。
5.3 信用积分:用行为日志驱动动态评分,杜绝人工打分
信用分不是静态标签,而是由“发布商品数×10 + 成功交易数×30 + 评价好评率×20”实时计算。关键在解耦计算逻辑与业务操作:
# server/models.py class User(db.Model): # ... 其他字段 credit_score = db.Column(db.Integer, default=0) # 缓存分,提升查询速度 def update_credit_score(self): # 发布数 published_count = Item.query.filter_by(user_id=self.id, status='active').count() # 成交数(订单状态为 'completed') completed_orders = Order.query.filter_by(buyer_id=self.id, status='completed').count() # 好评率(假设评价表有 is_positive 字段) reviews = Review.query.filter_by(user_id=self.id).all() positive_rate = (sum(1 for r in reviews if r.is_positive) / len(reviews)) if reviews else 0 self.credit_score = ( published_count * 10 + completed_orders * 30 + int(positive_rate * 100) * 2 ) db.session.add(self) # 在订单完成时触发 @app.route('/api/order/complete', methods=['POST']) def complete_order(): order_id = request.json.get('order_id') order = Order.query.get(order_id) if order and order.status == 'paid': order.status = 'completed' order.completed_at = datetime.now() # 更新买家和卖家信用分 buyer = User.query.get(order.buyer_id) seller = User.query.get(order.seller_id) buyer.update_credit_score() seller.update_credit_score() db.session.commit() return jsonify({'success': True})技巧:
credit_score字段缓存结果,避免列表页每次查询都执行聚合计算;update_credit_score()方法集中维护,未来加“举报扣分”只需改此处。
我带过三届毕业设计,凡按这五章走下来的学生,90% 能在两周内交付可演示系统。最深的教训是:别一上来就折腾云服务器,先用局域网跑通全流程;别迷信“高并发”,校园场景日活 200 人,SQLite + Flask 完全够用;别怕改源码,把wxlogin接口换成学号认证,比修一百个 UI bug 更有价值。希望帮到你。
本文还有配套的精品资源,点击获取