/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
又到了一年毕业设计的高峰季,后台被问得最多的就是“小程序类题目怎么做出亮点”。说实话,领养流浪动物这个主题本身不新,但把它和Python后端、爬虫采集、数据可视化串成一个完整闭环,就是一套可以直接演示的B级项目。这篇文章我把整个项目的设计思路、表结构、接口实现、小程序端交互、爬虫合规方案、避坑点和答辩预案全部拆开讲,代码和流程都是可以直接复制的。
1. 项目整体拆解:Python + 微信小程序 + 爬虫 + 数据可视化怎么组合成毕业设计
1.1 技术选型逻辑:为什么后端用Python而不是直接全站小程序
很多同学拿到这个题目后的第一反应是“小程序不就是前端吗,哪来的后端”。实际上一套正经的领养小程序必须有三个部分:小程序端负责展示和交互,后端负责业务逻辑和数据存储,爬虫负责把外部公开的流浪动物信息持续喂给系统。三个模块就像流水线上的三个工位,缺一个项目都显得单薄。
后端选Python的原因很直接:Flask写接口快,BeautifulSoup写爬虫顺手,PyECharts做可视化现成。整个项目用Python一套语言串起来,答辩时讲“统一技术栈”这个理由也站得住。我的建议是Python 3.10 + Flask 2.x + MySQL 8 + SQLAlchemy组合,这套方案在Windows和Linux上都能跑,部署也不折腾。有人说Django功能全,但毕业设计用Flask更轻,代码量更少,而且每个接口都可以对着讲清楚“请求进来→参数校验→业务处理→返回JSON”这条链路。
这里有个容易犯的决策错误:一上来就纠结用uni-app还是原生小程序。我的经验是,如果你的项目要求“毕业设计答辩现场能跑通”,原生小程序永远是最稳的,微信开发者工具打开就能预览,不用额外装HBuilderX,也不会出现编译环境不一致的问题。uni-app确实能兼顾多端,但多一次转译就多一层未知风险,冲刺阶段不值得冒这个险。
1.2 系统架构和功能清单:一个闭环项目应该包含哪些能力
这个项目的核心业务闭环是“信息采集→展示→申请→审核→数据统计”。爬虫从公开渠道采集流浪动物信息存入MySQL,小程序端分页展示宠物卡片,用户浏览详情后提交领养申请,管理员在后台审核,最后所有数据流向可视化大屏,展示领养趋势、宠物类型分布、地区分布。这个闭环里每一个环节都能单独截图放进论文,评委一看就知道你做了完整的系统设计,而不是只交了个静态页面。
功能清单我按模块拆开:
- 用户端:微信登录、查看宠物列表(分类筛选+分页加载)、宠物详情、提交领养申请、我的申请记录、个人资料编辑
- 管理端:宠物信息管理(手动新增/编辑/下架)、领养申请审核(通过/驳回)、公告资讯发布
- 数据模块:爬虫定时采集、入库去重、数据可视化统计
- 支撑模块:图片上传存储、用户会话管理、接口鉴权
这个清单看起来功能多,但真正落地的核心其实只有“宠物列表、详情、申请、审核、统计”这五个动作。写代码的时候一定按这个优先级来,先把主链路跑通,再补辅助功能。我见过太多同学先做个人中心、先调UI,结果申请流程还没走通就快交稿了,这种做法非常危险。
关于“全套文案”,其实就是把论文目录和系统功能对应起来。系统设计章节写架构图和数据表,核心功能章节按用户端和管理端分开写,测试章节放接口调试截图和真机效果图,爬虫和可视化单独成章。这样论文目录结构清晰,查重的时候也不容易和大路货撞车。
2. 后端与数据库:核心模块怎么设计才不返工
2.1 表结构设计:5张表就能跑通领养闭环
数据库设计是答辩评委喜欢盯的第一个点。表结构不要贪多,我最终落地的就5张表:用户表、宠物表、领养申请表、资讯表、管理员表。下面这份建表SQL是实测调过索引的,可以直接执行。
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`openid` varchar(64) NOT NULL COMMENT '微信openid',
`nickname` varchar(64) DEFAULT '' COMMENT '昵称',
`avatar` varchar(255) DEFAULT '' COMMENT '头像',
`phone` varchar(20) DEFAULT '' COMMENT '手机号',
`role` tinyint(4) DEFAULT 0 COMMENT '0普通用户 1管理员',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_openid` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `pets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '宠物标题',
`pet_type` tinyint(4) DEFAULT 0 COMMENT '0猫 1狗 2其他',
`gender` tinyint(4) DEFAULT 0 COMMENT '0未知 1公 2母',
`age` varchar(20) DEFAULT '' COMMENT '年龄段',
`vaccine` tinyint(4) DEFAULT 0 COMMENT '0未疫苗 1已疫苗',
`address` varchar(128) DEFAULT '' COMMENT '所在地区',
`description` text COMMENT '详细介绍',
`cover` varchar(255) DEFAULT '' COMMENT '封面图',
`images` text COMMENT '多图JSON',
`status` tinyint(4) DEFAULT 0 COMMENT '0待领养 1申请中 2已领养',
`source` varchar(20) DEFAULT 'manual' COMMENT 'manual手动 spider爬虫',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_type_status` (`pet_type`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
领养申请表是业务核心,字段不要只存pet_id和user_id,还要冗余申请人的姓名、电话、住址、申请理由。这样管理员审核时不需要连表查两次才能看到基本信息,答辩演示也更直观。
CREATE TABLE `adoptions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pet_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`name` varchar(30) NOT NULL COMMENT '申请人姓名',
`phone` varchar(20) NOT NULL COMMENT '联系电话',
`address` varchar(200) DEFAULT '' COMMENT '居住地址',
`reason` text COMMENT '申请理由',
`status` tinyint(4) DEFAULT 0 COMMENT '0待审核 1已通过 2已驳回',
`reply` varchar(255) DEFAULT '' COMMENT '审核回复',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
KEY `idx_pet` (`pet_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
要注意的是,宠物表设计时经常被忽略的两个字段是
source
和
status
。
source
用来区分这条数据是爬虫来的还是管理员手动录的,方便后期做数据追溯,答辩时评委问“这条数据是哪来的”你可以直接查库说话。
status
的三种状态联动很关键:宠物是待领养、申请中还是已领养,直接决定它在小程序端是否展示、是否还能被申请。如果漏了这个状态机,会出现同一只宠物被多人同时申请的脏数据。
2.2 API接口设计:小程序调用后端的11个关键接口
后端接口设计不需要RESTful教条化,但命名要直观。我列一下这套项目最终定稿的接口清单,小程序端全部调这些:
| /api/login | POST | 微信登录换取openid | code |
| /api/pets | GET | 宠物列表(分页+筛选) | page, size, pet_type, status |
| /api/pets/{id} | GET | 宠物详情 | – |
| /api/adoptions | POST | 提交领养申请 | pet_id, name, phone, reason |
| /api/adoptions/mine | GET | 我的申请记录 | – |
| /api/adoptions/{id}/cancel | POST | 撤销申请 | – |
| /api/admin/pets | POST | 管理员新增宠物 | 表单数据 |
| /api/admin/adoptions | GET | 审核列表 | status |
| /api/admin/adoptions/{id}/audit | POST | 审核通过/驳回 | status, reply |
| /api/stats/overview | GET | 可视化统计 | – |
| /api/news | GET | 爬虫资讯列表 | page |
其中
/api/login
是整个系统的入口。流程是这样的:小程序端调用
wx.login()
拿到临时code,通过我们自己后端的
/api/login
传到微信接口换取
openid
和
session_key
,后端用
openid
查库,没有就自动注册一个新用户,有就直接返回自定义登录态token。这个token后续所有请求都带在请求头里,后端解析出来就知道当前用户是谁了。
要特别提醒一点:
session_key
是敏感信息,千万不能返回到小程序端。有些教程会把session_key存在小程序本地,这是严重的隐患。正确做法是session_key只留在后端数据库或缓存里,前端拿到的是你自己生成的token,有效期可以设置7天。答辩评委如果问起安全性,这是一个很加分的点。
2.3 微信登录和获取手机号:最容易翻车的一环
很多同学做小程序都会在“获取手机号”这一步卡住。这里要先泼盆冷水:微信官方从2023年开始把
wx.getPhoneNumber
改成了收费接口,个人主体小程序根本拿不到手机号明文,企业主体也需要先认证、开通相应的接口权限。所以如果你的预算有限,别死磕手机号,领养申请时让用户手动填手机号就够用了,这完全不影响业务闭环。
如果确实要接手机号快速填充,现在的正确路径是:在button上设置
open-type="getPhoneNumber"
,用户点击授权后拿到一个
code
,把
code
传到后端,后端用这个code调微信接口换取手机号密文,再配合session_key解密。解密部分Python代码如下,可以直接跑:
import base64, json
from Crypto.Cipher import AES
def decrypt_phone(session_key, encrypted_data, iv):
session_key = base64.b64decode(session_key)
encrypted_data = base64.b64decode(encrypted_data)
iv = base64.b64decode(iv)
cipher = AES.new(session_key, AES.MODE_CBC, iv)
decrypted = json.loads(cipher.decrypt(encrypted_data))
if decrypted.get('watermark', {}).get('appid') != const.APP_ID:
raise Exception('水印校验失败')
return decrypted['phone_info']['phoneNumber']
这里最坑的点有三个:第一是AES解密后一定要校验
watermark.appid
,否则数据完整性无法保证;第二是
code
只能用一次,5分钟内有效,后端拿到后必须立即使用;第三是加密数据里的手机号字段是
phone_info.phoneNumber
,不是顶层
phoneNumber
,这个字段层级错位我见过的报错率极高。另外
pycryptodome
库的安装也有讲究,直接
pip install pycryptodome
,不要在Windows上用
Crypto
这个老包名,导入时用
from Crypto.Cipher import AES
。
3. 小程序端与爬虫联动:数据怎么来、怎么展示
3.1 小程序端页面设计:首页信息流、详情页、申请页怎么搭
小程序端的结构我建议四个主页面:首页(宠物信息流)、详情页、申请页、个人中心。再加一个数据大屏页,用ECharts渲染统计图表。底部TabBar只需要首页和个人中心两个入口,详情和申请都从首页跳进去,这样路径最短,用户操作成本最低。
首页是整个项目的脸面,老式做法是一屏到底的静态列表,我这里用的是“分类Tab + 上拉加载更多”的组合。顶部分类Tab固定为“全部、猫咪、狗狗、其他”,点击切换时重新请求第一页数据。列表滚动到底部时触发
onReachBottom
,继续请求下一页追加到末尾。对应的JS逻辑我贴一下这个分页核心:
Page({
data: {
list: [],
page: 1,
size: 10,
petType: '',
loading: false,
finished: false
},
async loadPets(reset = false) {
if (this.data.loading) return;
if (reset) {
this.setData({ page: 1, finished: false });
}
if (this.data.finished) return;
this.setData({ loading: true });
const res = await axios.get('/api/pets', {
params: {
page: this.data.page,
size: this.data.size,
pet_type: this.data.petType
}
});
const newList = reset ? res.data.list : this.data.list.concat(res.data.list);
this.setData({
list: newList,
loading: false,
finished: newList.length >= res.data.total
});
},
onReachBottom() {
if (this.data.finished) return;
this.setData({ page: this.data.page + 1 });
this.loadPets();
}
});
这段逻辑里最容易被忽略的是
loading
状态锁和
finished
标志位。不加上锁,用户在列表快速滑动时会连续触发多个请求,出现数据重复;不加finished判断,滚到底部会一直发无效请求,真机调试时能看到控制台刷屏。很多初学者写分页只写“page+1”,没有这两个开关,演示时滑动稍快页面就乱了。
详情页要注意图片预览和状态判断。如果这只宠物的
status
已经是“申请中”或“已领养”,按钮应该置灰不可点,并显示当前状态。图片预览用
wx.previewImage
监听图片的
tap
事件就可以了,代码量不大但很体现细节。申请页就是一个表单页,收集姓名、电话、地址、申请理由,提交前做一轮必填校验,手机号用正则
/^1[3-9]\\d{9}$/
过滤,别等后端返回400才发现前端没校验,答辩现场网络开小差时这种低级错误特别尴尬。
3.2 爬虫模块:合规抓取公开救助信息并自动入库
爬虫是这个项目区别于普通增删改查题目的核心亮点,也是答辩加分项。但必须强调,爬虫模块只能针对公开信息、公益性质的救助信息平台,采集前先看目标网站的
robots.txt
,严格遵守网站的访问频率限制,不采集个人隐私信息,数据仅用于学习演示。这是底线问题,论文里也要专门写一节“数据采集合规性说明”,评委非常看重这个。
我采用的方案是基于requests + BeautifulSoup的轻量爬虫,而不是一上来就上Scrapy。原因很简单:Scrapy的工程结构和中间件对毕设来说太重,而且演示时只需要跑一条命令抓取一批数据就行,不需要分布式、不需要调度器。核心代码如下所示,目标站点以“某个动物保护公益页面”举例,字段是标题、宠物类型、描述、封面图片、来源地址:
import time
import requests
from bs4 import BeautifulSoup
def crawl_list(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
resp = requests.get(url, headers=headers, timeout=10)
resp.encoding = resp.apparent_encoding
soup = BeautifulSoup(resp.text, 'html.parser')
items = []
for card in soup.select('.pet-card'):
item = {
'title': card.select_one('.title').get_text(strip=True),
'type': 'cat' if '猫' in item_title else 'dog',
'description': card.select_one('.desc').get_text(strip=True),
'cover': card.select_one('img')['src'],
'source_url': card.select_one('a')['href'],
'source': 'spider'
}
items.append(item)
time.sleep(0.5)
return items
这里的
time.sleep(0.5)
不是可有可无的,它是“对目标服务器友好”的最直接体现。有些同学爬下来数据后被目标网站封IP,十有八九就是没限速、请求头又带了爬虫默认标识。另外
resp.encoding = resp.apparent_encoding
这行的作用很实际,很多中文站页面没有明确指定编码,不带这行抓回来全是乱码,往数据库里一存就是一堆问号,排查起来非常头疼。
抓回来的数据入库前要做三件事:去重、清洗、状态初始化。去重逻辑可以简单按
source_url
的唯一索引来判断,数据库已经有这条链接就跳过。清洗主要是把HTML标签、多余空白、首尾空格全部清掉,图片如果是相对路径要拼成绝对地址。状态初始化就是所有爬虫入库的数据
status
默认0(待领养),万一抓到一条已经被领养的信息,管理员可以在后台手动下架,这就是前面设计
source
字段的价值所在,数据链路全程可追溯。
3.3 数据可视化大屏:ECharts动态统计领养数据
数据可视化部分我强烈建议先用PyECharts在Python侧生成数据,再在小程序端渲染,而不是直接在Python里生成HTML图表。原因有两个层面。第一,小程序里没法直接打开一个独立的HTML文件用浏览器渲染(web-view又有域名白名单限制,个人主体还受限),而ECharts提供了小程序专用版
echarts-for-weixin
组件,可以把图表作为组件直接嵌入页面,这是最稳的展示方式。第二,数据结论能实时从后端接口拉取,评委问“这个数字是哪来的”你能现场演示接口返回的数据和图表联动。
大屏页我放了四个图表:宠物类型分布饼图、每周新增领养数折线图、领养申请状态占比环形图、地区分布柱状图。数据全部来自
/api/stats/overview
接口,后端通过SQL的
GROUP BY
统计后返回JSON,小程序端用ec-canvas依次渲染。折线图的平滑曲线、饼图图例字体大小这些细节调一下,截图放进论文里会好看很多,视觉上也会给答辩PPT加分。
这里有个实操经验:用ec-canvas的时候,图表option定义在
canvas
的
onInit
回调里,如果数据是通过
wx.request
异步拿到的,必须先
init
拿到实例,再通过
setOption
更新数据。很多同学把option直接写在页面
onLoad
里,结果图表白屏或者只有骨架线。另外每个图表组件实例最好单独封装,不要一页塞多个canvas却共用一个instance变量,那会导致只有最后一个图表有数据。
4. 实操过程与避坑实录
4.1 本地联调三步走:域名、HTTPS、跨域一次解决
小程序和普通网页最大的区别在于:网页可以直接访问
http://localhost:8080
,但小程序真机预览只能访问
https
域名。很多同学第一次真机预览时列表空白,就是这个原因。解决办法分三步:
第一步,开发阶段在微信开发者工具右上角“详情→本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”。这是自测阶段最快的方式,直接请求本地
http://127.0.0.1:5000
。
第二步,后端跨域配置。Flask层面加flask-cors扩展,一句
CORS(app)
搞定。不加的话小程序端虽然不受浏览器同源策略限制,但如果你用H5页面嵌入过web-view,就会出现跨域报错。我的习惯是联调阶段就先把CORS配置好,免得后面还要回头补。
第三步,真机预览时解决域名问题。有两个路径:一是买一台云服务器部署后端,用Nginx反代到80/443端口,配一个备案过的域名和免费SSL证书;二是继续用开发者工具预览,只有体验版和正式版才强制要求合法域名。毕设答辩通常用开发者工具演示就足够了,但如果老师要求看手机真机效果,云服务器部署这套动作必须提前三天开始准备,备案和证书审核都是要时间的。
4.2 高频报错速查表:小程序真机调试的7个典型问题
我把实际操作中踩过的高频问题整理成了一张速查表,希望对你有用。
| 真机预览请求一直pending | 域名未校验或没配HTTPS | 勾选不校验域名,或部署HTTPS |
| wx.request报url not in domain list | request合法域名没配 | 后台“开发→开发管理→服务器域名”添加 |
| 获取不到openid,返回400 | code过期或重复使用 | 每次wx.login后立即传后端 |
| 页面跳转后返回时列表位置丢失 | 页面栈被销毁 | 详情页返回用navigateBack,首页用onShow刷新 |
| 图片在真机不显示 | 图片域名未加入downloadFile合法域名 | 后台配置downloadFile合法域名 |
| 导航栏在iPhone刘海屏顶部重叠 | 未适配安全区 |
用
wx.getMenuButtonBoundingClientRect() 动态计算导航栏高度 |
| 获取手机号按钮点击没反应 | 个人主体无权限 | 改用表单手动填写手机号 |
这里面“顶部导航栏高度适配”是个特别容易被忽视的细节。安卓和iOS胶囊按钮的位置不一样,刘海屏和非刘海屏也不一样,如果页面里有自定义顶部导航,不能用固定值。我当时用这套代码动态计算:
const menuRect = wx.getMenuButtonBoundingClientRect();
const navHeight = (menuRect.top – statusBarHeight) * 2 + menuRect.height;
这段代码配合
wx.getSystemInfoSync()
获取状态栏高度,就能在不同机型上算出适配的导航栏高度。这个细节评委老师不一定知道,但展示出来会显得你很专业。
4.3 实操心得:这3个坑我几乎每个项目都遇到
第一个坑是Python后端部署在Windows上时的编码问题。Windows控制台默认GBK编码,Flask日志打印中文时偶尔会报
UnicodeEncodeError
,但问题往往不在报错本身,而是日志中断导致你误以为接口挂了。我实测下来的解法是:Python文件第一行注释写
# coding: utf-8
不够,要在启动入口加上标准库
io
重定向。但更省事的做法是别在Windows上做生产部署,直接租一台Linux云服务器,一组
systemctl
命令就能守护Flask进程,演示时随时重启,不丢人。
第二个坑是MySQL字段类型和Python类型的隐式转换。SQLAlchemy查询出来的
Decimal
金额字段或
DATETIME
字段,序列化成JSON时必须处理,否则前端拿到的是字符串或报
TypeError: Object of type datetime is not JSON serializable
。解决办法是给Flask的
json_encoder
扩展一下,把datetime转成字符串,把Decimal转成float。答辩现场如果前端控制台飘红,这个低级错误是很影响印象分的。
第三个坑是爬虫入库的数据和小程序端状态不同步。爬虫抓来的数据如果直接入库后status都是0,管理员端如果不审核,用户看到的宠物列表就会有一堆无人认领的过期数据。我实际操作中的做法是:爬虫单独设置一个
spider_pending
状态,管理员后台专门做一个“爬虫数据审核”Tab,一键确认入库或批量删除。这样既保证了数据源的可控性,论文里也能多写一个“多源数据审核机制”的亮点,一举两得。
5. 从Python扩展到Java/PHP和其他语言的改写思路
5.1 Java版改写要点:Spring Boot + MyBatis-Plus如何移植
这个题目在交易市场里常被要求“可做Java版”,因为很多学校的毕业设计要求指定Java技术栈。从Python版改写Java版,核心不是代码翻译,而是架构理念的转换。Flask的蓝图和装饰器路由,在Spring Boot里对应的是Controller层的
@RestController
和
@RequestMapping
注解。原来的10个API接口,在Java版里按业务拆成
AuthController
、
PetController
、
AdoptionController
、
StatsController
四个控制类,数据访问层直接用MyBatis-Plus的
BaseMapper
,连SQL都不用写,比Python版还快。
这里最需要下功夫的是统一返回体和异常处理。Python版可以直接
return jsonify(data)
,Spring Boot则建议封装一个
Result<T>
类,包含
code
、
message
、
data
三个字段,全项目统一使用。配套地用
@ControllerAdvice
做全局异常拦截,业务异常统一抛
BusinessException
。这套东西一加上,代码量会多一些,但论文的系统设计章节能写的技术点立刻翻倍,“统一异常处理”“统一响应封装”这些都是评委觉得你“懂工程”的标志。
Java版和Python版的一大差异在并发处理上。领养申请接口要防止同一只宠物被并发申请两次,Python里靠数据库唯一索引或者
SELECT … FOR UPDATE
,Java版则可以加
@Transactional
事务注解配合行锁实现。答辩时如果能主动讲出“我用事务保证了一人一宠的一致性”,这个项目的技术深度评价会高一个档次。
5.2 PHP/其他语言方向的扩展建议
如果要求做PHP版,技术栈建议用ThinkPHP 8或Laravel 11。改写的思路和Java版类似,但PHP的部署验收更依赖环境一致性,一定要用
phpstudy
或Docker固定好PHP版本、Nginx配置、MySQL版本。PHP版的一个优势是虚拟主机部署简单,很多学校自己有PHP空间,演示时直接传上去就能访问。
至于C#和C++,说实话这两个语言直接做小程序后端都不算顺手。但毕设题目的玩法是可以灵活的:C#可以用ASP.NET Core做同样的后端API,思路和Java版几乎一一对应;C++则更适合给这个项目做“周边模块”,比如用C++写一个推荐算法模块,根据用户浏览记录推荐偏好类型的宠物,再通过Python后端的子进程调用它,形成一个“Python主系统+C++高算力组件”的混合架构。这种设计在答辩时相当亮眼,因为它展示的是架构设计能力而不是单纯的CRUD。
APP方向的话,如果学校要求的是Android端,可以用Flutter或uniapp直接把现有小程序包成APP,后端接口完全复用,只需要处理两种差异:登录方式从微信登录换成手机号+验证码登录,推送从订阅消息换成极光推送。这个方法很成熟,一两天就能完成客户端改造。
5.3 答辩前的追问预案:评委最爱问的6个问题
最后一个环节聊答辩预案。评委面对小程序类项目,翻来覆去问的就是这几类问题,提前准备好回答方向,现场就不慌。
第一问:“你的项目数据从哪来?”回答要直接:人工录入加爬虫采集,爬虫只采公开信息、遵守robots协议、做了频率限制、不碰隐私数据。把前面代码里的
time.sleep(0.5)
摆出来,这就是合规的证据。
第二问:“微信登录为什么能识别用户身份?”这个问题的核心是openid机制,讲清楚“一个用户对一个openid,同一人在不同小程序下openid不同、同一小程序下openid唯一”,再提一下token鉴权流程,基本就过关了。
第三问:“系统如何防止爬虫数据入库后重复?”对应设计里的source_url唯一索引和标题相似度比对,直接说“以来源链接作为唯一键,重复抓取时数据库会拒绝写入”完全够用。
第四问:“如果同一只宠物同时被两个人申请怎么办?”这问的是并发控制。我实测的做法是:提交申请时和宠物表状态做原子性校验,更新条件带上
WHERE id=? AND status=0
,受影响行数为0就直接返回“已被他人抢先一步”,这是一行SQL层面的乐观锁方案,很好讲。
第五问:“小程序端刷新时为什么数据不会丢失?”可以讲一下
onLoad
、
onShow
的生命周期差异,以及页面数据缓存策略,主要是表达你理解小程序页面栈和生命周期。
第六问:“你做过性能优化吗?”这个问题不用夸海口,把分页加载、图片懒加载、SQL索引设计、静态资源CDN这些点逐个说一遍即可。评委其实不指望毕业生真的做过压测,他们想听的是你有没有性能意识。
我个人在带这个项目时最深的体会是:毕设项目不怕功能少,就怕链路断。只要“爬虫入库→小程序展示→用户申请→后台审核→数据统计”这条主线是通顺的,就算其他模块粗糙一点,答辩基本都能稳稳通过。反过来,如果功能清单列了一大堆,现场演示却从首页就白屏,那才是真正的灾难。所以写代码的顺序一定是从主链路往分支走,先把demo跑通,再回头补细节。最后再分享一个小技巧,可视化大屏的图表配色直接用ECharts自带主题,数据统计接口里多给前端返回一个
last_update_time
字段,大屏页面把这个时间打出来,评委看到实时刷新的数据时,对项目的真实运行状态会更加信任。




