1. 这个"Python + Android + 小程序"题目,本质是个三层架构题
收到这个项目需求的时候,我盯着标题看了半天——"Python基于Android的大学生科技科创项目综合管理平台的小程序"?三个技术名词挤在一起,乍一看像要求用Python开发一个Android应用,再塞进小程序里,多少有点"什么都想沾一点"的味道。但拆开想清楚之后,你会发现这就是一个标准的前后端分离项目:Python负责后端服务与业务逻辑,Android以管理端身份承担审批、统计这类高频操作,微信小程序则作为学生端入口。
1.1 三个关键词背后的真实分工
我先把需求里的三个词挨个落地:
- Python:后端服务,用Flask提供REST接口,处理项目申报、审批流转、进度报告、经费预算这些核心业务逻辑,顺便做数据持久化。
- Android:管理端载体。指导老师和院系管理员在手机上打开Android App,审批学生提交的项目申报书、查看项目进度、导出统计报表。
- 小程序:学生端。学生通过微信扫码进入,完成登录、申报、材料上传、进度填报、查看审批结果等操作,不需要安装独立App,使用门槛最低。
很多初学者容易被标题带偏,以为"Python基于Android"是用Python去写Android原生应用,然后跑去研究Kivy、BeeWare,搞了好几天连环境都没跑通。实际上真正合理的方案是:Python只做服务端,小程序和Android都是消费接口的客户端。Windows、报表这类低频但计算密集的活交给Python接口,高频的打开操作交给客户端,各司其职。
1.2 我的选型结论:Flask + 微信小程序原生 + Android管理端
技术栈定下来,我在框架选择上也纠结过一阵。后端一开始考虑过Django,毕竟它自带admin后台和ORM,学生管理系统这种场景用Django似乎顺理成章。但仔细掂量之后,我把后端换成了Flask,原因有三点:
- 这个项目的核心业务表撑死十来张,Flask的轻量级特性完全够用,不需要Django那套整装框架。
- 学生团队迭代过程中,SQLAlchemy的模型改动非常灵活,加字段、改关联关系都不用大动干戈。
- Django的admin后台在这个场景里反而鸡肋——管理端已经由Android App承担,再去维护一套Web后台属于重复建设。
小程序端我没有选uni-app或Taro,直接用微信原生语法写。理由是项目客户端只有两个:学生端小程序 + 管理端Android,uni-app"一次编写多端运行"的优势在这里发挥不出来,反而多一层编译转换会引入排查成本。原生小程序的wx.request、wx.uploadFile这些API是官方文档直接对应的,出了问题百度一下立刻有答案,对学生团队最友好。
1.3 核心用户角色与业务闭环
开发之前,我先把业务流程画了一条主线出来(这里不画图,直接用文字描述):
- 学生打开小程序,微信授权登录,自动成为"学生"角色。
- 学生在小程序里填项目申报书:项目名称、类别(科技发明/学术论文/创业实践)、成员名单、指导老师、预算金额,然后提交。
- 管理员在Android端看到待审批列表,逐条审核,通过则项目状态变为"立项"。
- 项目执行期间,学生按时间节点提交进度报告,每份报告附带文字说明和成果文件。
- 学生提交结题材料,管理员审核后归档,项目状态变为"已结题"。
围绕这条主线,用户角色我一开始设计了四维权限:学生、指导老师、学院管理员、系统管理员。但真写起来发现,权限判断根本不需要引入RBAC那套复杂体系。我只在用户表上留了一个role字段,后端每次请求里做两个判断——是否登录、是否有审批权限,两个装饰器函数就解决了。第一版我画了五张权限表,最后一张都没用上,全删了。项目管理的核心是流程与状态,不是权限矩阵,把主链路跑通永远排在第一位。
这个判断在后面开发中被反复验证:学生提交审批、管理员审核通过、学生看到状态变化,这个闭环一旦通了,其他问题都是细节层面的补丁。
2. 后端服务骨架:数据库建模与REST API是平台的地基
后端是平台的定盘星。我常说,小程序端的代码写错了顶多刷新一下页面,后端接口一旦设计跑偏,前端返工、管理端返工、测试返工,所有端口的开发进度都会卡住。所以这一层我花的时间最多,也最值得。
2.1 六张核心表的建模思路
数据库我用的SQLite起步,SQLAlchemy做ORM。虽然SQLite不适合生产环境高并发,但开发阶段部署简单、免配置,全团队拉代码就能跑,这对学生团队来说比什么都重要。等后期用户量上来了,再平滑切到MySQL,ORM模型基本不用改。
建表逻辑围绕"人—项目—审批"三条主线展开,共六张表:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| users | 用户表 | openid, role, real_name, student_no, college |
| projects | 项目表 | name, category, leader_id, teacher_name, status, budget |
| project_members | 项目成员表 | project_id, user_id, is_leader |
| reports | 进度报告表 | project_id, report_name, content, file_url, period |
| approvals | 审批记录表 | project_id, approver_id, action, comment, created_at |
| announcements | 通知公告表 | title, content, created_at, target_role |
建表时有几个细节值得展开说说。projects表的status字段我用了varchar,而不是int枚举,"pending、approved、rejected、in_progress、pending_final、completed"这几个字符串直接可读,调试连数据库都不用翻代码。很多教程喜欢用0/1/2/3数字表示状态,省了空间但苦了人,每次还要对着注释表翻译一遍,没必要。
project_members表是典型的中间表,用来处理"一个项目多个成员、一个用户参与多个项目"的多对多关系。is_leader字段标记谁是负责人,负责人对项目有编辑权,普通成员只有查看权。
approvals表单独拎出来的原因是审批记录天然是审计日志,每次操作都要留痕。action字段记录approve或reject,comment存审批意见,created_at自动写入当前时间。有了这张表,管理端就能实时查出"谁在什么时候通过了哪个项目",出了问题追溯起来一目了然。
2.2 三大核心接口链路设计
接口设计遵循两个原则:一是以状态为纽带的流程驱动,二是接口粒度适中,不要太粗(一个接口干所有事)也不要太细(一个操作拆十个接口)。
我按业务链路把接口分成三组:
申报链路
POST /api/project/create:创建项目,接收表单数据写库GET /api/project/detail?project_id=xxx:查询项目详情与成员列表POST /api/project/update:修改项目信息,仅负责人有权限
审批链路
GET /api/project/pending_list?role=admin:管理员拉取待审批列表POST /api/project/review:提交审批意见,传project_id、action、commentGET /api/project/my_approvals:查询我提交过的审批记录
进度链路
POST /api/report/submit:提交进度报告,带文件上传GET /api/report/list?project_id=xxx:查看某项目的全部报告POST /api/project/apply_final:申请结题,提交结题材料
三个链路看似独立,实际通过projects表的status字段串成一条线:pending(待审批)→ approved(已立项)→ in_progress(执行中)→ pending_final(待结题)→ completed(已结题)。每次审批通过、报告提交、结题申请,都会触发status的状态迁移,小程序端也要跟着刷新页面。
我在设计接口时特意把POST /api/project/review设计成"审批通过或拒绝走同一个接口",action字段区分即可。原因是Android管理端审批列表里,通过和拒绝本来就是同一个页面的两个按钮,接口分开反而要写两套请求逻辑。
2.3 Flask工程起步代码:比Django更轻,正好够用
直接上个可运行的骨架,方便你抄作业。后端目录结构大概是:
backend/ ├── app.py # 应用入口 ├── models.py # SQLAlchemy数据模型 ├── auth.py # 登录鉴权与装饰器 ├── api/ │ ├── project_api.py # 项目与审批接口 │ ├── report_api.py # 进度报告接口 │ └── user_api.py # 用户信息接口 ├── requirements.txtapp.py的初始化这样写:
from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///kc_platform.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 上传文件限制50MB CORS(app) db = SQLAlchemy(app) # 注册蓝图 from api.project_api import project_bp from api.report_api import report_bp from api.user_api import user_bp app.register_blueprint(project_bp, url_prefix='/api/project') app.register_blueprint(report_bp, url_prefix='/api/report') app.register_blueprint(user_bp, url_prefix='/api/user') if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)models.py里重点写users和projects两张表:
from app import db from datetime import datetime class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) openid = db.Column(db.String(64), unique=True, nullable=False) role = db.Column(db.String(10), default='student') # student/teacher/admin real_name = db.Column(db.String(20)) student_no = db.Column(db.String(20)) college = db.Column(db.String(40)) class Project(db.Model): __tablename__ = 'projects' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) category = db.Column(db.String(20)) # 科技发明/学术论文/创业实践 leader_id = db.Column(db.Integer, db.ForeignKey('users.id')) teacher_name = db.Column(db.String(20)) status = db.Column(db.String(15), default='pending') budget = db.Column(db.Float, default=0.0) created_at = db.Column(db.DateTime, default=datetime.now)登录鉴权用JWT,auth.py里写一个校验token的装饰器:
import jwt from functools import wraps from flask import request, jsonify SECRET_KEY = 'your-secret-key-change-me' def token_required(f): @wraps(f) def decorated(*args, **kwargs): token = request.headers.get('Authorization', '').replace('Bearer ', '') if not token: return jsonify({'code': 401, 'msg': '未登录'}), 401 try: payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) request.user_id = payload['user_id'] request.user_role = payload['role'] except jwt.ExpiredSignatureError: return jsonify({'code': 401, 'msg': '登录已过期'}), 401 except jwt.InvalidTokenError: return jsonify({'code': 401, 'msg': '无效token'}), 401 return f(*args, **kwargs) return decorated这个骨架跑起来之后,后端就能对外提供稳定的API了。我建议你开发时开着debug=True,接口报错时Flask会直接把堆栈打到终端,定位问题比看日志文件快得多。生产环境务必关掉debug,这是后话。
3. 小程序端拆解:登录、申报、待办三大页面模块的实现
后端接口稳定之后,小程序端的工作就是"把字典和接口翻译成页面"。整体页面结构我控制在六个以内:首页、申报页、进度页、消息页、我的、登录页。页面少了逻辑不好放,页面多了学生嫌烦,六个正好。
3.1 登录鉴权:wx.login到token的完整链路
登录是小程序所有功能的前置条件。微信小程序的登录逻辑和传统用户名密码登录完全不同,核心是wx.login获取临时code,然后交给后端换取openid和自定义token,链路是这样的:
- 小程序端调
wx.login,微信返回一个一次性code,有效期五分钟。 - 小程序把code通过
wx.request发给后端POST /api/user/wxlogin。 - 后端拿code去调微信接口换取openid,然后用openid查users表:
- 查到了,说明是老用户,直接签发token返回。
- 查不到,先创建新用户记录,把role设为"student"(这里可以做个小逻辑:如果用户填了邀请码,则根据邀请码识别为老师),再签发token。
- 小程序把token存到
wx.setStorageSync('token', xxx),后续所有请求都在header里带Authorization: Bearer xxx。
小程序端的request封装,我写成了一个公共方法,所有页面统一调用,这样token过期拦截逻辑只用写一次:
// utils/request.js const BASE_URL = 'https://your-domain.com/api'; function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: { 'Authorization': 'Bearer ' + wx.getStorageSync('token') }, success: (res) => { if (res.data.code === 200) { resolve(res.data); } else if (res.data.code === 401) { // token过期,清掉本地token,跳回登录页 wx.removeStorageSync('token'); wx.reLaunch({ url: '/pages/login/index' }); reject(res.data); } else { reject(res.data); } }, fail: (err) => reject(err) }); }); } module.exports = { request, BASE_URL };登录页的按钮点击逻辑:
// pages/login/index.js const { request } = require('../../utils/request'); Page({ async handleLogin() { wx.login({ success: async (res) => { const code = res.code; try { const resp = await request('/user/wxlogin', 'POST', { code }); wx.setStorageSync('token', resp.data.token); wx.setStorageSync('userInfo', resp.data.user); wx.reLaunch({ url: '/pages/home/index' }); } catch (err) { wx.showToast({ title: '登录失败,请重试', icon: 'none' }); } } }); } });这里有个小坑必须提醒:wx.login返回的code每次都不一样,而且只能用一次。如果你发现某次登录总是报"code无效",先检查是不是同一个code被重复调用了后端接口。
3.2 申报表单与文件上传:组合拳的实现细节
申报页是小程序里交互最重的模块,学生要填项目名称、选类别、填预算、选成员、填指导老师。表单我用原生picker组件选类别和成员,预算用input type="digit",表单校验全部在前端先做一遍,后端也要再校验一遍,两遍才稳妥。
文件上传是申报中容易出问题的地方。立项申请书、成员信息表、指导老师同意书,动辄好几个PDF。小程序对文件上传的限制比较多,我总结了几条实测经验:
wx.uploadFile的name字段必须和后端接口接收参数的字段名一致,比如后端用file,前端就传name: 'file',错了后端收到的是空文件。- 单次上传有并发限制,实测下来同时并发超过10个请求时,后发的请求很大概率被微信拒掉。我这边采用串行上传方案:先把所有文件加进一个队列,一个传完再传下一个,每个文件传完更新进度条。
- 文件大小限制两个方面:微信官方限制单个文件最大10MB,服务器端也要对齐限制,避免有人绕过前端直接传大文件把后端拖垮。
上传代码的核心逻辑:
// pages/submit/index.js async uploadFiles(fileList, projectId) { const token = wx.getStorageSync('token'); const promises = fileList.map((filePath) => { return new Promise((resolve, reject) => { wx.uploadFile({ url: `${BASE_URL}/report/upload`, filePath: filePath, name: 'file', formData: { project_id: projectId }, header: { 'Authorization': 'Bearer ' + token }, success: (res) => { const data = JSON.parse(res.data); resolve(data); }, fail: reject }); }); }); // 这里注意:并发控制如果要做,需要自己实现队列 const results = await Promise.all(promises); return results; }如果你要严格串行上传,可以用async/await配合循环,一次只放一个请求出去。我实测过,串行比并行的总耗时并没有慢太多,但稳定性好很多——至少不会出现一半文件传完一半失败的情况。
3.3 通知与进度卡片:如何把后端状态变成前端UI
学生在小程序里最关心的就两件事:项目现在到哪一步了?老师/管理员有没有通过我的申请?所以首页和进度页要直观呈现这两个信息。
我的做法是首页放一个"我的项目"列表,每张卡片显示项目名称、当前状态标签、最近更新时间。状态和标签色的映射关系:
| 后端status | 前端展示 | 标签色 |
|---|---|---|
| pending | 待审核 | 橙色 |
| approved | 已立项 | 绿色 |
| in_progress | 执行中 | 蓝色 |
| pending_final | 待结题 | 紫色 |
| completed | 已结题 | 灰色 |
| rejected | 被驳回 | 红色 |
小程序端不直接展示英文status,而是通过一个映射函数转成中文标签。这样后端只管状态机,前端只管UI展示,两边的解耦很干净。
通知页的数据来源有两个:一是系统推送的公告(后端announcements表),二是与用户相关的审批动态(approvals表里approver_id等于当前用户或项目关联用户的数据)。我拉取通知列表的接口做了分页,?page=1&page_size=10,小程序端滚动到底部时自动加载下一页。这个"加载更多"的逻辑虽然简单,但很实用——如果一次性把全量数据塞给小程序,列表加载速度会肉眼可见地变慢。
进度页的数据结构是一个时间线:立项时间 → 每次报告提交时间 → 结题时间。时间线组件小程序没有内置,我用wx:for循环加一根竖线和圆点模拟出来。这个组件的关键是日期格式化,后端返回的是ISO时间字符串,前端要用new Date(isoString)转成本地时间,再格式化成YYYY-MM-DD HH:mm。
4. 联调阶段必踩的坑:抓包调试、域名校验与Mock数据
后端服务跑在本地,小程序跑在微信开发者工具里,两边要打通,第一个拦路虎就是网络环境。这个阶段我专门花了一天时间处理联调问题,踩坑记录如下。
4.1 小程序调试环境的三个开关
微信开发者工具的默认配置是很严格的,直接请求本地IP的接口十有八九会被拦。你得在"详情 → 本地设置"里做三件事:
- 勾选"不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书"。开发阶段这相当于开了白名单模式,任何http接口都能请求。
- 把"调试基础库"选到与目标用户匹配的版本,我这边固定用最新的稳定版基础库。版本太旧可能缺少新组件API,版本太新可能遇到实验性要求的坑。
- 设置"真机调试"时需要把host和端口从localhost改成电脑的局域网IP,手机和电脑必须同一WiFi网段,否则真机预览请求不到本地服务。
这三个开关每次新建项目都容易漏掉第二个,导致拷贝别人代码后发现某API在你本地不可用。我第一次排查这种问题时浪费了两个小时,最后发现是基础库版本低了一个大版本。
4.2 Charles在小程序接口调试中的实际用法
联调离不开抓包工具,我用Charles的频率最高。虽然微信开发者工具自己的Network面板也能看请求,但真机预览的时候,手机上的小程序请求在开发者工具的Network面板里是看不到的,这时候就需要用Charles抓真机流量。
Charles最常用的配置步骤:
- 电脑端启动Charles,端口默认8888,记下电脑的局域网IP。
- 手机WiFi设置代理:地址填电脑IP,端口8888。
- 手机访问
chls.pro/ssl下载并安装Charles根证书。这一步是抓HTTPS包的必要前提,证书不装,看到的全是加密乱码。 - 在Charles的Proxy → SSL Proxying Settings → SSL Proxying里,添加
*:443或者只添加你的接口域名。 - 用Filter输入框过滤出api域名下的请求,就能看到完整的URL、请求头、响应体。
要说明的是,正确使用抓包工具本身是常规开发流程的一部分。我主要用它做三件事:第一,校验小程序发出的请求头和参数是否和后端接口定义一致;第二,后端返回异常时快速定位是接口bug还是前端传参错误;第三,初步判断响应耗时,如果某个接口在Charles里显示耗时超过2秒,就需要回后端查SQL或加索引了。
Charles的响应重写功能也救过我一命。联调中途后端某接口突然挂掉,前端又急着验收页面效果,我直接在Charles的Map Local功能里把接口响应对应到一个本地JSON文件,前端照常跑,等后端修好了再切回真实接口。这个技巧在演示前尤其好用。
4.3 Mock数据与真实接口的切换策略
说到Mock,我再多说一句。项目开发前期,前端可以和后端并行开发,前端不等后端接口写就就先造一批Mock数据,把页面UI和交互逻辑全部调通。我推荐一个简单的方案:在utils/request.js里加一个常量USE_MOCK,设为true时请求直接返回mockData.js里的预设JSON,设为false时走真实网络请求。
// utils/mock.js const mockData = { '/project/list': { code: 200, data: [ { id: 1, name: '基于深度学习的校园垃圾分类系统', status: 'approved', updated_at: '2024-05-20 10:00:00' }, { id: 2, name: '智能宿舍门锁管理系统', status: 'pending', updated_at: '2024-05-18 14:30:00' } ] } }; function mockRequest(path, method, data) { return new Promise((resolve) => { setTimeout(() => { resolve(mockData[path] || { code: 404, msg: 'mock data not found' }); }, 300); }); }Mock数据的好处是让前端开发完全不依赖后端进度,等接口真正联调时,只改一个开关就能全量切换。这里有个细节:Mock数据的结构要严格跟着接口文档走,字段名、嵌套层级、响应code都要和真实接口一致,否则到时候切回来又是一轮改页面。
5. 交付前检查清单与四个亲身踩坑记录
项目开发到最后,功能看起来都齐了,但真正拿出去演示或者交付时,总会有几个意想不到的问题冒出来。这部分是我在多个项目里被锤出来的经验集合。
5.1 演示前要过一遍的检查清单
环境类
- 后端服务是否已在服务器或演示机上运行?
0.0.0.0:5000是否监听外网端口? - 小程序端BASE_URL是否已从localhost改成了正式服务器域名?
- 服务器域名是否符合"不校验合法域名"的配置?真机演示时如果没勾选,第一件事就失败。
- 后端数据库里是否有演示数据?我建议预置两个学生账号、一个管理员账号、五条项目记录,演示时直接展示,不要现场造数据。
权限类
- 学生账号和管理员账号是否权限区分明确?把学生账号误用到管理员功能,演示时会很尴尬。
- token过期时间是否设置合理?我遇到过token设成30秒过期,演示现场每操作一步就要重新登录的情况。
流程类
- 四条主链路是否全部走通:申报→审批→进度→结题。
- 被驳回的项目,小程序端是否能正确显示驳回意见?这个场景很多团队会漏测,但评委或老师提问时很可能问到。
5.2 时间、经费、角色权限三个隐藏难点
难点一:经费预算的数据类型
预算金额用Float存,看起来没问题,但浮点运算的精度陷阱会咬人。比如预算998.50元,计算报销比例时可能得到998.4999999,界面显示又要四舍五入。后端建议直接用Decimal类型,或者干脆用整数存分(预算值乘以100)。我在第一版用Float,后来改成了int分单位,省掉一整个精度处理的麻烦。
难点二:角色权限的边界
前面说了用role字段做简单判断,但这个简单指的是实现简单,业务边界要提前定义清楚。比如:学生能否修改已经提交审批的项目?管理员能否删除已立项的项目?这些规则不提前定,开发到一半就会出现"谁能操作哪个按钮"的争吵。
我的做法是写了一份简短的权限清单:
- 学生只能编辑自己的项目,且仅限pending状态。
- 管理员可以审批pending状态的项目,可以查看全部项目详情。
- 指导老师可以查看名下项目,不可审批。
- 仅系统管理员可以删除任何状态的任何项目。
这份清单贴在项目文档首页,所有端开发都按它实现,后面几乎没有因为权限问题返工。
难点三:上传文件的存储路径
开发时文件存本地目录,上传后管理系统一重启或者目录权限没配好,文件就访问不到了。我后来统一改成按日期分目录存储,文件名加时间戳前缀防止重名,文件访问URL也就是后端域名加相对路径。这个细节解决了实际部署中最常见的404问题。
5.3 调试接口时我自己踩过的四个坑
最后把我在这个项目里踩得最深、也最有代表性的四个坑列出来,希望能帮你省时间。
坑一:wx.request返回的不是JSON对象
这是新手最容易懵的问题之一。wx.request的success回调里拿到的res.data实际上是后端返回的字符串,必须用JSON.parse(res.data)转成对象才能操作。如果你的后端返回了非JSON格式的文本,比如HTML错误页,JSON.parse会直接抛异常。我的处理经验是在公共request方法里加一层try/catch,解析失败则统一返回code: 500的结构,而不是让错误散落到每个页面。
坑二:日期格式前后端不一致
小程序端picker组件返回的是2024-05-20格式的字符串,后端datetime字段默认返回2024-05-20 10:30:00。如果前端直接用这个字符串做日期比较,会踩到大小比较错误的坑。我统一在公共工具里写了一个格式化函数,所有日期展示都走它,避免各写各的格式转换。
坑三:小程序并发请求上限
微信小程序的并发请求上限实测是10个左右,超过后多余的请求会pending,等待前面释放。如果页面一次性发起多个wx.request,后续操作会莫名卡住。解决方案是控制请求粒度:页面初始加载只发两个请求,滚动加载分页时再发第三个;需要多个接口数据时,可以用Promise.all合并或串联,避免并发爆发。
坑四:Android管理端的Android原生适配
标题里的Android管理端,在开发时还要额外注意一点:Android机型众多,不同的屏幕尺寸对长文本、表格类UI的展示差异很大。审批列表里的项目名称过长时,要加ellipsis截断,避免撑破布局;日期选择器要确认低版本Android的兼容性,有些系统的DatePicker行为不太一致。我用的是Kotlin + RecyclerView做审批列表,数据从后端接口拉,UI状态跟随项目状态绑定,适配问题主要集中在文本溢出和弹窗样式上。
经过这一轮完整的从零搭建到联调测试,整个平台的雏形就跑起来了。每次做类似的项目管理系统,我最大的体会都是:技术难点从来不在单个技术栈本身,而在于让不同端口的数据模型与状态流转保持一致。把后端的状态机定义清楚,前端各管各的展示,联调时候的摩擦就会少一大半。至于中途那些抓包、Mock、日志排查的经验,永远是实践出来的,文档教不会。
如果你也在做类似的大学生项目管理系统,我的建议很简单:先把流程定死,再写代码;先把骨架跑通,再补细节;先把主链路演示顺,再碰权限和文件存储这些进阶功能。按这个顺序来,交付的时候你会轻松很多。