简介:这份资源是依托Python、Django与Wepy框架构建的校园二手交易平台微信小程序毕业设计资料,包含论文与完整代码,适合对多技术栈感兴趣的学习者,可用于毕业设计、课程实践、实训项目或创业原型搭建。后端基于Django的MTV结构处理业务逻辑,数据库采用MySQL,前端在Node.js环境下用Wepy框架完成页面构建与功能测试,主要模块涵盖用户注册与资料完善、商品发布与查看、评论、点赞与收藏等互动功能。压缩包共181个文件,约3.66MB,以39个py源码、29个wpy页面组件、20个js脚本为主,另含pyc编译文件、zbak备份、png图片、json配置及docx说明文档,目录结构清晰,便于按模块阅读与调试。目前已有40人学习。读者可借此理解Django与Wepy的协作方式,掌握小程序端页面组织与接口调用思路,并参考运行说明书完成环境搭建与代码调试,为二次开发或功能扩展提供可借鉴的实践样本。
1. 校园二手交易平台:为什么用 Django + Wepy 而不是 SpringBoot 或若依
每年毕业季,宿舍楼下堆成山的二手书、小风扇、自行车,最后大多进了废品站。学生想卖,找不到人;想买,不知道谁在卖。校园二手交易平台要解决的就是这个信息撮合问题——把发布、搜索、下单、沟通这条链路跑通,让同一所学校里的供需对上。
技术选型上,后端我选 Python 的 Django,前端小程序用 Wepy 框架。为什么不用热搜里常见的 SpringBoot 或若依框架?因为校园项目的特点是:开发人手少、迭代快、预算低。Django 自带 ORM、Admin 后台、用户认证和迁移工具,一个后端能顶三个人用;若依那套前后端分离方案更适合企业级中后台,对小程序场景偏重。Wepy 是早期的小程序组件化框架,语法接近 Vue,对熟悉前端但没写过原生小程序的开发者友好,编译产物就是标准小程序代码,不引入额外运行时。
这套组合适合谁?适合在校学生做课程设计或毕业设计,也适合想快速验证校园二手业务的小团队。它不追求高并发,追求的是两周内能跑起来、能演示、能真实用。下面从环境搭建到核心功能,再到部署踩坑,一步步拆开讲。
2. 环境搭建与项目骨架:从 python 安装到 Django 跑起来
2.1 后端环境:python 安装与虚拟环境隔离
很多人卡在第一步不是代码写错,而是 python 安装和包管理混乱。Windows 上建议从 python 官网下载 3.10 或 3.11 版本,安装时勾选“Add Python to PATH”。Linux 系统安装 python 通常自带 3.8 以上,但 pip 可能需要单独装。装完后用python --version和pip --version确认。
虚拟环境是必须的,否则 Django 版本冲突会让你怀疑人生。我一般用 venv,不推荐全局装包。
# 创建项目目录并进入 mkdir campus_market && cd campus_market # 创建虚拟环境(Windows 用 python -m venv venv) python3 -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate source venv/bin/activate # 安装 Django 和数据库驱动 pip install django==4.2 mysqlclient pillow这里选 Django 4.2 是因为它是 LTS 版本,官方维护到 2026 年,校园项目用两年足够。mysqlclient是 MySQL 驱动,pillow用于处理商品图片上传。如果你用 SQLite 做开发,可以暂时不装 mysqlclient,但上线前一定要换 MySQL,SQLite 的并发写入在多人同时发布商品时会锁表。
提示:pip 安装慢的话换国内镜像源,命令后面加
-i https://pypi.tuna.tsinghua.edu.cn/simple,这是常规操作,不涉及任何网络工具。
2.2 创建 Django 项目与核心 App 划分
一个校园二手平台至少需要用户、商品、订单、消息四个模块。Django 的 App 划分要按业务边界来,不要把所有模型塞进一个 App。
# 创建 Django 项目 django-admin startproject campus_market . # 创建四个核心 App python manage.py startapp users python manage.py startapp goods python manage.py startapp orders python manage.py startapp chat创建完后在settings.py的INSTALLED_APPS里注册这四个 App。数据库配置改成 MySQL:
# settings.py 数据库配置片段 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'campus_market', 'USER': 'root', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': {'charset': 'utf8mb4'}, } }utf8mb4是为了支持 emoji 和生僻字,学生发布商品时经常在描述里加表情,用utf8会报错。HOST和PORT按实际数据库地址改,本地开发就是 127.0.0.1。
2.3 Wepy 前端环境:小程序项目初始化
Wepy 的安装依赖 Node.js,建议用 14.x 或 16.x 版本,太高版本编译会报错。先全局安装 wepy-cli:
npm install wepy-cli -g # 初始化项目,选择空模板 wepy init standard campus-market-wepy cd campus-market-wepy npm installWepy 项目结构里,src/pages放页面,src/components放组件,src/api放请求封装。编译命令是wepy build --watch,产物在dist目录,用微信开发者工具打开dist即可预览。
前后端联调时,Django 跑在127.0.0.1:8000,Wepy 请求这个地址。微信开发者工具需要勾选“不校验合法域名”,否则本地请求会被拦截。上线时再把域名换成备案过的 HTTPS 域名。
3. 核心数据模型与接口:商品发布、搜索、下单怎么落地
3.1 商品模型设计:字段、索引与图片存储
商品表是平台的核心,字段设计直接影响搜索和列表性能。我一般这样定义:
# goods/models.py from django.db import models from users.models import User class Category(models.Model): name = models.CharField(max_length=32, unique=True) sort_order = models.IntegerField(default=0) class Meta: ordering = ['sort_order'] class Goods(models.Model): STATUS_CHOICES = ( (0, '在售'), (1, '已售'), (2, '下架'), ) title = models.CharField(max_length=64, db_index=True) description = models.TextField(max_length=1000) price = models.DecimalField(max_digits=8, decimal_places=2) original_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True) category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True) seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name='goods_list') status = models.SmallIntegerField(choices=STATUS_CHOICES, default=0, db_index=True) cover_image = models.ImageField(upload_to='goods/%Y%m/') view_count = models.IntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True, db_index=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['status', '-created_at']), models.Index(fields=['category', 'status']), ]title加db_index=True是为了搜索时走索引,但注意 MySQL 的 like '%关键词%' 不会走索引,后面会讲怎么优化。status和created_at的联合索引覆盖了“在售商品按时间倒序”这个最高频查询。cover_image用upload_to按年月分目录,避免单目录文件过多导致 inode 性能下降。
图片上传前要在 Wepy 端压缩,学生用手机拍的照片动辄 3-5MB,直接传会导致接口超时。Wepy 里可以用wx.compressImage压缩到 200KB 以内再上传。
3.2 商品发布与列表接口:Django REST 写法
接口用 Django 的JsonResponse或 DRF 都行,校园项目为了少装依赖,我直接用原生视图加csrf_exempt装饰器。发布接口:
# goods/views.py import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.core.paginator import Paginator from .models import Goods, Category @csrf_exempt def publish_goods(request): if request.method != 'POST': return JsonResponse({'code': 405, 'msg': '方法不允许'}) try: data = request.POST title = data.get('title', '').strip() price = data.get('price', '0') if not title or len(title) < 2: return JsonResponse({'code': 400, 'msg': '标题至少2个字'}) if float(price) <= 0: return JsonResponse({'code': 400, 'msg': '价格必须大于0'}) goods = Goods.objects.create( title=title, description=data.get('description', ''), price=price, category_id=data.get('category_id'), seller_id=request.session.get('user_id'), cover_image=request.FILES.get('cover'), ) return JsonResponse({'code': 0, 'msg': '发布成功', 'id': goods.id}) except Exception as e: return JsonResponse({'code': 500, 'msg': str(e)})这里用request.session取用户 ID,前提是登录接口已经把用户信息写进 session。Wepy 端请求时带上 cookie,Django 的 session 才能识别。如果前后端域名不同,需要配置CORS和credentials,这是常见翻车点。
列表接口带分页和筛选:
def goods_list(request): page = int(request.GET.get('page', 1)) category_id = request.GET.get('category_id') keyword = request.GET.get('keyword', '').strip() qs = Goods.objects.filter(status=0).select_related('seller', 'category') if category_id: qs = qs.filter(category_id=category_id) if keyword: qs = qs.filter(title__icontains=keyword) paginator = Paginator(qs, 10) page_obj = paginator.get_page(page) data = [{ 'id': g.id, 'title': g.title, 'price': str(g.price), 'cover': g.cover_image.url if g.cover_image else '', 'seller': g.seller.nickname, 'created_at': g.created_at.strftime('%m-%d %H:%M'), } for g in page_obj] return JsonResponse({'code': 0, 'data': data, 'total': paginator.count})select_related是关键优化,不加的话每取一个商品都要查一次卖家和分类,10 条数据就是 20 次额外查询。icontains对应 SQL 的like '%关键词%',数据量超过 5000 条后明显变慢,后面进阶部分讲替代方案。
3.3 Wepy 端请求封装与页面调用
Wepy 的请求不要在每个页面里写wx.request,统一封装成api.js:
// src/api/index.js import wepy from 'wepy' const BASE_URL = 'http://127.0.0.1:8000' export function request(url, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wepy.request({ url: BASE_URL + url, method: method, data: data, header: { 'content-type': 'application/json' }, success: res => { if (res.data.code === 0) { resolve(res.data) } else { wepy.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data) } }, fail: err => { wepy.showToast({ title: '网络异常', icon: 'none' }) reject(err) } }) }) } export function getGoodsList(page, categoryId, keyword) { return request('/goods/list', 'GET', { page, category_id: categoryId, keyword }) }页面里调用:
import { getGoodsList } from '../api/index' export default class Home extends wepy.page { data = { list: [], page: 1, hasMore: true } methods = { async loadGoods() { const res = await getGoodsList(this.page, this.categoryId, this.keyword) this.list = this.list.concat(res.data) this.hasMore = this.list.length < res.total this.$apply() } } }Wepy 的$apply()是手动触发视图更新,忘记写会导致数据变了但页面不刷新,这是新手最常见的坑。请求封装里统一处理了code !== 0的提示,页面只需要关心成功逻辑。
4. 避坑与排查:校园二手平台开发中最容易翻车的 5 个点
4.1 图片上传后前端显示 404
现象:商品发布成功,但列表页图片是裂图,控制台报 404。原因:Django 开发模式下MEDIA_URL没有配置路由,上传的文件无法通过 URL 访问。解决:在settings.py加MEDIA_URL = '/media/'和MEDIA_ROOT = os.path.join(BASE_DIR, 'media'),在urls.py加:
from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # ... 其他路由 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)注意static()只在DEBUG=True时生效,上线后要用 Nginx 托管 media 目录。
4.2 Wepy 编译后页面空白,控制台无报错
现象:wepy build --watch显示编译成功,但微信开发者工具打开是白屏。原因:Wepy 的app.wpy里config配置的pages路径和实际文件不匹配,或者dist目录没有重新生成。解决:检查src/app.wpy的pages数组,路径不带.wpy后缀;删除dist目录重新编译;确认微信开发者工具打开的是dist目录而不是项目根目录。
4.3 登录状态丢失,每次请求都提示未登录
现象:登录接口返回成功,但后续请求 session 取不到用户。原因:Wepy 的wepy.request默认不带 cookie,Django 的 session 依赖 cookie。解决:在请求封装里加withCredentials: true,同时 Django 端配置CORS_ALLOW_CREDENTIALS = True和SESSION_COOKIE_SAMESITE = 'None'。如果不用 CORS 库,就在响应头手动加Access-Control-Allow-Credentials: true。
4.4 商品列表分页重复或漏数据
现象:翻到第二页时出现第一页的商品,或者某些商品永远不出现。原因:排序字段created_at有相同值,MySQL 分页时顺序不稳定。解决:在Meta.ordering里加一个唯一字段兜底,比如['-created_at', '-id']。这样即使时间相同,也有确定的顺序。
4.5 并发下单导致同一商品被卖两次
现象:两个用户同时点“立即购买”,都生成了订单。原因:下单接口没有做库存或状态校验的原子操作。解决:用select_for_update锁行,或者用条件更新:
updated = Goods.objects.filter(id=goods_id, status=0).update(status=1) if not updated: return JsonResponse({'code': 400, 'msg': '商品已被抢走'})filter(status=0).update(status=1)是原子操作,返回受影响行数,0 表示已经被别人改过。这比先查再改可靠得多。
5. 进阶技巧:用 Django 的 Q 对象和 Wepy 的 mixin 提升开发效率
5.1 复杂搜索用 Q 对象替代 icontains 拼接
校园二手平台经常需要“标题或描述包含关键词”的搜索。用Q对象可以组合条件,避免多次查询:
from django.db.models import Q def search_goods(request): keyword = request.GET.get('keyword', '').strip() qs = Goods.objects.filter(status=0) if keyword: qs = qs.filter( Q(title__icontains=keyword) | Q(description__icontains=keyword) ).distinct() # 后续分页逻辑Q(title__icontains=keyword) | Q(description__icontains=keyword)生成WHERE title LIKE '%kw%' OR description LIKE '%kw%',distinct()去重。数据量超过 1 万条时,icontains会全表扫描,这时候可以加一个search_keywords字段,发布时把标题和描述的分词结果存进去,搜索时用search_keywords__contains走索引。这是用空间换时间的常规做法。
5.2 Wepy mixin 复用登录校验和分页逻辑
Wepy 支持 mixin,可以把“检查登录”和“上拉加载更多”抽出来复用:
// src/mixins/pagination.js import wepy from 'wepy' export default class PaginationMixin extends wepy.mixin { data = { page: 1, hasMore: true, loading: false } methods = { async loadMore(fetchFn) { if (!this.hasMore || this.loading) return this.loading = true const res = await fetchFn(this.page) this.list = this.list.concat(res.data) this.hasMore = this.list.length < res.total this.page += 1 this.loading = false this.$apply() } } }页面里mixins = [PaginationMixin],然后调用this.loadMore(getGoodsList)即可。这样每个列表页少写 20 行重复代码,也避免了忘记重置loading导致上拉失效。
5.3 用 Django Admin 做运营后台,省掉一套管理界面
Django 自带 Admin 是校园项目最大的福利。注册模型后,运营同学可以直接在后台改商品状态、审核用户:
# goods/admin.py from django.contrib import admin from .models import Goods, Category @admin.register(Goods) class GoodsAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'price', 'seller', 'status', 'created_at') list_filter = ('status', 'category') search_fields = ('title', 'seller__nickname') list_editable = ('status',) actions = ['mark_sold'] def mark_sold(self, request, queryset): queryset.update(status=1) mark_sold.short_description = '批量标记为已售'list_editable让状态可以直接在列表页改,actions加批量操作。这样不用单独写管理后台,省下的时间可以打磨小程序端的体验。
5.4 验证方法:用 pytest 跑通核心接口
热搜里 pytest 框架出现频率很高,校园项目也值得加。装pytest-django后写测试:
# goods/tests.py import pytest from django.test import Client from goods.models import Goods, Category @pytest.mark.django_db def test_publish_goods(): client = Client() category = Category.objects.create(name='教材') response = client.post('/goods/publish', { 'title': '高等数学教材', 'price': '15.00', 'category_id': category.id, }) assert response.json()['code'] == 0 assert Goods.objects.count() == 1跑pytest就能验证发布接口是否正常。测试数据库是独立的,不会污染开发数据。我一般把发布、搜索、下单三个接口的测试写全,改代码后跑一遍,比手动点页面快得多。
这套 Django + Wepy 的组合,我从环境搭建到接口联调大概用了三天,其中一天半在踩图片上传和 session 的坑。如果你也在做校园二手平台,建议先把用户和商品两个模块跑通,再补订单和聊天。不要一上来就追求功能全,能发布、能搜到、能联系卖家,这个平台就有人用。希望帮到你。
本文还有配套的精品资源,点击获取