1. 毕设选题的算账逻辑:门诊系统这门"性价比"体现在哪
每年到毕设开题季,计算机专业的同学都会纠结同一个问题:做什么题目既能让导师点头,又能保证自己三个月内写得完?我的观点一直很明确——如果你是 Java 技术栈,门诊管理系统(也叫门诊医疗管理平台、医院门诊诊疗管理系统)是性价比最高的选择之一,这话不夸张。
为什么这么说?先算一笔账。一个合格的毕设要同时满足三个考核点:业务复杂度、技术覆盖度、可演示性。门诊管理系统恰好三项全占。
业务复杂度这块,它不是一个简单的增删改查堆砌题。"患者建档—挂号—分诊—医生诊疗—开处方—收费—药房发药"这条链路跨了七个环节,涉及四类角色(患者、挂号员、医生、药房管理员),每两个环节之间都有状态流转和数据一致性问题。比如患者挂了一个号,医生接诊后才能开处方;处方没有收费之前药房不能发药。这种业务规则不是靠页面多就能糊弄过去的,而是要体现在表结构、接口权限和事务控制里。
技术覆盖度更不用说。SpringBoot 做接口层,MyBatis-Plus 做数据访问,MySQL 存业务数据,前端用 Vue 或者 Thymeleaf 渲染页面,再配一套基于 Token 的登录鉴权——这一套组合下来,Java 基础、数据库、Web 开发、软件工程这几门核心课程的知识点全用上了。答辩时老师问"你项目里用了哪些技术",你能给出一份长长的清单,而且每一项都有对应的落点,不是背概念。
可演示性可能是最容易被忽略、也最要命的一点。门诊系统的演示效果天然就强:打开首页能看到今日挂号排队列表,医生端能录入诊断结果并生成处方,药房端收到处方后一键确认发药,收费员完成结算后系统自动生成收费流水和报表数据。每一步操作都有页面反馈,老师坐在那里看五分钟就能理解整个系统是干什么的。这比做一个抽象的"通用后台管理系统"有说服力得多。
当然,也有人说这题目太"烂大街"了。我的看法是:同题不同质。题目一样,但导师一眼就能分辨出谁是真做了、谁是从网上扒代码随便改了改。
1.1 一个选题覆盖三大学分考核点
展开说说刚才提到的三个维度,它们其实对应毕设评审最核心的标准。
第一个维度是工作量。毕设最忌讳"功能太少、页面太少"。门诊系统天然包含三个层次:基础数据管理(科室、医生、药品、患者),业务流转(挂号、诊疗、处方、收费),统计报表(日就诊量、科室收入、药品消耗)。三层铺开就是二十多个页面、十几张表,工作量完全够,不会出现"一个学期只写了个登录页面"的尴尬。
第二个维度是技术难度。系统里藏着几个典型的计算机问题:并发场景下的号源控制、跨表事务下的处方与库存一致性、多角色权限的数据隔离。这些不是功能清单里的"添头",而是真正需要动脑子设计的点,也是答辩时最能展示水平的地方。一个能把并发挂号讲明白的学生,和一个只能说"这个我调了接口"的学生,差距是肉眼可见的。
第三个维度是工程规范。SpringBoot 项目的分层架构(controller/service/mapper)、统一返回结构、全局异常处理、日志记录——这些东西在课堂作业里可以不讲究,但毕设里做了,论文就有内容可写,答辩就有话可说。很多同学论文第三章"系统设计"写不满两页,就是因为代码里根本没有设计可言。
1.2 同题不同质:导师眼里"用心做"和"凑数做"的分界线
我见过不少拿同一套源码来改的毕设。改得好的,会把表结构重新设计一遍,把科室排班、号源池、退费流程这些细节补上;改得敷衍的,连登录页面的医院名称都没换。
导师判断你有没有用心,看的往往不是代码量,而是异常和反向流程。举个例子:患者挂号后不想看了要退号,号源要不要释放?如果退号发生在收费之后,挂号费退不退?药已经发了但患者要退药,库存怎么回补?这些边界问题,网上下载的"通用门诊管理系统"源码基本都不会处理。你只要把退号、退费、退药这几个反向流程做完整,就已经甩开同题选手一大截。
所以我的建议是:选题大胆选门诊系统,但别满足于"能跑"。把反向流程做完整,把并发控制做扎实,把数据一致性讲清楚,这篇毕设的质量上限会非常高。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 把门诊流程翻译成系统需求:六个环节、四类角色、一张状态图
开始写代码之前,先花一周时间把业务理清楚。这一步省不得,因为后面所有表结构、接口划分、页面设计都依赖它,而且它直接决定了你论文第二章"需求分析"有没有内容写。
门诊诊疗的核心链路,我拆成六个环节:
- 患者建档:患者第一次来医院,登记姓名、身份证号、手机号、既往病史。
- 挂号:患者选择科室和医生,系统从号源池扣掉一个号,生成挂号记录。
- 候诊:医生按顺序叫号,系统更新候诊队列状态。
- 医生诊疗:医生查看患者历史病历,填写主诉、诊断结果,必要时开具处方。
- 收费:收费员根据处方金额收款,更新收费状态,生成收费流水。
- 药房发药:药房看到已收费的处方,核对药品库存后发药,扣减库存。
每一步都是上一环节的输出驱动,任何一个环节断掉,后面的动作就做不了。这种"流程驱动"的业务模型,是最适合做毕设的,因为它天然需要状态管理。
2.1 四类角色的权限边界
系统里有四类使用者,权限边界必须从设计初期就划清楚。我习惯用一张表把"谁能做什么、谁不能做什么"固定下来:
| 角色 | 核心权限 | 不能做的事 |
|---|---|---|
| 患者 | 查看个人信息、历史就诊记录、挂号记录 | 不能修改诊断、不能看其他患者信息 |
| 挂号员/收费员 | 建档、挂号、退号、收费、退费 | 不能写处方、不能改诊断 |
| 医生 | 接诊、写病历、开处方、查看自己患者的病历 | 不能收费、不能操作库存 |
| 药房管理员 | 查看已收费处方、发药、处理退药、管理药品库存 | 不能改处方内容、不能改收费金额 |
这张表画出来之后,接口设计就有章法了:哪些接口需要校验当前登录用户的角色,哪些查询只允许医生查自己的患者,哪些操作必须加事务锁。这些都是答辩时老师必问的点,提前想清楚就能对答如流。
2.2 挂号单的一生:用状态机约束业务流转
我见过很多同学把挂号记录设计成"只有新增没有状态"的表,这是大忌。一条挂号记录从创建到完结,至少要经历这些状态:
- 0 待就诊:已挂号、未接诊
- 1 就诊中:医生开始接诊
- 2 已完成:诊疗结束、处方已开
- 3 已退号:任意未就诊状态都可以触发,但已就诊后不能退号
同样,收费记录有自己的状态:0 未收费、1 已收费、2 已退费。处方也有:0 待收费、1 已收费待发药、2 已发药、3 已退药。
设计状态字段时,建议用 int 型枚举值存储,代码里用枚举类定义,不要直接在数据库里存中文状态。否则排序、统计、条件查询都会出问题——比如你想统计"今天退号了几个人",如果状态存的是中文"已退号",你还得写模糊匹配。用数字,一个等值查询就完了。
业务理清楚之后,再选技术栈,就顺理成章了。
3. 技术栈落地实录:SpringBoot 版本兼容、MyBatis-Plus 生成 SQL 的坑
技术栈这块,我直接给一套验证过的、适合毕设的组合,再讲讲最容易翻车的地方。
推荐组合:
- JDK:17。别用 8,也别追求最新的 21。
- SpringBoot:2.7.x 或者 3.x 都行,但选完之后所有依赖版本都得跟着走。
- MyBatis-Plus:3.5.x,具体版本要和 SpringBoot 匹配。
- MySQL:8.0。
- 前端:Vue 3 + Element Plus(前后端分离),或者 Thymeleaf(传统 Web 项目)。
- 构建工具:Maven。
3.1 SpringBoot 版本太高,可能是你遇到的第一个大坑
很多同学习惯去 start.spring.io 上生成最新版 SpringBoot,一上来就是 3.3、3.4。然后就会遇到一个非常经典的问题:pom.xml 里引入 MyBatis-Plus 的 starter,启动时报 ClassNotFoundException: javax.servlet.Filter 或者 NoClassDefFoundError: javax/sql/DataSource。
这个坑的根源是 SpringBoot 3.x 把 Java EE 的 javax 命名空间整体迁移到了 jakarta。SpringBoot 2.x 生态里所有依赖都用 javax.servlet,到 3.x 全得换成 jakarta.servlet。如果你的 MyBatis-Plus 版本偏老,它的源码里写死的是 javax,在 SpringBoot 3.x 下直接 ClassNotFound。
解决办法有两个。第一个:稳定优先,用 SpringBoot 2.7.18 + MyBatis-Plus 3.5.3.1 + JDK 8/11,这套组合最成熟,网上资料最多,遇到问题搜得到答案。第二个:坚定用 SpringBoot 3.x,那 MyBatis-Plus 必须用适配 jakarta 的 3.5.3 以上版本,同时 JDK 升到 17。
我的个人建议是:毕设求稳,选方案一。除非你论文里想专门写"基于 SpringBoot 3 的新特性",否则没必要在兼容性上冒险。答辩老师更关心的是业务逻辑,不是版本号。
3.2 MyBatis-Plus 根据实体类生成建表 SQL?别偷懒,要手写建表
热词里有个说法是"mybatisplus根据java实体类生成创建表的sql语句"。确实有类似工具能根据实体类反射生成 DDL,但实际用起来体验一般:生成的字段注释、索引设计、长度设置都得手动补,甚至可能出现 varchar(255) 一把梭的灾难。数据库设计的主动权必须握在自己手里。
我的做法是反过来:先手写 SQL 建表,再用 MyBatis-Plus 的代码生成器从数据库表反向生成实体类、Mapper、Service。这样表结构是精心设计的,实体类只是映射,不会出现"为了将就实体类而歪曲表设计"的情况。
给一个挂号表的建表模板:
sql复制CREATE TABLE registration (
id BIGINT NOT NULL COMMENT '挂号ID(雪花ID)',
patient_id BIGINT NOT NULL COMMENT '患者ID',
doctor_id BIGINT NOT NULL COMMENT '医生ID',
dept_id BIGINT NOT NULL COMMENT '科室ID',
registration_no VARCHAR(32) NOT NULL COMMENT '挂号流水号',
status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待就诊 1就诊中 2已完成 3已退号',
fee DECIMAL(10,2) NOT NULL COMMENT '挂号费',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
KEY idx_patient (patient_id),
KEY idx_doctor (doctor_id),
KEY idx_status (status)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '挂号记录表';
几个关键点:id 用雪花 ID 不用自增主键,原因后面详细说;status 用 TINYINT 存枚举值;create_time 和 update_time 交给数据库自动维护,省去 Java 端手动 set 的麻烦。
3.3 项目目录结构:给论文里的"分层架构"一个体面的样子
SpringBoot 的标准分层是 controller → service → mapper,大家都会写,但很多同学把业务逻辑全堆在 controller 里。门诊系统这种业务链路长的项目,分层不清晰的话,后面写退费、退药逻辑时会非常痛苦。
我习惯的目录结构:
text复制com.hospital.outpatient
├── controller # 接口层,只做参数接收和结果返回
├── service # 业务层,核心业务逻辑都在这里
│ └── impl
├── mapper # MyBatis-Plus 数据访问层
├── entity # 数据库实体
├── dto # 接口入参/出参对象,不直接暴露实体
├── common # 统一返回结果、枚举、异常、工具类
└── config # MybatisPlusConfig、WebConfig、CorsConfig
规则只有一个:controller 里不写业务判断,service 里不写 SQL。所有跨表操作、状态校验、事务控制,全部收敛到 service 层。这样做的直接好处是:各模块之间的调用关系在论文的功能模块图里画得清清楚楚,代码的可读性也好得多。
4. 数据库建模实战:14 张核心表的依赖关系拆解
数据库设计是门诊系统真正的"地基"。我设计的门诊系统核心表大概是 14 张,分成四个域:
- 基础数据域:sys_user(系统用户)、dept(科室)、doctor(医生信息)、drug(药品)。
- 患者域:patient(患者档案)。
- 业务域:registration(挂号)、diagnosis(诊断)、prescription(处方)、prescription_item(处方明细)、charge_record(收费记录)。
- 库存与辅助域:drug_stock(药品库存)、stock_record(出入库记录)、schedule(医生排班)、operation_log(操作日志)。
4.1 主键策略:为什么不用自增 ID
这是我在多个项目里反复强调的一点。门诊系统的业务表主键,强烈建议用雪花 ID(MyBatis-Plus 内置 ASSIGN_ID 策略),而不是数据库自增。
原因有三条。
第一,数据迁移和导入导出时的冲突问题。自增 ID 一旦导出再导入,ID 就可能和已有数据撞车。医院系统经常要做数据迁移,这个问题在真实场景里很常见。
第二,接口安全。如果挂号记录 ID 是自增的,请求 registration/12、registration/13 就能遍历全部数据。毕设虽然安全等级要求没那么高,但老师问一句"ID 为什么不用自增",你能答出"为了避免遍历和数据迁移冲突",这是加分项。
第三,并发写入时的效率。自增 ID 在 insert 之后才能拿到 ID 值,写关联子表时要先插主表再拿自增 ID 塞子表,多一次往返。雪花 ID 可以在内存中生成,子表直接引用,一次事务里就能完成。
MyBatis-Plus 里配置很简单:
java复制@TableId(type = IdType.ASSIGN_ID)
private Long id;
4.2 处方主表和明细表:一对多的经典建模
处方和处方明细是典型的一对多关系:一条处方对应多条药品明细。很多新手会犯一个错误——把药品明细直接塞进处方表的几个字段里(比如 drug1、count1、drug2、count2),这在数据库建模上是大忌。任何可重复、可枚举的属性,都要拆成子表。
处方明细表的关键字段:
sql复制CREATE TABLE prescription_item (
id BIGINT NOT NULL COMMENT '明细ID',
prescription_id BIGINT NOT NULL COMMENT '处方ID',
drug_id BIGINT NOT NULL COMMENT '药品ID',
drug_name VARCHAR(100) NOT NULL COMMENT '药品名称(冗余快照)',
quantity INT NOT NULL COMMENT '数量',
unit_price DECIMAL(10,2) NOT NULL COMMENT '单价',
total_price DECIMAL(10,2) NOT NULL COMMENT '小计',
dosage VARCHAR(200) COMMENT '用法用量',
PRIMARY KEY (id),
KEY idx_prescription (prescription_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='处方明细表';
这里有一个很值得写进论文里的设计细节:drug_name 和 unit_price 是冗余快照字段。为什么冗余?因为药品名称和价格会变。医生开处方的时候价格是 10 元,一个月后调价成 12 元,历史处方上的金额不应该跟着变。所以处方明细里必须保存开单那一刻的名称和价格快照,而不是联表去查药品表的最新值。这个点一旦在论文和答辩里提出来,老师就知道你是真的接触过业务系统,不是只会写 CRUD。
4.3 库存表:发药扣库存,但要注意窗口期
药品库存设计也要提前想清楚。发药动作如果只是简单地把库存字段减一,在"收费后未发药"这个窗口期,如果有人查询库存,看到的是虚高数字。更严谨的做法是引入锁定库存的概念:
- 可用库存(available_stock):真正能卖给新患者的数量。
- 锁定库存(locked_stock):已经被已收费处方锁定、等待发药的数量。
流程是:医生开处方时校验可用库存,充足就扣减可用库存、增加锁定库存;发药完成后再扣减锁定库存。如果退药,则反向操作,可用库存加回来。
毕设阶段我的建议是:库存表先做到"发药时校验并扣减"就够用了,如果时间充裕再实现锁定库存。但表结构设计时,available_stock 和 locked_stock 两个字段要提前预留,否则后面加字段很麻烦,而且这段进阶设计正好可以写进论文的"系统优化"章节。
5. 三个核心难点的编码方案:并发挂号、处方流转、收费状态机
铺垫了这么多,终于到写代码的正题。我挑三个"网上源码几乎都做不好"的点,把方案讲透。这三个点也是答辩时最能体现你"真的懂"的地方。
5.1 并发挂号:同一个号源怎么保证不超卖
某个医生某天出诊有 50 个号,50 个人同时来抢,最后产生的挂号记录数必须严格等于 50,不能多。这就是经典的并发扣减问题。
最容易想到的写法是:先查 count,如果 count 小于总号数就 insert,然后 count 加一。这在单线程下没问题,并发一上来就超卖——两个请求同时查出来剩余 1 个号,都执行 insert,最后变成 2 条记录。
解决办法是数据库层面的原子操作。在 schedule(排班表)里维护一个 remain_count 字段,扣减时用一条 SQL 完成"判断 + 更新":
java复制@Transactional(rollbackFor = Exception.class)
public Registration register(RegisterDTO dto) {
// 关键:原子更新号源,防止并发超卖
int updated = scheduleMapper.deductRemainCount(dto.getScheduleId());
if (updated == 0) {
throw new BizException("号源已被抢完,请选择其他时段");
}
// 扣减成功才允许生成挂号记录
Registration reg = new Registration();
// ... 填充字段、生成流水号
registrationMapper.insert(reg);
return reg;
}
对应 Mapper XML:
xml复制<update id="deductRemainCount">
UPDATE schedule
SET remain_count = remain_count - 1
WHERE id = #{scheduleId} AND remain_count > 0
</update>
关键在于 AND remain_count > 0 这个条件。MySQL 的 UPDATE 会对命中的行加行级锁,多个并发请求同时执行时,只有一个能真正减到 1,其余因为条件不满足,影响行数为 0。这就从数据库层面杜绝了超卖。
有人会问:能不能不用 @Transactional?不行。这个注解保证"扣号源成功但插入挂号记录失败"时,号源能回滚回去。扣减和插入必须同生共死,否则会出现号源扣了但挂号记录没生成的情况。跨表写操作加事务,这是毕设代码里必须养成的习惯。
5.2 处方流转:医生开方之后,药房怎么知道有活干了
处方从医生端流转到药房端,最朴素的办法是药房页面每隔几秒轮询一次,查"有没有新的待发药处方"。毕设完全可以这么做,但要注意查询条件要按状态过滤,别把全部处方查出来再在内存里过滤。
核心代码:
java复制public Page<PrescriptionVO> listPendingDispense(int page, int size) {
LambdaQueryWrapper<Prescription> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Prescription::getStatus, PrescriptionStatus.PAID); // 已收费待发药
wrapper.orderByAsc(Prescription::getCreateTime);
return prescriptionMapper.selectPage(new Page<>(page, size), wrapper);
}
我特意用 LambdaQueryWrapper 而不是写自定义 SQL,原因是类型安全:字段名写错了编译期就能发现,不用等运行时报错。这一个小细节,能体现你对 MyBatis-Plus 的熟练度。
前端轮询时,用 setTimeout 递归代替 setInterval。原因很简单:setInterval 不管上一次请求有没有返回,到点就发下一次,可能造成请求堆积;setTimeout 递归是上一次完成后才调度下一次,更稳。这个细节写在博客笔记里不起眼,但演示时如果网络慢,区别会很明显。
5.3 收费和退费:用状态机约束,别在业务代码里撒 if
收费记录的状态流转方向是:未收费 → 已收费 → 已退费。如果代码里到处写 if (status == 1),后面想加"部分退费"状态时,改起来会想哭。规范做法是定义一个枚举类,把状态和允许的流转收拢在一起:
java复制public enum ChargeStatus {
UNPAID(0, "未收费"),
PAID(1, "已收费"),
REFUNDED(2, "已退费");
private final int code;
private final String desc;
public boolean canTransferTo(ChargeStatus target) {
if (this == UNPAID) return target == PAID;
if (this == PAID) return target == REFUNDED;
return false;
}
}
收费和退费的 service 方法里统一调用 canTransferTo,不满足就抛异常。整个收费模块的状态流转逻辑集中在一处,以后想扩展状态,只需要改这一个枚举。论文的"系统设计"章节可以直接贴这个枚举类,老师一看就明白你懂状态机设计。
5.4 跨表事务:退费时处方、收费记录和库存怎么保持一致
退费和退药往往连着发生。患者交了钱没取药要退费,处方状态要从"已收费"回到"未收费"或"已退药",收费记录要标记为"已退费",如果已经发药了还要回补库存。这个操作至少涉及 3 张表的更新,必须放在同一个事务里。
我的习惯是:凡是涉及多表写操作的方法,都加 @Transactional(rollbackFor = Exception.class),并且把 rollbackFor 写明白。这里有个非常经典的坑——@Transactional 的默认行为是只对 RuntimeException 回滚。如果你在业务代码里抛了一个自定义异常类,但它继承的是 Exception 而不是 RuntimeException,事务不会回滚,数据就处在"改了一半"的状态。这个知识点,十个答辩学生里有八个说不清楚,你能说清楚就是亮点。
6. 答辩现场防翻车指南:事务边界、演示数据与追问预案
最后聊聊答辩。很多同学项目做完了,一上台就紧张,老师随便问一个问题就卡壳。其实毕设答辩的问题来来去去就那几类,提前准备好就行。
6.1 三个最常见翻车现场
第一个:事务失效掉进坑里。老师问"你的退费操作如果中途失败了,数据怎么保持一致"。这时候你要是发现自己代码里压根没加事务注解,或者自定义异常没继承 RuntimeException,那就尴尬了。所以写代码的时候务必要检查,凡是多表写操作,事务注解一个都不能少。
第二个:并发问题答不上来。老师问"你考虑过两个患者同时挂最后一个号的情况吗"。如果代码里只有先查再插,没有原子更新,这题就答不上来。解决方案就是 5.1 里讲的那条 UPDATE SQL,把原理讲明白,老师就知道你懂并发控制。
第三个:启动报错不会看。很多同学的 SpringBoot 应用启动时控制台飘红,最常见的是端口被占用、数据库连接失败、Mapper 扫描不到。我的建议是:答辩之前把启动日志从头到尾过一遍,做到"系统出任何异常你能说出原因"。哪怕只是"这个错误是数据库密码写错了",你处理过一次,思路就对了。
6.2 演示数据要"演"出业务感
演示环节的翻车往往不在功能,而在数据。很多同学的数据库里随便插了几条测试数据,医生叫"医生A",药品叫"药1",一眼看过去就很假。我的建议是:准备一套贴近真实的演示数据,至少包括 5 个科室、10 位医生、30 种药品、20 位患者,以及若干条已经走完"挂号—诊疗—收费—发药"全流程的历史记录。
这样演示的时候,你可以先打开"今日就诊统计"页面,指着一排数据说"这是系统实时统计的就诊量和科室收入",然后从一张挂号单开始,完整走一遍接诊、开方、收费、发药的流程。老师看到的是一个数据充盈、逻辑闭环的系统,而不是一个测试库。
另外,演示前一定要在干净环境里重新启动应用,跑一遍完整流程,确认没有脏数据干扰。很多演示翻车都是因为之前测试留下的"半截数据"卡在某个中间状态,导致后续操作到处报错。这种事我见过太多次了,提前演练一遍能避免现场冒汗。
6.3 追问预案:把设计文档上的每个决定变成答辩素材
最后给一个实操技巧:拿一张纸,把系统里每一个"我为什么这么做"写下来。
为什么用雪花 ID?因为要避免遍历和数据迁移冲突。为什么处方明细冗余药品名称?因为价格和名称会变,历史数据不能跟着变。为什么挂号扣减用原子 SQL?因为要先保证不超卖,再谈用户体验。为什么收费状态用枚举?因为状态流转要可控制、可维护。
这些问题,每个都能在答辩时变成展示思考深度的机会。你不需要背答案,只要项目真是自己做的,这些问题你写代码时都想过一遍。答辩不是考核,是把你做过的思考用语言说出来。
说得直白一点,毕设答辩不是比谁代码量大,而是比谁讲得清楚"代码为什么这么写"。门诊管理系统给了你大量这样的素材,抓住上面这几个核心点,原理讲透,这个项目拿个不错的成绩是大概率事件。
我自己带过的学生里,用这套方案做门诊系统,最典型的节奏是:前期调研和画流程图用了两周,动手写代码只用了三周,剩下时间全在补异常处理、反向流程和边界测试。这个节奏很健康——业务想得越清楚,代码反而写得越快,最后论文和答辩都从容。如果你正准备开题,不妨按这个思路来。
