ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

小程序宿舍管理系统毕设实战:权限、状态机与工程细节

小程序宿舍管理系统毕设实战:权限、状态机与工程细节 毕设做了个“基于小程序的宿舍管理系统”源码加论文一起整完整个过程从选题到答辩大概两个月。今天就顺着这个项目把台前幕后都拆开聊一聊。先明确一点如果你也在做类似的毕设或者想接个中小型管理系统练手这个小程序的选题非常推荐。原因很朴素——它不是一个“玩具项目”有用户分权、有状态流转、有数据模型前端是微信小程序后端可以选Java Spring Boot也可以选Python Flask覆盖面广可深可浅既能满足本科毕设的体量要求又有足够的空间被评委追问和展示亮点。我当时为什么选这个题目说白了宿舍管理系统是所有校园管理场景里最有“画面感”的。你不需要跟评委解释业务背景谁都住过宿舍谁都知道报修、查寝、卫生评分这些事。业务的“共识成本”很低这让你可以把精力放在技术实现上而不是花大力气讲业务有多复杂。1. 整体设计与思路拆解任何一个管理系统的内核剥开一看都是“角色 权限 数据流转”。宿舍管理系统也不例外。1.1 为什么选小程序而不是Web端或App这可能是整个毕设最关键的一个选题决策。我当时排除了纯Web管理后台也没有做原生App而是选了微信小程序原因主要有三个。第一是触达成本。小程序不需要安装扫码即用这对“学生报修”“查看通知”“日常打卡”这类高频轻交互场景非常友好。你做一个App用户要下载、要注册、要授权每一步都是流失点小程序在这方面的摩擦小得多。第二是开发成本可控。前端不用自己处理复杂的浏览器兼容微信开发者工具一套到底组件、API、调试都是现成的。我的前端部分大概用了两周多就完成了所有页面。这个好处你只有做毕设的时候才体会最深——你的主要精力应该放在系统设计、后端逻辑和论文撰写上而不是在CSS兼容性里挣扎。第三是演示效果天然加分。答辩的时候你用模拟器把小程序跑起来手机屏幕上的交互界面天然比电脑浏览器里的网页更有“完成度”。评委看到的是一个完整闭环学生端小程序 管理端后台 服务端接口。这套组合给人最直观的印象就是“这个学生真的做了一套系统”。如果你选的题目是一个业务系统我非常建议优先考虑小程序作为前端载体。微信生态自带用户体系你甚至不需要自己做完整的注册登录流程直接用微信的wx.login换 openid 就能搞定身份识别。1.2 角色权限怎么拆宿舍管理系统的用户角色我最终拆成了三类学生、宿管员、系统管理员。没有做“楼长”这个中间层原因是我觉得楼长的职责可以被宿管员覆盖加中间层只会增加角色权限设计的复杂度但对毕设的“功能复杂度”并没有本质提升。学生端报修提交、报修进度查询、宿舍通知查看、个人住宿信息、卫生成绩查询。宿管员端报修工单处理、查寝记录登记、卫生检查打分、学生的住宿信息管理。管理员端楼栋管理、宿舍分配、用户管理、数据统计导出。这个权限模型的精髓在于它不是一个简单的“角色不同菜单不同”的静态划分而是引入了数据权限和操作权限两个维度。比如宿管员能看到的报修单只限于自己管理的那栋楼管理员则看全部。数据权限不是前端隐藏一下就行后端接口必须做数据过滤。这一点在毕设答辩中很常见评委一般都会问“你怎么保证一个宿管员不能把另一个楼栋的报修单也处理了”你只要回答“我在Service层做了楼栋ID的条件约束查询的时候强制拼接管理员的管辖范围”就够了具体的技术实现后文会展开。1.3 技术选型的心路历程技术栈我选的是“小程序原生框架 Python Flask SQLite”后来又迁移到了MySQL。我给你们拆一下为什么这样选以及这个选择背后的权衡。前端选原生而非uni-app或者Taro是因为毕设项目体量不大原生小程序框架已经能覆盖所有需求而且不用额外引入一套编译链。调试方便出问题也容易定位。如果你已经会Vue那用uni-app也没问题但从零开始原生反而是效率最高的。后端Flask是Python写的。为什么不选Spring Boot说白了就一个字快。你写毕设不是为了进大厂拧螺丝而是为了用最短时间实现一个完整可用的系统。Flask配合蓝图Blueprint可以非常清晰地把模块拆开写接口的体验比Java要轻快得多。这个是个人偏好如果你对Java更熟那Spring Boot完全没问题。我后面给关键代码示例会同时给Java风格思路和Python风格思路。数据库最开始用了SQLite图省事。后来发现毕设论文里要画ER图、写数据表结构SQLite显得不够“正式”我就换成了MySQL。这个切换成本极低因为我所有数据库操作都走的是ORM模型改个连接串和驱动就好。这套组合打下来的体会是毕设选型的第一原则不一定是“最主流”而是“你最熟且足够演示”。你要是本身Java很强那Spring Boot势如破竹你要是Python熟Flask足够撑起体面。2. 核心细节解析与系统设计这一章来点干的数据库表怎么设计、接口怎么定、权限怎么在后端落地、报修单的状态怎么流转。2.1 数据模型设计的底线我设计的数据表一共十二张核心的几个是用户表、宿舍楼表、宿舍房间表、住宿关系表、报修单表、通知表、查寝记录表、卫生评分表。有些表比较简单比如通知表就是标题内容发布时间有些表则要仔细设计。先看用户表CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL, role tinyint NOT NULL DEFAULT 2 COMMENT 1管理员 2宿管员 3学生, name varchar(32), student_no varchar(20), phone varchar(20), avatar varchar(255), create_time datetime, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) );有个小细节很多人会忽略一个用户可以拥有多个角色吗比如宿管员本身也是住宿舍的那她需要学生的功能怎么办我在设计里让角色是单值字段但通过“住宿关系表”来区分功能边界——宿管员可以同时有一条住宿记录她以“学生身份”使用学生功能以“宿管员身份”使用管理功能。这个小设计避免了角色表的复杂化答辩时也能讲出一个清晰的思路。再看报修单表CREATE TABLE repair ( id int NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL, student_id int NOT NULL, dorm_id int NOT NULL, category varchar(32) COMMENT 水/电/木/网络, description text, images varchar(1000), status tinyint NOT NULL DEFAULT 0 COMMENT 0待处理 1进行中 2已完成 3已驳回, handler_id int, handle_remark varchar(255), create_time datetime, finish_time datetime, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) );我特别想说的是order_no这个字段。很多同学的毕设里都不生成业务单号直接用自增ID做展示。但报修单这种东西学生和服务人员沟通的时候说“工单号xxx”比说“数据库第247条记录”要专业多了。我生成单号的方式是R 年月日 三位随机数比如R20250614103。这个细节在论文里可以写一小段答辩的时候提及“业务单号的生成策略”能给评委一种“这学生真做过项目”的印象。2.2 状态机是流转逻辑的灵魂报修单的状态这条线是整个系统的生命力所在。我定义了一个严格的状态流转不让它乱跳待处理(0) - 进行中(1) - 已完成(2) 待处理(0) - 已驳回(3)这个状态机意味着什么意味着已完成的单子不能再被驳回已驳回的单子也不能直接变成已完成。我在后端接口设计的时候每个状态变更操作都有前置状态校验。代码看起来就是这样def update_repair_status(repair_id, target_status, handler, remark): repair Repair.query.get(repair_id) if not repair: raise BizError(工单不存在) allowed_transitions { 0: [1, 3], # 待处理可以转进行中或者驳回 1: [2], # 进行中只能转已完成 } if target_status not in allowed_transitions.get(repair.status, []): raise BizError(f不允许从状态{repair.status}流转到{target_status}) # 业务操作...这个校验逻辑简单到只有几行但它体现了你对业务闭环的理解。答辩的时候你可以画一个状态流程图然后对着图讲一遍这个环节几乎必加印象分。2.3 数据权限不只是一个Where条件前面提到宿管员只能看自己楼栋的数据这里展开说说。如果你只在前端做判断那别人直接调接口就能越权。后端必须校验。我当时在Flask里写了一个装饰器def require_role(*roles): def decorator(f): wraps(f) def wrapper(*args, **kwargs): token request.headers.get(X-Token) user verify_token(token) if not user: return jsonify(code401, msg未登录) if user.role not in roles: return jsonify(code403, msg无权访问) g.user user return f(*args, **kwargs) return wrapper return decorator然后宿管员查报修列表时接口里强制带上楼栋过滤bp.route(/repairs) require_role(2, 3) # 宿管员和管理员 def list_repairs(): user g.user query Repair.query if user.role 2: # 宿管员 building AdminBuilding.query.filter_by(admin_iduser.id).first() if not building: return jsonify(code403, msg未分配管理楼栋) query query.filter_by(building_idbuilding.building_id) # 分页返回...这个设计最大的好处是“一个角色一套逻辑”不会出现接口复用导致的数据越权。也是后来答辩时评委反复追问的安全点问了三四轮我都能答上来。2.4 登录态与用户身份小程序端的登录我用的是微信官方的wx.login拿 code然后在后端调微信接口换 openid再用自己的逻辑签发 token。这是标准流程跟绝大多数小程序一致。登录接口逻辑是这样的小程序端wx.login()获取临时 code后端拿 code 调微信接口jscode2session换回 openid查数据库有没有这个 openid有就直接签 token没有就自动注册一个新用户默认角色为学生返回 token 和用户资料签 token 我用的是itsdangerous库其实就是一个签名串把 user_id 放进去def generate_token(user_id): s Serializer(current_app.config[SECRET_KEY], expires_in86400) token s.dumps({uid: user_id}) return token.decode(ascii)这个流程里有一个很关键的坑小程序端必须在用户点击授权按钮之后才调用wx.getUserProfile不能在一进来就弹窗。微信官方对用户隐私的限制越来越严你没做任何交互就弹授权窗会被判定为违规。我的做法是先进入系统用户如果需要使用真实姓名和头像再去个人信息页里触发授权。这样既合规又不会影响初次进入的无感体验。3. 实操过程与核心环节实现有了设计和接口框架这一章就聊聊我从零到落地到底怎么一步步做出来的以及过程中那些“如果没人告诉你你自己要踩好几天”的细节。3.1 前端小程序的工程结构原生小程序的目录结构其实很清晰核心就四个部分pages/、components/、utils/、app.js。我建议你不要一上来就分太多目录先把页面跑通再逐步抽公共组件。我实际的目录结构是├── app.js ├── app.json ├── app.wxss ├── pages/ │ ├── index/ # 登录/首页 │ ├── repair/ # 报修列表 │ ├── repair-form/ # 提交报修 │ ├── repair-detail/ # 报修详情 │ ├── notice/ # 通知列表 │ ├── dorm/ # 住宿信息 │ ├── mine/ # 个人中心 │ └── admin/ # 宿管员/管理员页面 ├── components/ │ ├── status-tag/ # 状态标签组件 │ └── empty/ # 空状态组件 └── utils/ ├── api.js # 封装 wx.request └── auth.js # token 存取utils/api.js这一步很重要。如果你每个页面都直接写wx.request那项目页面多起来之后改一个域名要翻好多个文件。我当时封装的写法现在看依然觉得实用const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method, data, header: { Content-Type: application/json, X-Token: wx.getStorageSync(token) || }, success(res) { if (res.data.code 401) { // token过期跳回登录页 wx.reLaunch({ url: /pages/index/index }); } resolve(res.data); }, fail(err) { reject(err); } }); }); }; module.exports { request };用 Promise 包装wx.request的好处非常多。你在页面里可以async/await串行处理逻辑不用嵌套好几层 success 回调。这个小改造会让前端的代码可读性提升一个档次。3.2 报修流程的前后端完整链路我拿“学生提交报修”这个最核心的流程来拆解你就能明白整个系统的数据是怎么流动的。第一步学生在页面选宿舍自动带出、填描述、拍照上传。图片上传这里的处理方式是先调wx.uploadFile传到后端的/api/upload接口后端把图片存到服务器的static/uploads/目录然后返回一个 URL。学生提交报修时这个 URL 会跟表单一起传到后端。第二步后端校验参数生成单号插入报修单记录状态为待处理。第三步宿管员端每隔一段时间拉取待处理列表或者小程序端用wx.requestSubscribeMessage订阅通知。这里需要说一下通知的事——我当时没有做微信模板消息推送因为个人小程序很多类目无法开通订阅消息。所以通知统一在系统内做宿管员打开管理页面就能看到待办角标学生打开详情页就能看到状态变化。这个对毕设来说足够了。如果你非要加一个“服务通知”得申请企业小程序学生身份的毕设基本审核不过不要浪费时间。第四步宿管员点击“开始处理”状态从待处理转为进行中处理完点击“完成”填一个备注状态变更为已完成。第五步学生端刷新列表看到状态变化整个闭环结束。前端页面展示状态的时候我写了一个状态标签组件Component({ properties: { status: Number }, observers: { status(val) { const map { 0: { text: 待处理, color: #f59e0b }, 1: { text: 处理中, color: #3b82f6 }, 2: { text: 已完成, color: #10b981 }, 3: { text: 已驳回, color: #ef4444 }, }; this.setData({ display: map[val] || {} }); } } });这个组件在多处复用避免重复写判断逻辑。你写毕设的时候养成这种“抽组件”的习惯论文里还能加一节“组件化设计”评委很吃这一套。3.3 宿管员端的查寝与卫生评分查寝功能我设计成“按楼栋 按日期”的模式。宿管员进入查寝页面选择楼栋、日期就能看到该楼栋的所有宿舍列表。每个宿舍后面有状态选项正常、未归、已请假。数据表的查询逻辑如下SELECT d.id, d.room_no, r.status FROM dorm_room d LEFT JOIN checkin_record r ON r.room_id d.id AND r.check_date 2025-06-14 WHERE d.building_id 1这个左连接写得比较取巧如果当天还没有查寝记录r.status就是 NULL前端默认显示“未查”。宿管员点选之后就做一次INSERT ... ON DUPLICATE KEY UPDATE当天记录存在就更新不存在就插入。卫生评分同理无非是多了几个评分维度地面、床铺、阳台、物品摆放。我把这四项平均一下作为总分。这个功能本身不复杂但要注意前端评分要用防重复提交。宿管员点太快或者网络卡顿可能一条记录被插入两次。解决方案就是在后端建一个唯一索引(room_id, check_date)这样数据库层面就不会有重复。别只靠前端按钮加loading那是防君子不防小人的。3.4 管理员端的数据统计与导出管理员端的统计功能就是个亮点菜。我用了 ECharts 在小程序里画柱状图和饼图统计维度包括各楼栋的报修数量对比报修类型分布水、电、木、网络近期每天的报修趋势平均处理时长数据接口就是几个聚合查询。比如报修类型分布SELECT category, COUNT(*) AS cnt FROM repair WHERE create_time BETWEEN ? AND ? GROUP BY category后端返回一组对象前端用饼图渲染。这部分的实现成本不高但对毕设的“完成度”提升是肉眼可见的。论文里可以配几张截图效果非常好。还有一个我录了一段录屏展示的“导出Excel”功能。后端用openpyxl生成 xlsx 文件小程序端通过wx.downloadFile下载后用wx.openDocument打开。这个小功能对实际管理是有用的做起来也不难。核心代码如下from openpyxl import Workbook def export_repairs(): wb Workbook() ws wb.active ws.append([单号, 宿舍, 分类, 描述, 状态, 创建时间, 完成时间]) rows query_repairs_for_export() for r in rows: ws.append([r.order_no, r.dorm_name, r.category, r.description, r.status_text, r.create_time, r.finish_time]) file_path save_to_temp_file(wb) return send_export_file(file_path)前端wx.downloadFile({ url: ${BASE_URL}/api/export/repairs, header: { X-Token: getToken() }, success(res) { wx.openDocument({ filePath: res.tempFilePath, fileType: xlsx }); } });能在小程序里直接打开 Excel这个交互在真实场景里也是实用的建议保留。4. 常见问题与排查技巧实录任何一个项目做完肯定是一路踩坑过来的。有些坑甚至不是代码报错能看出来的是逻辑上、细节上的。这一部分我把我踩过的、身边同学踩过的坑整理出来按“症状-原因-解法”的方式写方便你以后遇到同类问题直接索引。4.1 小程序不能直接访问本地IP这是一个新手必踩的天坑。你在开发者工具里本地后端跑在127.0.0.1:5000前端请求也填了127.0.0.1:5000但小程序开发工具默认是不允许访问本地的。报错信息晦涩经常是“request:fail”。解法是在开发者工具的“详情-本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”然后在“工具-代理设置”里把代理设为“不使用代理”。等到真机预览的时候更麻烦手机不能访问你电脑上的127.0.0.1。这时候你得让后端监听的地址是0.0.0.0然后手机访问的地址改成你电脑在局域网内的 IP。经典操作流程flask run --host0.0.0.0 --port5000然后真机调试时BASE_URL改成http://192.168.x.x:5000。注意手机和电脑要在同一个WiFi下。这个细节虽然基础但每年都有大量同学在这上面卡一整天。4.2 Token 过期可别只前端跳转前后端分离最大的坑之一就是“谁来判断登录失效”。我最初只在前端遇到 401 跳转首页但后端接口没有任何统一的校验处理。后来发现一个问题有些接口报 401 时前端跳转了但原因是参数错误而非 token 过期这会误导排查方向。所以我把后端统一做了全局异常捕获app.errorhandler(BizError) def handle_biz_error(e): return jsonify(codee.code, msge.message) app.errorhandler(TokenExpiredError) def handle_token_expired(e): return jsonify(code401, msg登录已过期)然后前端request封装里判断if (res.data.code 401) { wx.removeStorageSync(token); wx.reLaunch({ url: /pages/index/index }); wx.showToast({ title: 请重新登录, icon: none }); }这样 token 失效和业务报错两种 401 就彻底区分开了。排查的时候看返回的msg就能定位问题。4.3 并发提交导致的重复报修你有没有想过学生在报修页点击多次提交按钮会发生什么网络稍微慢一点前端的 loading 还没弹出来用户已经点了五遍于是后端产生了五张一模一样的报修单。前端解决方案是在提交前按钮加disabled但这不是银弹。严谨的做法是后端做幂等控制。我当时在报修表加了一个request_id字段也就是前端生成一个 UUID 传给后端。后端在插入前先查一下有没有相同request_id的记录existing Repair.query.filter_by(request_idreq.get(request_id)).first() if existing: return jsonify(code200, dataexisting)这个方案的原理就是“唯一键防止重复提交”成本极低但体现出的工程质量完全不一样。答辩的时候你要是主动说出“前端做了防抖后端做了幂等”评委想不给你高分都难。4.4 图片上传权限和大小限制学生报修要传图这个需求很常见但也有坑。微信小程序的wx.uploadFile默认大小限制是 10M这个还好但要注意很多云开发环境里图片不能直接存。如果你用 Flask 自带的静态目录存储要注意 Nginx 层对client_max_body_size的限制默认可能只有 1M传图失败时会报 413。我当时的做法是后端限制图片大小 5M并且做了格式白名单.jpg.png.jpeg。上传时做尺寸压缩一般手机拍照 2-4M压缩后变成几百K存储压力小很多。前端用wx.compressImage压缩后再传这个API很实用wx.compressImage({ src: tempFilePath, quality: 70, success(res) { // 把 res.tempFilePath 拿去 uploadFile } });4.5 查寝记录用“今天”导致的时区问题这个坑是从真实项目里搬来的教训。如果你后端跑在服务器上日期函数用的是服务器本地时间而服务器时区不是北京时间那当天查寝记录可能全部错位。尤其部署到云主机后默认时区往往不是Asia/Shanghai。解法是在后端应用启动时强制设置时区import os os.environ[TZ] Asia/Shanghai在 Flask 里更稳妥是用datetime库直接生成并且统一用前端传来的日期字符串。我的做法是查寝页面的日期由前端选择并直接传给后端后端只做解析不自己生成“今天”。这样避免了很多时区麻烦。4.6 常用问题速查表我把密集踩过的技术问题按照优先级整理成了一张表方便你遇到问题直接查。症状可能原因解决方案小程序请求失败未勾选“不校验合法域名” / 真机IP不对开发者工具勾选设置真机用局域网IPtoken一直失效签发的token有效期太短或校验逻辑有问题确认后端verify_token是否读取请求头正确报修单重复前端多次提交后端无幂等控制后端用request_id唯一键防重图片上传413Nginx或框架默认文件大小限制调整client_max_body_size和上传大小查寝数据错一天服务器时区不是北京时间设置时区或后端不自行生成日期小程序发布后接口不可用正式版有域名校验配置HTTPS域名到小程序后台白名单5. 答辩要点与毕设进阶扩展建议项目本身做完只是一个里程碑对毕设来说答辩表现同样重要。我见过太多代码写得不错但答辩讲不到点子上的同学最后分数平平。所以这一章聊一聊答辩怎么讲、往哪个方向扩展能加分。5.1 答辩时要把“选择理由”和“演进过程”讲清楚很多同学答辩时只会平铺直叙“我做了CRUD、我用了一些技术”这样讲出来的效果就像在念开发日志毫无亮点。换个思路——评委最想听到的是“权衡”。比如你可以说“我的系统选型经历了从 SQLite 到 MySQL 的调整原因是随着数据表增多和 ER 图的完善需要更规范的数据库管理同时也因为论文里需要体现更正式的生产级配置。”这就是一个“演进”的故事。比一开始就选 MySQL 然后说“我用的是MySQL”要生动得多。再比如你可以说“我在设计报修单的时候将状态设计为枚举常量而不是硬编码字符串目的是避免魔法数字和时间字符串混在业务代码里。”这种细节讲出来评委马上知道你不是只会调包而是真的在思考。我建议答辩前把以下问题都准备一遍如果用户量和数据量增长系统哪里会先成为瓶颈你的权限模型如何扩展到更多角色数据库表为什么这么设计有没有冗余有没有反范式如果小程序端更复杂了比如增加社交功能你会怎么改架构报修单状态机如果新增分支怎么保证原有流程不受影响每个问题都不是让你背答案而是考察你有没有真正吃透这个系统。你只要做完了上面这些设计其实大部分问题都能自然回答上来。5.2 让毕设有“一技之长”的三个方向如果你的时间充裕想在同题目的毕设里做出一点点差异化“亮点”不需要多一两个就行。我给三个方向按性价比排序。第一个是消息通知闭环。虽然个人小程序不能随便用模板消息但你可以做一个“待办推送”的邮件提醒——宿管员收到新报修时后端自动发一封邮件。技术实现不复杂但做成一个独立模块之后系统就从一个“同步查询系统”变成了一个“主动通知系统”这个变化在答辩时很容易讲出彩。第二个是数据大屏。管理员端除了看统计图表还可以做一个可视化的宿舍楼状态总览——每个楼栋、每个宿舍都有颜色标识实时的入住率、报修率、评分情况一眼可见。技术上用到一个canvas绘制或者普通表格也可以但视觉冲击力强很多。做起来不算复杂因为基础数据都是现成的。第三个是缓存层。如果你的查询里面有很多聚合统计每次实时去数据库GROUP BY开销并不大但可以让系统体现出“性能意识”。比如管理员首页的统计可以用缓存存五分钟过期再重新查库。代码量很小但“缓存-失效-回源”这个链路写进论文技术厚度一下就不一样了。5.3 从毕设到“产品”的最后一公里最后说一点关于心态的话。很多同学把毕设当成任务去交差做完就扔非常可惜。宿舍管理系统这种项目其实是“完美落地场景”代码量适中、业务边界清晰、可以不断往上叠加新功能、甚至能直接拿到真实小区物业去用。我自己的经验是做几个“产品化”的改造整个过程对能力的提升比写十篇实验报告都有用。这个项目后续可以考虑的链路很多微信支付预缴电费、宿舍门禁二维码联动、同学互评的宿舍评分体系等等。如果你还有时间挑一个方向深入两三天很快就发现它已经不是“毕设”了而是一个真正可以用的工具。用过的人给你反馈你再去改这种迭代的感觉是课堂上学不到的。回归到最初的问题——为什么选择这个毕设题目我现在的回答依然是它足够真实。真实到你不需要编一个高大上的业务背景去衬托它的价值它就在每个大学生身边你懂评委也懂。如果让我重新选一次我还是会做宿舍管理系统但我会在架构设计阶段就想清楚“用户拿到的最终体验是什么”而不是一上来就写登录注册的代码。把业务故事的最后一个场景放在脑子里再往回想技术方案项目的方向就不会跑偏。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进