资讯动态

校园二手平台Django+Vue+小程序三层架构实践

发布时间:2026/9/16 18:34:59 来源:尧图企业网站定制
简介这是一套面向计算机专业本科生的毕业设计完整交付物聚焦校园二手交易场景采用微信小程序前端 Django后端 Vue管理后台 MySQL数据库全栈技术架构解决传统线下二手交易效率低、信息不对称、沟通成本高等痛点适用于课程设计、毕设参考与全栈开发实战学习。资源包共778个文件24.5MB涵盖155个Vue组件文件含管理后台页面、125张PNG/JPG图标与截图、95个JS交互逻辑脚本、61个Python核心业务模块含Django视图、模型、路由、2个SQL数据库初始化与表结构脚本以及安装/运行/构建等配套BAT批处理工具。目前已有88人学习下载。读者可直接部署运行获得可商用的前后端分离系统原型同时获取完整毕业论文文档、分步视频教程及清晰的工程目录结构便于理解模块划分逻辑与技术集成路径。1. 为什么校园二手交易小程序要用 Django Vue 微信小程序三层架构一个真实的毕业设计场景学生想快速上线一个能扫码发布、微信登录、实时聊天、订单状态流转的校园二手平台但发现单用微信小程序原生开发卡在数据持久化和后台管理上改用 uni-app 虽能跨端却让 Django 的 ORM、Admin 和权限体系完全闲置而纯 Vue 前端配 Flask 又难支撑高并发图片上传与事务性订单操作。这个标题里的「微信小程序 Django Vue MySQL」不是堆砌技术名词而是对校园场景的精准解耦——微信小程序负责轻量级用户触达与微信生态集成如微信支付、手机号快速验证Vue 构建 PC 端后台管理系统审核商品、导出数据、处理投诉Django 承担核心业务逻辑与数据中枢用户关系、库存扣减、消息队列、API 统一鉴权MySQL 则以事务安全和索引优化保障多并发下的商品下架、订单创建不超卖。它适合需要交付完整可运行系统、有数据库设计要求、且需展示前后端分离工程能力的本科毕设尤其当答辩老师问“你怎么保证二手商品被抢购时不会重复下单”——答案就藏在 Django 的select_for_update()与 MySQL 行锁配合里而不是一句“前端做了防重提交”。2. 搭建 Django 后端服务从项目初始化到 RESTful API 暴露2.1 初始化 Django 项目并配置 MySQL 连接校园二手平台对数据一致性要求高必须使用支持事务的 MySQL 而非 SQLite。先确保已安装mysqlclientDjango 官方推荐驱动pip install django4.2.13 mysqlclient2.2.4提示Django 4.2.x 是当前长期支持LTS版本兼容 Python 3.8–3.12mysqlclient需提前安装 MySQL 开发头文件Linux 下sudo apt-get install libmysqlclient-devmacOS 用brew install mysql-client。创建项目并配置数据库django-admin startproject campus_market . python manage.py startapp goods python manage.py startapp orders python manage.py startapp users修改settings.py中的DATABASES配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: campus_market_db, USER: campus_user, PASSWORD: SecurePass2024!, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, charset: utf8mb4, }, TEST: { CHARSET: utf8mb4, COLLATION: utf8mb4_unicode_ci, } } }关键参数说明charset: utf8mb4强制使用 utf8mb4 编码避免微信昵称中的 emoji如 存入失败init_command启用严格模式防止插入空字符串或超长字段时静默截断TEST块为单元测试单独指定字符集避免测试库建表失败。执行迁移前需在 MySQL 中手动创建数据库及用户CREATE DATABASE campus_market_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER campus_userlocalhost IDENTIFIED BY SecurePass2024!; GRANT ALL PRIVILEGES ON campus_market_db.* TO campus_userlocalhost; FLUSH PRIVILEGES;2.2 设计核心模型商品、订单与用户关系校园二手交易的核心约束是「一人一校一账号」「商品归属明确」「订单状态不可逆跳转」。以下为goods/models.py的关键定义from django.db import models from django.contrib.auth.models import User from django.core.validators import MinValueValidator class Campus(models.Model): name models.CharField(max_length50, uniqueTrue) code models.CharField(max_length10, uniqueTrue) # 如 BIT-2024 class UserProfile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) campus models.ForeignKey(Campus, on_deletemodels.PROTECT) phone models.CharField(max_length11, blankTrue) class Goods(models.Model): STATUS_CHOICES ( (on, 在售), (off, 已下架), (sold, 已售出), ) owner models.ForeignKey(User, on_deletemodels.CASCADE, related_namegoods) campus models.ForeignKey(Campus, on_deletemodels.PROTECT) title models.CharField(max_length100) price models.DecimalField(max_digits10, decimal_places2, validators[MinValueValidator(0.01)]) description models.TextField() status models.CharField(max_length10, choicesSTATUS_CHOICES, defaulton) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) class Meta: ordering [-created_at] indexes [ models.Index(fields[campus, status]), models.Index(fields[owner, status]), ]为什么这样设计Campus表独立存在而非仅用UserProfile.campus_name字符串便于后续按学校聚合统计如“北航本周成交额 Top10 商品”且PROTECT级联阻止误删学校导致商品归属丢失Goods.status使用choices而非布尔字段预留扩展空间未来可加pending_audit审核中状态indexes显式声明复合索引微信小程序首页按学校在售状态筛选商品时数据库能直接走索引避免全表扫描。执行迁移并创建超级用户python manage.py makemigrations python manage.py migrate python manage.py createsuperuser # 用于后台管理登录2.3 构建 RESTful API用 Django REST Framework 暴露商品接口微信小程序前端需调用/api/goods/获取列表、/api/goods/{id}/查看详情。安装 DRF 并注册pip install djangorestframework3.14.0在settings.py中添加INSTALLED_APPS [rest_framework] REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework.authentication.SessionAuthentication, rest_framework.authentication.TokenAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticatedOrReadOnly, ], }编写goods/views.pyfrom rest_framework import generics, permissions from rest_framework.response import Response from .models import Goods, Campus from .serializers import GoodsSerializer class GoodsListView(generics.ListAPIView): serializer_class GoodsSerializer permission_classes [permissions.IsAuthenticatedOrReadOnly] def get_queryset(self): campus_code self.request.query_params.get(campus) if campus_code: try: campus Campus.objects.get(codecampus_code) return Goods.objects.filter(campuscampus, statuson) except Campus.DoesNotExist: return Goods.objects.none() return Goods.objects.filter(statuson) class GoodsDetailView(generics.RetrieveAPIView): queryset Goods.objects.filter(statuson) serializer_class GoodsSerializer lookup_field id接口行为验证访问http://127.0.0.1:8000/api/goods/?campusBIT-2024返回该校在售商品 JSON未登录用户可查看但 POST/PUT 需 Token 或 Session 认证queryset在RetrieveAPIView中预过滤statuson避免用户通过 URL 修改 ID 访问已下架商品。3. 微信小程序前端实现扫码发布与微信登录态透传3.1 初始化小程序项目并接入 Django 登录态微信小程序不能直接访问 Django 的 Session Cookie因跨域且 Cookie 默认SameSiteLax必须采用 Token 方式。流程如下小程序调用wx.login()获取code小程序将code发送给 Django/api/auth/login/接口Django 用code向微信服务器换取openid和session_keyDjango 创建或查找对应User生成 Token 并返回。users/views.py实现import requests from django.conf import settings from django.contrib.auth import authenticate from django.contrib.auth.models import User from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.authtoken.models import Token api_view([POST]) def wechat_login(request): code request.data.get(code) if not code: return Response({error: code required}, statusstatus.HTTP_400_BAD_REQUEST) # 向微信服务器换取 openid url fhttps://api.weixin.qq.com/sns/jscode2session?appid{settings.WX_APPID}secret{settings.WX_SECRET}js_code{code}grant_typeauthorization_code resp requests.get(url) data resp.json() if errcode in data: return Response({error: data.get(errmsg)}, statusstatus.HTTP_400_BAD_REQUEST) openid data[openid] session_key data[session_key] # 根据 openid 查找或创建用户 try: user User.objects.get(usernamefwx_{openid}) except User.DoesNotExist: user User.objects.create_user( usernamefwx_{openid}, passwordNone, # 不设密码仅靠微信认证 ) # 生成 Token token, _ Token.objects.get_or_create(useruser) return Response({ token: token.key, user_id: user.id, username: user.username, })配置项说明settings.WX_APPID和settings.WX_SECRET需在settings.py中明确定义严禁硬编码在代码中usernamefwx_{openid}保证唯一性避免不同用户 openid 冲突微信 openid 全局唯一Token.objects.get_or_create()复用已有 Token减少频繁生成。3.2 小程序端调用逻辑获取 Code → 请求 Token → 存储本地pages/index/index.js示例// 小程序端 Page({ data: { goodsList: [] }, onLoad() { this.loginAndFetchGoods(); }, loginAndFetchGoods() { wx.login({ success: (res) { wx.request({ url: http://127.0.0.1:8000/api/auth/login/, method: POST, data: { code: res.code }, success: (resp) { const token resp.data.token; wx.setStorageSync(auth_token, token); // 存入本地缓存 this.fetchGoods(); }, fail: (err) console.error(登录失败, err) }); } }); }, fetchGoods() { const token wx.getStorageSync(auth_token); wx.request({ url: http://127.0.0.1:8000/api/goods/?campusBIT-2024, header: { Authorization: Token ${token} }, success: (res) { this.setData({ goodsList: res.data }); } }); } });注意事项开发阶段用127.0.0.1可调试上线必须替换为 HTTPS 域名微信要求wx.setStorageSync存储 Token 是临时方案生产环境应结合wx.checkSession定期刷新header中Authorization: Token xxx格式必须与 DRF 配置一致大小写敏感。3.3 扫码发布功能调起相机 → 识别二维码 → 提交商品校园场景中学生常扫宿舍楼公告栏上的「发布入口码」快速跳转发布页。小程序端使用wx.scanCode// pages/publish/publish.js Page({ onShow() { // 页面显示时自动扫码仅限特定场景需用户授权 wx.authorize({ scope: scope.camera, success: () { this.startScan(); } }); }, startScan() { wx.scanCode({ onlyFromCamera: true, success: (res) { // 假设扫码内容为https://campus.example.com/publish?campusBIT-2024categorybook const url new URL(res.result); const campus url.searchParams.get(campus); const category url.searchParams.get(category); this.setData({ campus, category }); } }); }, onSubmit() { const token wx.getStorageSync(auth_token); wx.request({ url: http://127.0.0.1:8000/api/goods/, method: POST, header: { Authorization: Token ${token} }, data: { title: this.data.title, price: this.data.price, description: this.data.desc, campus: this.data.campus, // 图片需先上传至 Django 服务器此处省略上传逻辑 } }); } });关键点onlyFromCamera: true强制使用摄像头避免相册图干扰扫码后解析 URL 参数预填发布表单提升校园场景下的发布效率商品图片必须走独立上传接口如/api/upload/image/Django 用Pillow处理缩略图禁止前端 base64 直传微信小程序单次请求限制 10MB且 base64 体积膨胀 33%。4. Vue 后台管理系统基于 Element Plus 实现商品审核与数据导出4.1 初始化 Vue 项目并连接 Django API使用 Vue 3 Vite Element Plus 构建 PC 端后台命令如下npm create vitelatest campus-admin -- --template vue cd campus-admin npm install npm install element-plus axios dayjs在src/main.js中引入 Element Plusimport { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue const app createApp(App) app.use(ElementPlus) app.mount(#app)配置 Axios 全局 baseURL 与请求拦截器// src/utils/request.js import axios from axios const request axios.create({ baseURL: http://127.0.0.1:8000/api/, timeout: 10000, }) // 请求拦截自动携带 Token request.interceptors.request.use(config { const token localStorage.getItem(admin_token) if (token) { config.headers.Authorization Token ${token} } return config }) export default request4.2 商品审核列表表格 状态筛选 批量操作src/views/GoodsAudit.vue实现带搜索、分页、状态筛选的审核页template el-card div slotheader span待审核商品/span el-input v-modelsearchTitle placeholder按标题搜索 stylewidth: 200px; margin-left: 20px / el-select v-modelfilterStatus placeholder状态筛选 stylewidth: 120px; margin-left: 10px el-option label全部 value / el-option label在售 valueon / el-option label已下架 valueoff / el-option label已售出 valuesold / /el-select el-button typeprimary clickfetchGoods stylemargin-left: 10px查询/el-button /div el-table :datagoodsList stripe stylewidth: 100% el-table-column proptitle label商品标题 width250 / el-table-column propowner.username label发布人 width120 / el-table-column propcampus.name label所属学校 width120 / el-table-column propprice label价格 width100 / el-table-column propstatus label状态 width100 template #defaultscope el-tag :typescope.row.status on ? success : info {{ statusMap[scope.row.status] }} /el-tag /template /el-table-column el-table-column label操作 width180 template #defaultscope el-button sizesmall clickhandleApprove(scope.row.id)通过/el-button el-button sizesmall typedanger clickhandleReject(scope.row.id)拒绝/el-button /template /el-table-column /el-table div classpagination stylemargin-top: 20px; text-align: right el-pagination v-model:current-pagepage v-model:page-sizepageSize :page-sizes[10, 20, 50] layouttotal, sizes, prev, pager, next, jumper :totaltotal size-changehandleSizeChange current-changehandleCurrentChange / /div /el-card /template script setup import { ref, onMounted } from vue import request from /utils/request const goodsList ref([]) const total ref(0) const page ref(1) const pageSize ref(10) const searchTitle ref() const filterStatus ref() const statusMap { on: 在售, off: 已下架, sold: 已售出 } const fetchGoods async () { const params { page: page.value, page_size: pageSize.value, title: searchTitle.value, status: filterStatus.value } const res await request.get(goods/, { params }) goodsList.value res.data.results total.value res.data.count } const handleApprove async (id) { await request.patch(goods/${id}/, { status: on }) fetchGoods() } const handleReject async (id) { await request.patch(goods/${id}/, { status: off }) fetchGoods() } const handleSizeChange (val) { pageSize.value val fetchGoods() } const handleCurrentChange (val) { page.value val fetchGoods() } onMounted(() { fetchGoods() }) /script表格设计要点el-table-column中propowner.username直接穿透关联模型Django REST Framework 序列化器需显式定义owner_username serializers.CharField(sourceowner.username, read_onlyTrue)statusMap对象将数据库值映射为中文标签避免模板中写v-if判断handleApprove/handleReject使用PATCH修改单个字段符合 REST 规范且 Django 视图中只需partial_update即可。4.3 数据导出功能生成 Excel 并触发浏览器下载校园管理员常需导出「本月成交商品清单」用于财务对账。后端提供/api/export/goods/接口返回 CSV 或 Excel# users/views.py import csv from django.http import HttpResponse from openpyxl import Workbook from openpyxl.styles import Font from io import BytesIO api_view([GET]) def export_goods_to_excel(request): campus_code request.query_params.get(campus) queryset Goods.objects.filter(statussold) if campus_code: queryset queryset.filter(campus__codecampus_code) output BytesIO() wb Workbook() ws wb.active ws.title 成交商品 # 表头 headers [商品标题, 价格, 发布人, 学校, 成交时间] for col_num, header in enumerate(headers, 1): cell ws.cell(row1, columncol_num, valueheader) cell.font Font(boldTrue) # 数据行 for row_num, goods in enumerate(queryset, 2): ws.cell(rowrow_num, column1, valuegoods.title) ws.cell(rowrow_num, column2, valuefloat(goods.price)) ws.cell(rowrow_num, column3, valuegoods.owner.username) ws.cell(rowrow_num, column4, valuegoods.campus.name) ws.cell(rowrow_num, column5, valuegoods.updated_at.strftime(%Y-%m-%d %H:%M)) wb.save(output) output.seek(0) response HttpResponse( output.read(), content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet ) response[Content-Disposition] attachment; filenamecampus_goods_export.xlsx return response前端调用const handleExport () { const url http://127.0.0.1:8000/api/export/goods/?campus${selectedCampus}; const link document.createElement(a) link.href url link.download campus_goods_export.xlsx document.body.appendChild(link) link.click() document.body.removeChild(link) }注意Django 默认不支持大文件流式响应此方法适用于千条以内数据若需导出万级数据应改用 Celery 异步任务 文件存储 下载链接返回。5. 数据库脚本与部署避坑MySQL 字符集、索引失效与宝塔部署实操5.1 完整数据库初始化脚本含表结构、基础数据与索引优化database_init.sql必须包含三类语句建库建表、插入默认学校、创建关键索引。以下是精简版核心片段-- 1. 创建数据库显式指定字符集 CREATE DATABASE IF NOT EXISTS campus_market_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE campus_market_db; -- 2. 创建表省略字段定义仅示例关键约束 CREATE TABLE goods ( id int NOT NULL AUTO_INCREMENT, owner_id int NOT NULL, campus_id int NOT NULL, title varchar(100) NOT NULL, price decimal(10,2) NOT NULL CHECK (price 0.01), status varchar(10) NOT NULL DEFAULT on, created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), updated_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY (id), KEY goods_campus_id_status_idx (campus_id,status), KEY goods_owner_id_status_idx (owner_id,status), CONSTRAINT goods_campus_id_9e3b1c1a_fk_campus_id FOREIGN KEY (campus_id) REFERENCES campus (id) ON DELETE PROTECT, CONSTRAINT goods_owner_id_5a3b2d4e_fk_auth_user_id FOREIGN KEY (owner_id) REFERENCES auth_user (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 3. 插入默认学校避免空数据导致小程序崩溃 INSERT INTO campus (name, code) VALUES (北京航空航天大学, BUAA-2024), (北京理工大学, BIT-2024), (清华大学, THU-2024); -- 4. 创建全文索引用于商品标题模糊搜索 ALTER TABLE goods ADD FULLTEXT(title, description);执行顺序与验证先mysql -u root -p database_init.sql导入登录 MySQL 执行SHOW CREATE TABLE goods\G确认ENGINEInnoDB和CHARSETutf8mb4存在运行EXPLAIN SELECT * FROM goods WHERE campus_id1 AND statuson;检查key列是否命中goods_campus_id_status_idx。5.2 宝塔面板部署 DjangoNginx Gunicorn Supervisor 配置详解本地开发用python manage.py runserver但生产环境必须用 Gunicorn Nginx。宝塔面板操作步骤安装 Python 项目管理器宝塔 → 软件商店 → 搜索「Python 项目」→ 安装添加项目项目名称campus-marketPython 版本3.10与开发环境一致项目路径/www/wwwroot/campus-market启动文件manage.py启动命令gunicorn campus_market.wsgi:application -c gunicorn.conf.py配置gunicorn.conf.py放在项目根目录import multiprocessing bind 127.0.0.1:8001 # Gunicorn 监听端口 bind_mode tcp workers multiprocessing.cpu_count() * 2 1 worker_class sync worker_connections 1000 max_requests 1000 max_requests_jitter 100 timeout 30 keepalive 5 preload True daemon False raw_env [] pidfile /www/wwwroot/campus-market/gunicorn.pid loglevel info access_log_format %(h)s %(l)s %(u)s %(t)s %(r)s %(s)s %(b)s %(f)s %(a)s accesslog /www/wwwroot/campus-market/logs/access.log errorlog /www/wwwroot/campus-market/logs/error.logNginx 反向代理配置宝塔 → 网站 → 设置 → 反向代理代理名称Django API目标URLhttp://127.0.0.1:8001启用缓存否API 不缓存SSL开启绑定域名后强制 HTTPSSupervisor 进程守护宝塔 → 软件商店 → Supervisor → 添加进程名称campus-market-gunicorn启动命令/www/wwwroot/campus-market/venv/bin/gunicorn campus_market.wsgi:application -c /www/wwwroot/campus-market/gunicorn.conf.py运行目录/www/wwwroot/campus-market用户www宝塔默认 Web 用户关键验证点访问https://your-domain.com/api/goods/应返回 JSON状态码 200查看宝塔「Supervisor」页面进程状态为RUNNING查看/www/wwwroot/campus-market/logs/error.log无ImportError或OperationalError。5.3 常见索引失效场景与修复LIKE 查询、函数索引与统计信息更新校园二手平台上线后管理员反馈「按标题搜索变慢」。排查发现 SQL 为SELECT * FROM goods WHERE title LIKE %手机%;该语句无法使用普通 BTree 索引因%在开头导致最左匹配失效。解决方案有三方案适用场景操作命令全文索引标题/描述模糊匹配支持自然语言搜索ALTER TABLE goods ADD FULLTEXT(title, description);SELECT * FROM goods WHERE MATCH(title, description) AGAINST(手机 IN NATURAL LANGUAGE MODE);生成列 函数索引MySQL 8.0精确前缀匹配如title LIKE iPhone%ALTER TABLE goods ADD COLUMN title_prefix VARCHAR(20) STORED AS (SUBSTRING(title, 1, 20));CREATE INDEX idx_title_prefix ON goods(title_prefix);Elasticsearch 替代百万级商品需复杂检索拼音、同义词、拼写纠错需额外部署 ES 集群超出毕设范围执行全文索引后还需更新统计信息使优化器生效ANALYZE TABLE goods;提示ANALYZE TABLE会扫描表并更新索引基数影响查询计划选择生产环境建议在低峰期执行。最后验证全文索引效果-- 查看执行计划 EXPLAIN SELECT * FROM goods WHERE MATCH(title, description) AGAINST(笔记本电脑 IN NATURAL LANGUAGE MODE)\G输出中type: fulltext表示成功走全文索引rows值应远小于全表行数。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价