考研这条路上信息差往往比努力更能拉开差距。我去年帮一个考研社群做了一套研究生之路考研学习交流微信小程序前端用 vue 语法写的 uniapp 编译成微信小程序后端用 Python 提供接口目标很直接把分散在群聊、网盘、公众号里的学习资料和备考经验沉淀到一个能打卡、能刷题、能交流的独立空间里。整个项目前后大概花了三周业余时间第一周做需求拆解和数据库设计第二周搭前端页面和处理接口联调第三周集中处理登录、文件上传、内容审核这些小程序的合规门槛。这篇博文我会把从选型到落地的完整过程写出来不是那种只贴几个代码片段就完事的教程而是把每步背后的取舍、踩过的坑、以及可以直接照抄的实现方案都放进来。无论你是想做一个类似的学习类小程序还是打算把 vue uniapp Python 这套组合用在其他业务场景都值得看下去。1. 项目整体定位与方案选型1.1 为什么选 uniapp 而不是原生小程序先说结论如果你和我一样团队里只有前端没有专门的小程序开发或者你做完小程序之后还想要一个可以丢到应用商店的 App 版本、一个能被搜索引擎收录的 H5 站点那 uniapp 几乎是当前成本最低的选择。我之前做过原生微信小程序项目写了一堆 wx.request、wx.login、wx.navigateTo后面想再出一个 H5 版本几乎等于把页面层重写一遍。这次做考研学习交流系统我直接把跨端需求提前考虑了。uniapp 的技术底座是 Vue你可以用 Vue 2 的选项式 API也可以用 Vue 3 的组合式 API 加 TypeScript。由于我准备长期维护这个系统这次选的是 Vue 3 Vite TypeScript 的组合。项目创建时直接跑npx degit dcloudio/uni-preset-vue#vite-ts graduate-road cd graduate-road npm install npm run dev:mp-weixin编译完成后用微信开发者工具导入项目根目录下的dist/dev/mp-weixin文件夹即可。注意这里的坑导入的是dist/dev/mp-weixin不是项目根目录。我第一次用的时候把根目录导进去结果开发者工具直接提示找不到 app.json查了半天才发现是导入路径选错了。用 uniapp 的好处我用一个表格列清楚方便你对比维度原生微信小程序uniapp学习成本必须掌握小程序专属语法Vue 语法直接迁移多端支持仅微信微信/支付宝/抖音/H5/App组件生态官方组件 第三方uview、uni-ui 等条件编译不支持支持跨端差异化代码调试方式开发者工具CLI 开发者工具对于考研学习交流系统这种内容型产品核心页面首页、资料列表、社区帖子、我的基本都是列表加详情加表单的组合逻辑复杂度不高但页面数量多。用 uniapp 的页面路由和组件复用能明显减少重复代码。而且后面如果想做 App 端编译产物直接就是原生应用上线成本比找原生开发低一大截。1.2 后端为什么是 Python 配 Flask后端没有选 Java 或 Node而是用 Python Flask主要有三个考虑。第一开发速度。考研学习交流系统的接口数量前期大概 30 个左右用 Flask 的蓝图机制拆模块后一个接口从定义路由到调试完往往一个下午能搞定三四个。Flask 不像 Django 那样自带全家桶但也正因为它轻启动快、排错直观对小程序这种纯 API 后端非常合适。第二Python 的生态可以往智能方向扩展。考研数据的价值不只是展示比如根据错题频率推荐相同知识点的题目用传统 SQL 也能做但想做更细的相似度推荐或者把院校报录比、分数线做成统计分析Python 这边有 pandas、numpy 和现成的机器学习库后续扩展不用换技术栈。第三团队的技术舒适区。很多做微信小程序的个人开发者或小团队后端基础往往是 Python因为写脚本、做爬虫、处理数据的习惯都在 Python 上。让一个写 Flask 的人去接 Node 或者 Java光环境配置和框架理解就要耗掉不少时间。我知道有人会建议 FastAPI它自带 OpenAPI 文档和异步支持确实更适合高并发场景。但考研学习交流系统的并发峰值大概就是晚上 9 点到 11 点的打卡和刷题时段Flask 跑在 gunicorn 多进程后面完全够用。我的选择是后面如果接口要承担更大的流量再替换成 FastAPI 的成本也不高蓝图的路由定义方式是兼容的。环境准备上先建一个干净的 Python 虚拟环境避免把系统 Python 搞乱python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install flask flask-cors flask-sqlalchemy pyjwt requests redis pip freeze requirements.txt这里也把 Flask、FastAPI、Django 三个框架按项目需求比一下框架定位适合情况本项目是否选Flask轻量 API接口少、逻辑清晰、快速上线是FastAPI异步高性能 API高并发、重视接口文档备选Django全家桶需要后台管理、权限体系复杂不选1.3 整体架构与数据流向整个系统的链路很简单前端 uniapp 编译出的微信小程序发起 HTTPS 请求经过 Nginx 反向代理转发到 Flask 应用Flask 负责业务逻辑和鉴权数据写入 MySQL热数据打卡连续天数、首页倒计时配置放在 Redis。小程序端vue 组件、页面路由、API 请求封装、状态管理Pinia接口层Flask 蓝图、JWT 鉴权、统一异常处理数据层MySQL 存业务数据Redis 存 session 和计数部署层Nginx gunicorn supervisor上传文件存放于云服务器磁盘配合 CDN 加速这个架构没有引入微服务、消息队列这种重东西。原因很简单小项目最重要的是把业务跑通过度设计只会拖慢进度。等用户量起来再拆成本远低于一开始就把系统搭得很重。实际上这套架构支撑日活几千人没有任何问题真正需要扩容的往往是文件存储和带宽而不是应用本身。2. 需求拆解与核心业务设计2.1 功能需求拆解学习资源和交流社区两条主线考研学习交流系统表面上看是资料下载加帖子评论实际拆下来核心场景有五个我按重要程度排了序资料库。支持上传 pdf/word 等文档分类展示支持搜索和下载。这是吸引用户的第一入口也是留存的关键。刷题系统。公共课题目按章节和年份组织提供单题作答和整套练习两种模式自动判分并收集错题。打卡与统计。每天完成学习任务后打卡系统记录连续天数错题复习提醒也会在打卡后弹出。社区交流。帖子、评论、点赞、收藏。资料分享和上岸经验都沉淀在这里。个人中心。学习记录、错题列表、收藏、反馈入口。在用户画像上我把人群分成三类考研新手需要院校信息、公共课资料和入门规划备考主力需要刷题、错题回顾和打卡控制节奏上岸学生愿意分享笔记经验同时需要曝光和认可。这三类人在系统里形成闭环上岸学生贡献内容和资料备考主力消费内容并产生数据新手通过搜索进入社区。给团队定的 MVP 原则是先砍掉课程购买直播一对一咨询这些想得美但做不动的功能把资料、刷题、打卡、社区四件事做扎实。上线后根据用户反馈再迭代比一开始堆功能要稳健很多。实际上后来上线后用户最常用的也确实是资料下载和错题本而不是我心里预想的社区灌水。2.2 数据库表设计这是整个项目里最不能省的部分。表结构设计合理后面写接口会非常顺设计不合理后面每个接口都要带着别扭。我贴出几张核心表的 DDL可以直接照抄。用户表CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, openid VARCHAR(64) NOT NULL, nickname VARCHAR(32) DEFAULT 考研er, avatar_url VARCHAR(255) DEFAULT , phone VARCHAR(20) DEFAULT , target_school VARCHAR(64) DEFAULT , major VARCHAR(64) DEFAULT , create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;题目表CREATE TABLE question ( id INT NOT NULL AUTO_INCREMENT, subject TINYINT NOT NULL COMMENT 1政治 2英语 3数学 4专业课, chapter VARCHAR(32) DEFAULT , year SMALLINT DEFAULT NULL, content TEXT NOT NULL, options JSON DEFAULT NULL, answer VARCHAR(16) NOT NULL, analysis TEXT, type TINYINT DEFAULT 1 COMMENT 1单选 2多选 3大题, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_subject (subject) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;错题表CREATE TABLE wrong_book ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, question_id INT NOT NULL, wrong_answer VARCHAR(16), review_count INT DEFAULT 0, last_review_time DATETIME DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_question (user_id, question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;资料表、帖子表和评论表的设计思路基本一致无非是记录用户 ID、内容字段、状态字段和时间戳。这里提醒一个小细节content 和 JSON 类型的字段一定要用 utf8mb4 字符集否则用户发个生僻字或者 emoji写入 MySQL 直接报错。这个坑我踩过最后全表重建才发现是字符集的问题。2.3 接口设计规范接口规范在前后端联调之前就应该定好不然后面全是扯皮。我定的统一返回格式是{ code: 200, message: success, data: {} }状态码约定状态码含义前端处理200成功正常逻辑400参数错误检查传参401未登录或 token 过期跳转登录页403无权限提示无权限500服务器错误全局提示分页接口统一用page和page_size参数默认值是 1 和 10。列表接口统一返回{ list, total, has_more }前端通过has_more控制上拉加载是否继续请求而不是靠前端自己算。token 方案用 JWT前端存在uni.setStorageSync(token)请求时在 header 的 Authorization 字段带上。token 过期后后端返回 401前端需要统一拦截并跳转登录页这个小细节在第 5 章详细展开。3. 微信小程序端核心功能实现3.1 uniapp 项目初始化与目录结构创建项目之前先把 Node 环境准备好建议用 Node 18 以上版本npm 源换成国内镜像能省很多时间。项目创建命令我在第 1 章写过了这里重点说目录结构。graduate-road/ ├── src/ │ ├── pages/ # 页面 │ │ ├── index/index.vue │ │ ├── resource/list.vue │ │ ├── questions/index.vue │ │ ├── community/index.vue │ │ ├── checkin/index.vue │ │ └── user/index.vue │ ├── components/ # 复用组件 │ ├── api/ # 接口封装 │ ├── store/ # Pinia 状态管理 │ ├── utils/ │ │ ├── request.js │ │ └── auth.js │ ├── static/ # 静态资源 │ ├── App.vue │ ├── main.ts │ ├── manifest.json │ └── pages.jsonpages.json 在 uniapp 里相当于原生小程序的 app.json负责配置页面路由、tabBar、导航栏样式。tabBar 我配置了首页、题库、社区、打卡、我的五个入口。这里注意小程序原生 tabBar 最多支持 5 个如果页面超过 5 个就需要把不常用的入口收进首页宫格或者我的页面里。3.2 登录授权与获取手机号这个模块是大多数小程序项目里最容易写废的地方我把完整链路理一遍。第一步uni.login拿到临时 codeuni.login({ provider: weixin, success: async (res) { const loginRes await request({ url: /api/auth/login, method: POST, data: { code: res.code, userInfo: { nickName: 等登录后补充, avatarUrl: } } }) uni.setStorageSync(token, loginRes.data.token) } })第二步后端拿着 code 去微信接口换 openid生成自定义 token 返回。这一段后端代码在第 4 章写。第三步如果后续需要填充头像昵称一般由用户主动触发没必要一进来就弹授权框现在微信也收紧了这个能力。手机号获取要特别提醒现在小程序不能像以前一样直接调 API 拿到手机号了必须在页面上放一个拥有open-typegetPhoneNumber的 button 组件用户点击后触发事件返回的是一个动态加密的 code不能直接展示需要传给后端由后端调用微信接口解密换取手机号。button open-typegetPhoneNumber getphonenumberonGetPhoneNumber classphone-btn 绑定手机号 /buttononGetPhoneNumber(e) { const { code } e.detail if (code) { request({ url: /api/auth/bind-phone, method: POST, data: { code } }).then(res { uni.showToast({ title: 绑定成功, icon: success }) }) } else { uni.showToast({ title: 已取消, icon: none }) } }注意手机号绑定能力要求小程序已经通过微信认证个人主体的微信小程序目前不能使用这个组件。所以如果项目主体没有资质这个功能直接不要放进去免得用户点开才发现不可用。3.3 首页、资料列表、刷题与错题本首页最关键的信息是考研倒计时和每日一句。倒计时用 JS 计算const now new Date() const target new Date(2025-12-20T00:00:00) const diffDays Math.ceil((target - now) / (1000 * 60 * 60 * 24))这里我用的是预设考试日期更稳妥的做法是把目标日期从后端配置接口读取因为每年考研日期都会有官方通知写在代码里后期改成本高。每日一句可以做成后端每天定时任务生成也可以先放静态数组里上线后再动态化。资料列表页要支持下拉刷新和上拉加载。uniapp 里下拉刷新需要先在 pages.json 对应页面的 style 里打开enablePullDownRefresh然后在页面里监听onPullDownRefresh。上拉加载依赖onReachBottom生命周期。刷题页需要注意选项状态不能被重复点击答完题立即展示解析并且把错题异步写入错题本不要让用户感觉到操作卡顿。我贴一个答题选项的核心逻辑async submitAnswer(option) { if (this.answered) return this.answered true this.selected option const isCorrect option this.currentQuestion.answer if (!isCorrect) { await request({ url: /api/wrong-book/add, method: POST, data: { questionId: this.currentQuestion.id, wrongAnswer: option } }) } this.showAnalysis true }错题本则是一个分页列表每道题显示复习次数 reviewCount点进去可以重新作答。如果后续想把错题导出成 pdf后端再加一个导出接口即可。3.4 社区发帖与打卡社区发帖界面用 textarea 输入文字uni.chooseImage选择图片uni.uploadFile上传到后端。这里有个体验细节图片上传是异步的如果用户点发布时图片还没传完需要禁用发布按钮等所有图片返回 URL 后再拼装提交否则会出现帖子内容里图片地址为空的问题。如果想要在帖子中嵌入视频预览uniapp 的 video 组件支持点播流媒体格式这个项目里我没有优先做因为视频审核和存储成本都比较高属于后面迭代的功能。打卡模块更简单本质上是对应日期的一条记录。连续天数由后端统一计算前端只负责展示async doCheckin() { const res await request({ url: /api/checkin, method: POST }) if (res.data.checked) { this.continuousDays res.data.days uni.showToast({ title: 已打卡${res.data.days}天, icon: success }) } }打卡成功后我习惯在页面里放一个今日复习提醒的弹层逻辑是如果错题本里有今天该复习的题目弹出提示引导用户去刷错题。这个功能一下子把打卡和刷题两个模块串起来了留存效果很好。4. Python 后端核心接口实现4.1 Flask 项目结构与蓝图划分后端项目同样要按业务模块切分而不是把所有路由堆在一个文件里。我的目录结构study_api/ ├── app.py ├── config.py ├── extensions.py ├── api/ │ ├── __init__.py │ ├── auth.py │ ├── resource.py │ ├── question.py │ ├── community.py │ ├── checkin.py │ └── upload.py ├── models/ │ ├── __init__.py │ ├── user.py │ ├── question.py │ └── community.py ├── utils/ │ ├── token.py │ └── response.py ├── uploads/ └── requirements.txtapp.py 注册蓝图的核心代码from flask import Flask from flask_cors import CORS from api.auth import auth_bp from api.question import question_bp from api.resource import resource_bp from api.community import community_bp from api.checkin import checkin_bp from api.upload import upload_bp from extensions import db def create_app(): app Flask(__name__) app.config.from_object(config.Config) db.init_app(app) CORS(app, supports_credentialsTrue) app.register_blueprint(auth_bp) app.register_blueprint(question_bp, url_prefix/api) app.register_blueprint(resource_bp, url_prefix/api) app.register_blueprint(community_bp, url_prefix/api) app.register_blueprint(checkin_bp, url_prefix/api) app.register_blueprint(upload_bp, url_prefix/api) return app app create_app() if __name__ __main__: app.run(debugTrue)config.py 里存放所有配置项class Config: SQLALCHEMY_DATABASE_URI mysqlpymysql://user:passwordlocalhost/graduate_road?charsetutf8mb4 SQLALCHEMY_TRACK_MODIFICATIONS False SECRET_KEY 换成一个足够长的随机字符串 WX_APPID 你的小程序appid WX_SECRET 你的小程序secret REDIS_URL redis://localhost:6379/0 UPLOAD_FOLDER ./uploads MAX_CONTENT_LENGTH 16 * 1024 * 10244.2 登录鉴权与 JWT微信小程序登录接口的完整后端实现我用请求库去调微信的 code2Session 接口import requests import jwt from flask import Blueprint, request, jsonify from config import Config from models import db, User auth_bp Blueprint(auth, __name__) auth_bp.route(/api/auth/login, methods[POST]) def login(): data request.get_json() code data.get(code) user_info data.get(userInfo, {}) res requests.get(https://api.weixin.qq.com/sns/jscode2session, params{ appid: Config.WX_APPID, secret: Config.WX_SECRET, js_code: code, grant_type: authorization_code }) res_data res.json() openid res_data.get(openid) user User.query.filter_by(openidopenid).first() if not user: user User( openidopenid, nicknameuser_info.get(nickName, 考研er), avatar_urluser_info.get(avatarUrl, ) ) db.session.add(user) db.session.commit() token jwt.encode({user_id: user.id}, Config.SECRET_KEY, algorithmHS256) return jsonify({code: 200, data: {token: token, user: user.to_dict()}})JWT 校验装饰器是这样写的后面每个需要登录的接口直接加上注解# utils/token.py import jwt from functools import wraps from flask import request, jsonify from config import Config def login_required(f): wraps(f) def wrapper(*args, **kwargs): auth_header request.headers.get(Authorization) if not auth_header or not auth_header.startswith(Bearer ): return jsonify({code: 401, message: 未登录}), 401 token auth_header.split( )[1] try: payload jwt.decode(token, Config.SECRET_KEY, algorithms[HS256]) except jwt.ExpiredSignatureError: return jsonify({code: 401, message: 登录已过期}), 401 except jwt.InvalidTokenError: return jsonify({code: 401, message: 无效token}), 401 request.user_id payload.get(user_id) return f(*args, **kwargs) return wrapper有个细节值得说明小程序端每次请求都发送 token后端每次都要做 JWT 校验这个开销其实很低不需要动不动就上 Redis 验证 session。但如果后期有强制下线封号踢人需求JWT 不好主动失效届时可以把 token 版本号存 Redis校验时比对版本。4.3 刷题与错题本接口提交答案的接口是整个系统里调用最频繁的设计时要轻量只返回判题结果不返回完整错题列表。我贴一个完整的实现question_bp.route(/api/questions/submit, methods[POST]) login_required def submit_answer(): data request.get_json() question_id data.get(questionId) answer data.get(answer) question Question.query.get(question_id) if not question: return jsonify({code: 400, message: 题目不存在}), 400 is_correct (answer question.answer) if not is_correct: wrong WrongBook.query.filter_by( user_idrequest.user_id, question_idquestion_id ).first() if not wrong: wrong WrongBook( user_idrequest.user_id, question_idquestion_id, wrong_answeranswer, review_count1 ) db.session.add(wrong) else: wrong.wrong_answer answer wrong.review_count 1 wrong.last_review_time datetime.now() db.session.commit() return jsonify({ code: 200, data: { isCorrect: is_correct, correctAnswer: question.answer, analysis: question.analysis } })错题本分页列表question_bp.route(/api/wrong-books, methods[GET]) login_required def wrong_books(): page request.args.get(page, 1, typeint) page_size request.args.get(pageSize, 10, typeint) query WrongBook.query.filter_by(user_idrequest.user_id)\ .order_by(WrongBook.create_time.desc()) total query.count() items query.offset((page - 1) * page_size).limit(page_size).all() result [] for item in items: q item.question result.append({ id: item.id, questionId: q.id, content: q.content, options: q.options, answer: q.answer, wrongAnswer: item.wrong_answer, reviewCount: item.review_count, lastReviewTime: item.last_review_time }) return jsonify({ code: 200, data: { list: result, total: total, hasMore: page * page_size total } })这里强调一点答题提交接口往往是小程序端调用最频繁的如果在写答案时就同步把错题的全部详情解析出来返回给前端反而会让用户等很久。所以 submit 接口只返回很轻量的对错结果错题列表接口再展开完整内容。4.4 内容发布与文件上传文件上传是社区帖子、资料分享都会用到的接口。Flask 处理起来简单但要注意文件类型校验和重命名from werkzeug.utils import secure_filename import uuid import os ALLOWED_EXTENSIONS {pdf, doc, docx, ppt, pptx, png, jpg, jpeg} upload_bp.route(/api/upload, methods[POST]) login_required def upload_file(): file request.files.get(file) if not file: return jsonify({code: 400, message: 没有文件}), 400 ext file.filename.rsplit(., 1)[-1].lower() if ext not in ALLOWED_EXTENSIONS: return jsonify({code: 400, message: 不支持的文件类型}), 400 filename f{uuid.uuid4().hex}.{ext} file.save(os.path.join(Config.UPLOAD_FOLDER, filename)) file_url fhttps://你的域名/uploads/{filename} return jsonify({code: 200, data: {url: file_url}})用 uuid 重命名的原因很实际用户上传的文件名可能是第3章笔记最终版.pdf直接存磁盘很容易出现编码问题而且重名文件会互相覆盖。用 uuid 后原文件名单独存数据库即可展示时再从库里取。5. 实战中的高频问题与排查实录5.1 登录与手机号获取的坑第一个坑个人主体小程序无法用手机号快速验证组件。代码写了功能灰了审核时也会被打回。解决办法是遵守平台规则让客户选择个体工商户或企业主体或者不开放手机号绑定改用微信昵称和头像登录。第二个坑wx.login返回的 code 有效期很短且一次一码。如果登录接口请求超时或者网络抖动导致后端没拿到 code再重发同一个 code 就会报 invalid code。处理方式是前端在进入小程序时主动请求一次登录如果返回 401 再重新调uni.login刷新 code。第三个坑code2Session 接口需要配置合法的服务器域名。开发调试时可以在微信开发者工具里勾选不校验合法域名但上线前必须在小程序后台把 HTTPS 域名配置进 request 合法域名否则线上所有请求都会被拦截。5.2 页面适配问题微信小程序不同机型的状态栏高度、胶囊按钮位置都不一样。如果自定义导航栏计算导航栏高度时不能用固定值。我用的是这个方案const getNavHeight () { const system uni.getSystemInfoSync() const menu uni.getMenuButtonBoundingClientRect() const statusBarHeight system.statusBarHeight const navBarHeight (menu.top - statusBarHeight) * 2 menu.height return { statusBarHeight, navBarHeight, menuRight: menu.right, menuTop: menu.top } }这个公式算出来的单位是 px小程序布局如果用 rpx需要在需要换算的地方除以窗口宽度再乘 750。另一个高频问题在 iPhone 上底部安全区会遮挡打卡按钮处理办法是给底部按钮加上padding-bottom: env(safe-area-inset-bottom)CSS 里直接写就能适配。5.3 uniapp 调试与联调常见问题uniapp 开发最常见的一个现象是代码里写了console.log但微信开发者工具的 console 里不见输出。这种情况先检查是不是运行到了 release 版编译或者代码在错误的分支里根本没有执行。另一个原因是 uniapp 的日志在小程序里默认不输出console.table这类方法换成console.log就好。联调时要特别注意开发环境调用http://localhost:5000在小程序里是不能直接用的需要用电脑局域网 IP。同时微信开发者工具里勾选不校验合法域名这一项。还有一个跨域问题只在 H5 端出现微信小程序端本身没有跨域限制但如果你同时用 uniapp 编译 H5浏览器就会拦截跨域请求。解决方式有两个后端加 flask-cors或者前端 Vite 配置 devServer proxy。5.4 内容安全与审核微信小程序对社区类内容管控很严。发帖、评论接口在正式上线前必须接入内容安全检测否则审核会被打回。微信提供了security.msgSecCheck和security.msgSecCheckV2做文本检测图片内容用security.imgSecCheck。一个常见的做法是提交帖子内容时前端把 text 和图片 URL 一起发送到后端后端先调用检测接口通过后才写入数据库不通过则返回内容含有违规信息的提示。这样虽然多了一次网络请求但审核通过率会高很多。另外要注意隐私协议。小程序后台需要配置用户隐私保护指引明确列出采集的信息项微信昵称、头像、手机号等并在 app.json 里声明收集目的。uniapp 对应的字段在 manifest.json 的mp-weixin节点里配置。6. 项目上线与后续扩展方向6.1 从开发到上架的关键流程微信小程序上架不是一个纯技术问题流程上的坑比代码多。我按这次实际走通的流程整理一遍注册微信小程序账号。需要邮箱和企业/个体工商户资质个人主体可以做但功能受限。配置服务器域名。在小程序管理后台把 HTTPS 域名加入 request 合法域名。购买并配置 HTTPS 证书Nginx 配置 ssl 证书和反向代理。部署后端。用 supervisor 管理 gunicorn 进程gunicorn 启动命令里指定 worker 数量和 bind 地址。发布前检查。页面空白、接口超时、隐私协议未配置、类目选择错误都是审核被打回的常见原因。提交审核。审核周期一般 1 到 3 个工作日类目选择教育-教育信息服务。部署这里我用 supervisor 管理进程配置大意[program:graduate_road] command/home/deploy/venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 app:app directory/home/deploy/graduate_road autostarttrue autorestarttrue stderr_logfile/var/log/graduate_road.err.log stdout_logfile/var/log/graduate_road.out.log6.2 后续还能继续加的功能代码层面的扩展点有很多我挑几个最有价值的第一智能刷题推荐。基于错题数据统计用户薄弱的知识点章节再按照同类知识点出题。实现上不需要多复杂的算法错题表里每个题关联章节按章节频次排序就能做一个粗糙版本。想做得更好可以引入简单的协同过滤但用户量到几千之前不推荐。第二考研时间线组件。从考研准备期到初试、复试、调剂每个节点给用户推送提醒。数据来源可以用 Python 定时脚本抓取院校官网的招生简章解析后入库这个功能与 Python 生态天然适配。第三学习打卡数据可视化。把每日学习时长、刷题量、连续天数画成可视化图表帮助用户建立正反馈。小程序端用 canvas 手绘折线图和柱状图后端只需提供聚合统计接口。第四语音答题和课程速记。如果目标是做内容付费可以接入音频课程用 uniapp 的 audio 组件播放再配套章节笔记。这块变现能力比纯交流系统强不少。整个项目从踩坑到上线我最深的体会是考研学习交流系统看起来功能多但真正决定产品价值的是录题、整理资料、审核内容这些脏活累活。技术选型 vue uniapp Python 只是把这些事情串起来的工具真正的用户体验来自数据质量和社区氛围。如果你想复刻或改造这个项目我建议先把需求和表结构想清楚再动手写代码等接口文档稳定了前后端同时开工三周内做出一个可上架的版本是完全现实的。最后分享一个我个人的小习惯每次发布前把小程序端请求封装里所有错误 toast 都打开全局走一遍核心流程很多隐藏问题就是在这种走查里暴露出来的。
阅读完成 · 觉得有帮助?