简介:这是一套面向高校Java课程设计、毕业设计及项目实训的BS架构项目监管系统资料包,由可运行代码、系统设计论文和答辩PPT三部分构成,便于从开发调试到汇报展示全流程参考。系统覆盖用户登录与角色权限管理、项目提交与审批、计划与实施进度跟踪、结题与成果展示、数据报表与分析等模块,能够体现Browser/Server分层架构、权限控制和业务流转的设计思路,适合需要完成相似系统或理解项目申报管理流程的Java学习者。资源以zip压缩包提供,约18.24MB;详情页未显示文件清单,压缩包内通常按代码、论文、答辩PPT三类素材组织,代码部分可用于二次开发与功能扩展,论文可作课程设计或毕业设计写作参考,PPT便于直接演示答辩。目前已有65人学习浏览,对课程设计、项目实训或毕业设计阶段的B/S系统开发具有综合参考价值。
1. 一个毕设级别的「项目监管系统」到底在管什么
每到毕设季,身边总有人在问:Java项目基于BS的项目监管系统,到底该怎么做才算完整。所谓BS架构,就是浏览器当客户端,服务端管逻辑和数据,用户打开网址就能用,不用装任何客户端;项目监管系统管的是项目台账、进度跟踪、超期预警和审批流转,在工程监理、软件开发、科研课题管理里都能落地。说白了,就是给管理者一面能看到所有项目状态的仪表盘。这篇文章我会按自己做过的方式,把技术选型、数据库设计、核心代码、论文和答辩PPT一条线讲透,新手照着搭能跑起来,熟手能直接拿来改成自己的业务。
2. 技术选型先别急着敲键盘:为什么是 Spring Boot + MyBatis-Plus 而不是 SSH
2.1 BS架构为什么在监管系统里依然是默认答案
监管系统这类业务有一个共同特征:用户角色多、使用地点分散、权限层级分明。管理员要坐在办公室里看全局,项目经理可能在现场用笔记本登录,普通员工只想填个进度表。如果做成CS架构,每一次升级都要跑到每台电脑上去更新客户端,这在真实项目里就是灾难。BS架构把客户端统一收敛到浏览器里,服务端升级完,所有人刷新页面就是新版本,这一点在答辩时是必须讲清楚的「选型理由」。
另外,监管系统的数据敏感度不像财务系统那么极端,不需要本地离线操作,网络依赖完全可接受。浏览器端做展示和表单提交,服务端负责业务规则和权限校验,这种分工天然契合BS模型。SSH(Struts + Spring + Hibernate)时代已经过去了,现在再拿Struts 2写新项目,配置繁琐不说,答辩老师看到也会皱眉。Spring Boot + MyBatis-Plus + MySQL是目前做这类管理系统的常见组合,理由很朴素:Spring Boot把配置简化到近乎「开箱即用」,MyBatis-Plus把单表增删改查的样板代码压到最少,MySQL则是大家最熟悉的关系型数据库,出问题容易找人问。
2.2 技术栈清单:Spring Boot + MyBatis-Plus 组合的选型理由
| 层级 | 选型 | 选型理由 |
|---|---|---|
| 前端页面 | Vue 2 + Element UI 或 Thymeleaf + Bootstrap | 管理后台界面以表格、表单、弹窗为主,Element UI 组件现成,能省大量样式时间 |
| 后端框架 | Spring Boot 2.7.x | 内置Tomcat,打包成jar直接跑,不用额外部署应用服务器 |
| 持久层 | MyBatis-Plus 3.5.x | 继承MyBatis的SQL控制力,又自带分页插件、LambdaQueryWrapper、代码生成器 |
| 数据库 | MySQL 8.0 | 语法通用,网上资料多,Navicat等工具成熟 |
| 鉴权方案 | Session + 拦截器 | 毕设场景不需要引入Spring Security全家桶,拦截器代码量小且面试容易讲清 |
| 前端构建 | npm + Vue CLI(若用前后端分离) | 开发热更新快,打包后放Spring Boot静态目录或单独部署均可 |
这套组合的核心逻辑是「够用、好讲、不折腾」。Spring Security当然更正规,但配置项多,一旦理解不透彻,答辩时被追问细节很容易卡壳。Session + 拦截器的方式,代码就那么几行,逻辑一眼能看穿,老师问起来你能把每个环节讲透。
2.3 最小骨架:从空目录到能启动的十分钟
我一般会先用Spring Initializr生成一个最小项目,或者直接手写pom.xml。关键依赖就这几个:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>这段配置里,Spring Boot的parent锁定了依赖版本,mybatis-plus-boot-starter会自动装配MyBatis-Plus的核心功能,mysql-connector-j是MySQL 8的JDBC驱动。注意,如果你用的是MySQL 5.7,驱动坐标会略有不同,用旧版mysql-connector-java即可。
对应的application.yml也不是随手写的,每个参数都影响能不能跑起来:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/supervise_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4&allowPublicKeyRetrieval=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: autouseSSL=false是开发环境必须的,不然本地连接MySQL 8时会有SSL握手告警甚至直接失败;serverTimezone=Asia/Shanghai解决时区偏差八小时的问题;characterEncoding=utf8mb4保证中文字段不变成问号。allowPublicKeyRetrieval=true是应对MySQL 8默认认证插件的,不加这一项会出现「Public Key Retrieval is not allowed」的报错。整个项目在IDE里启动后,访问http://localhost:8080如果能拿到一个错误页面或提示,说明Tomcat已经起来了,骨架没问题。
3. 从建表开始把监管模型立起来:用户、项目、进度、预警四张核心表
3.1 监管系统的核心是「管状态」:台账、进度、预警的三角关系
很多第一次做监管系统的人会把重点放在增删改查上,结果做出来一个「花哨的Excel」。真正的监管系统,核心是「管状态」:一个项目立项之后是什么状态、目前进度到了哪一步、下一步该谁处理、是不是快逾期了。你写的每张表、每个接口,都在为回答这四个问题服务。项目表是主数据,进度表记录状态变化的历史,预警表把「该处理但没处理」的情况捞出来,用户表则决定谁能看到什么。这四张表的关系是:用户能创建和参与项目,项目下面挂着多次进度记录,每次进度记录都可能触发一条预警。
3.2 四张核心表的DDL:字段类型怎么选才不被答辩老师问住
我不建议一上来就用在线建表工具生成一大堆字段,监管系统的表结构应该精简到「每个字段都说得清用途」的程度。下面这四张表是我反复调整后留下的最小集:
CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(255) NOT NULL COMMENT 'BCrypt加密后的密码', real_name VARCHAR(50) NOT NULL COMMENT '真实姓名', role TINYINT NOT NULL DEFAULT 2 COMMENT '1=管理员 2=项目经理 3=普通员工', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) COMMENT '用户表'; CREATE TABLE proj_project ( id BIGINT AUTO_INCREMENT PRIMARY KEY, project_code VARCHAR(50) NOT NULL UNIQUE COMMENT '项目编号', project_name VARCHAR(100) NOT NULL COMMENT '项目名称', manager_id BIGINT NOT NULL COMMENT '项目经理ID,关联sys_user.id', status TINYINT NOT NULL DEFAULT 0 COMMENT '0=立项 1=进行中 2=已暂停 3=已完结 4=已废弃', start_date DATE NOT NULL COMMENT '计划开始日期', end_date DATE NOT NULL COMMENT '计划结束日期', description TEXT COMMENT '项目描述', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) COMMENT '项目台账表'; CREATE TABLE proj_progress ( id BIGINT AUTO_INCREMENT PRIMARY KEY, project_id BIGINT NOT NULL COMMENT '关联proj_project.id', progress TINYINT NOT NULL COMMENT '整体进度百分比,0-100', content VARCHAR(500) NOT NULL COMMENT '本次进度说明', report_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '上报时间', report_user BIGINT NOT NULL COMMENT '上报人ID' ) COMMENT '项目进度记录表'; CREATE TABLE proj_warning ( id BIGINT AUTO_INCREMENT PRIMARY KEY, project_id BIGINT NOT NULL COMMENT '关联proj_project.id', warning_type TINYINT NOT NULL COMMENT '1=超期未上报 2=进度严重滞后 3=项目即将到期', content VARCHAR(500) NOT NULL COMMENT '预警内容', status TINYINT NOT NULL DEFAULT 0 COMMENT '0=未处理 1=已处理', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) COMMENT '预警信息表';字段类型的选择上,有几个地方值得较真:状态字段我统一用TINYINT而不是VARCHAR存字符串,因为数字作为常量枚举,程序里好判断,数据库索引也更快;进度百分比用TINYINT足够表达0到100,用INT就是浪费;金额和信息类字段才考虑DECIMAL和TEXT。ID统一用BIGINT AUTO_INCREMENT,理由是毕设场景没有海量并发,自增主键简单可靠,面试时也不会被追问分布式ID方案。唯一键加在项目编号上,因为监管系统里项目编号是业务上的自然键,不允许重复。
3.3 用MyBatis-Plus让实体类反向生成建表SQL
DDL写完之后,后端对应的实体类可以直接照抄字段结构。这里有一个让新手省事到想哭的技巧:MyBatis-Plus支持在实体类上配置@TableName和@TableField,然后在测试类里直接调用BaseMapper来操作表,甚至可以配置ddl-auto让框架自动建表。热搜词里那个「mybatisplus根据java实体类生成创建表的sql语句」指的就是这个能力——你只需要写实体类,框架根据注解帮你把CREATE TABLE拼出来。
@Data @TableName("proj_project") public class ProjProject { @TableId(type = IdType.AUTO) private Long id; @TableField("project_code") private String projectCode; @TableField("project_name") private String projectName; @TableField("manager_id") private Long managerId; @TableField("status") private Integer status; @TableField("start_date") private LocalDate startDate; @TableField("end_date") private LocalDate endDate; @TableField("description") private String description; @TableField("create_time") private ZonedDateTime createTime; }这里有个参数细节:@TableId(type = IdType.AUTO)对应DDL里的AUTO_INCREMENT,如果你数据库用的是雪花ID或自定义ID生成,就得删掉这行或改成INPUT。LocalDate映射DATE类型是天然正确的,比用java.util.Date更安全——Date同时包含时分秒,和DATE列对接时会出现日期偏移问题。实体类写完后,再写一个继承BaseMapper<ProjProject>的接口,单表的CRUD就全都免费送给你了,这也是选MyBatis-Plus的核心理由。
4. 抄作业级别的核心代码:登录、台账、进度上报、超期预警一条链
4.1 登录与权限:一个拦截器守住所有后台接口
监管系统的安全问题不需要做得像银行一样复杂,但至少要保证「没登录的人进不来后台」。我在项目里用一个AuthInterceptor完成所有接口的守卫,原则是:除了登录接口和静态资源,所有/api/**请求必须携带有效Session。
@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(false); SysUser user = session == null ? null : (SysUser) session.getAttribute("loginUser"); if (user == null) { response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"message\":\"未登录或登录已过期\"}"); return false; } return true; } }然后再加一个配置类把它注册到拦截器链里:
@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private AuthInterceptor authInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/login", "/api/logout"); } }这段代码有两点值得说:request.getSession(false)里的false很关键,它的意思是「如果Session不存在就直接返回null,不要顺手创建一个」,这样能避免未登录请求也产生一堆僵尸Session堆积在内存里;第二个是excludePathPatterns用逗号把登录和退出登录接口排除了,如果你后面加了验证码接口,记得在这里同步放行。登录接口本身则要写一个LoginController,验证用户名密码后执行session.setAttribute("loginUser", user),这个Session在默认配置下能存活30分钟。
4.2 项目台账分页查询:LambdaQueryWrapper的用法和参数
台账列表是整个系统的门面,也是答辩演示时第一个要展示的功能。它必须具备分页、关键字搜索、状态筛选三个能力。
@RestController @RequestMapping("/api/project") public class ProjectController { @Resource private ProjectMapper projectMapper; @GetMapping("/page") public Result page(@RequestParam(defaultValue = "1") long page, @RequestParam(defaultValue = "10") long size, @RequestParam(required = false) String keyword, @RequestParam(required = false) Integer status) { LambdaQueryWrapper<ProjProject> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(keyword), ProjProject::getProjectName, keyword) .eq(status != null, ProjProject::getStatus, status) .orderByDesc(ProjProject::getCreateTime); Page<ProjProject> result = projectMapper.selectPage(new Page<>(page, size), wrapper); return Result.ok(result); } }LambdaQueryWrapper的优势是类型安全,字段名写错了编译期直接报错,而不是运行期才爆SQL异常。like(StringUtils.hasText(keyword), ...)这种写法是MyBatis-Plus官方推荐的「条件构造」套路:第一个布尔参数为false时,这个条件自动被忽略,省去了一堆if (keyword != null)判断。Page<>(page, size)会返回一个带records、total、pages的分页对象,前端直接就能渲染表格。参数默认值是1和10,前端分页组件会把pageNum和pageSize传过来,注意别把两者对应错了。
但这里有一个新手必踩的坑:如果你只加了mybatis-plus-boot-starter依赖而没有注册分页插件,selectPage返回的records里是全部数据,分页完全失效。必须单独配置一个拦截器:
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }PaginationInnerInterceptor是MyBatis-Plus的分页优化器,DbType.MYSQL指定数据库方言,这样生成的limit语句才是MySQL语法。忘了这步的人会在页面上看到数据全跑出来了,但总数和页码都不对,排查半天也找不到原因。
4.3 进度上报与状态流转:从「进行中」到「已审批」
进度上报是监管系统里业务规则最密集的接口。我把它拆成两步:普通员工上报进度,项目经理审核确认。上报时系统要做三件事——校验该项目是否存在、计算当前最新进度、自动判断是否触发预警。
@PostMapping("/progress/report") public Result reportProgress(@RequestBody ProgressReportDTO dto, HttpSession session) { SysUser user = (SysUser) session.getAttribute("loginUser"); // 1. 校验项目存在 ProjProject project = projectMapper.selectById(dto.getProjectId()); if (project == null) { return Result.error("项目不存在"); } // 2. 新增进度记录 ProjProgress progress = new ProjProgress(); progress.setProjectId(dto.getProjectId()); progress.setProgress(dto.getProgress()); progress.setContent(dto.getContent()); progress.setReportTime(LocalDateTime.now()); progress.setReportUser(user.getId()); progressMapper.insert(progress); // 3. 如果项目还是立项状态,上报意味着启动推进 if (project.getStatus() == 0) { project.setStatus(1); projectMapper.updateById(project); } return Result.ok("上报成功"); }这里有一个关键业务规则藏在第3步:项目状态从「立项」到「进行中」的转换,不是由管理员手动改,而是由第一次进度上报自动触发的。这种「状态跟着事件走」的设计,比单独做一个下拉框让用户改状态要合理得多,因为它留下了完整的操作轨迹。
在ProgressReportDTO里,progress字段我要强调一下:它是0到100之间的整数,但你不应该信任前端传来的任何值。在Service层或Controller层做一个校验,if (dto.getProgress() < 0 || dto.getProgress() > 100)直接拒绝。很多人以为这不算坑,直到答辩时老师手动往接口里塞了一个progress=999,当场翻车。
4.4 超期预警:@Scheduled定时任务与不被写死的阈值
预警模块是监管系统区别于普通管理系统的价值点,同时也是最难做出真实感的地方。我用的方案是Spring自带的@Scheduled注解,每天早上8点扫描一次数据库,找出那些「计划完成日期已过但状态还不是完结」的项目,然后生成预警记录。
@Component public class WarningTask { @Resource private ProjectMapper projectMapper; @Resource private WarningMapper warningMapper; @Scheduled(cron = "0 0 8 * * ?") public void checkOverdueProjects() { LocalDate today = LocalDate.now(); // 找出所有未完结且已超期的项目 LambdaQueryWrapper<ProjProject> wrapper = new LambdaQueryWrapper<>(); wrapper.lt(ProjProject::getEndDate, today) .notIn(ProjProject::getStatus, 3, 4); // 排除已完结、已废弃 List<ProjProject> overdueList = projectMapper.selectList(wrapper); for (ProjProject project : overdueList) { ProjWarning warning = new ProjWarning(); warning.setProjectId(project.getId()); warning.setWarningType(THREE); warning.setContent("项目[" + project.getProjectName() + "]已超期"); warning.setStatus(0); warningMapper.insert(warning); } } }这条cron表达式是「每天8点0分0秒触发」的标准写法,*表示任意值,?表示不指定具体值。很多新手把?和*混用后,发现任务要么不执行,要么一天执行无数次。另外在主启动类上别忘了加@EnableScheduling,这是启停开关,只有@Scheduled注解但没加启停,任务会静默失效,连日志都不报错——这类问题我后面在避坑章节会细讲。
5. 避坑手册:毕设项目监管系统里我替你踩过的六个雷
5.1 JDK版本不对,启动直接翻车
现象:Spring Boot项目启动时报错,提示UnsupportedClassVersionError,或者Tomcat启动到一半就退出。原因:你安装的是JDK 17,但项目用的Spring Boot 2.7.x是为JDK 8和11准备的,字节码版本不匹配。解决:装一个JDK 8或11,在IDE的项目设置和pom.xml里都把java.version指定为1.8,环境变量JAVA_HOME也要指到新装的版本。但新版JDK 17可以运行Spring Boot 3.x,如果项目是Boot 3就再用JDK 17。
5.2 数据库中文乱码:根因在连接参数不在代码
现象:页面上显示的中文正常,但存入MySQL后变成???。很多人第一反应是去改代码里response.setCharacterEncoding,其实大概率是JDBC连接串缺了characterEncoding=utf8mb4。我排查过不下五次这种问题,规律几乎一样:数据库表是utf8mb4、代码文件是UTF-8,唯独application.yml里jdbc:mysql://...没带编码参数。解决:在url后面拼上&characterEncoding=utf8mb4,同时确认MySQL的character_set_server是utf8mb4,用SHOW VARIABLES LIKE 'character%'查看。
5.3 MyBatis-Plus分页不生效:九成的人没配拦截器
现象:selectPage返回的total始终是总记录数,前端点击第2页返回的数据还是第一页的内容。原因:MyBatis-Plus分页必须依赖PaginationInnerInterceptor,这个插件默认不会自动注册。解决:在第4.2节里的MybatisPlusConfig配置类中加上拦截器Bean。配置完成后,控制台SQL日志里会多出LIMIT语句,如果还看到全量查询,说明拦截器没生效,检查配置类是否被Spring扫描到。
5.4 前后端分离后Session丢失:跨域配置踩坑
现象:前端用Vue项目单独起在8080端口,后端Spring Boot起在8081,登录接口调用成功后,再调用其他接口一律401。原因:浏览器同源策略把前后端当成两个源,Session Cookie被我方后端返回后被浏览器拦截,第二次请求根本没带上JSESSIONID。解决:在WebConfig里加上跨域配置,允许前端域名携带凭证,同时注意allowCredentials必须为true,且不能同时使用"*"作为允许来源,得写明具体路径。
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true) .maxAge(3600); }这段代码里有几个细节:allowCredentials(true)对应前端axios配置里要加上withCredentials: true,两端缺一不可;maxAge(3600)表示预检请求的结果缓存一小时,不然每次请求都会多发一次OPTIONS请求,响应速度会明显变慢。
5.5 定时任务静默失效:@EnableScheduling 这行字
现象:代码逻辑里写了@Scheduled(cron = "0 0 8 * * ?"),页面和数据都在,但预警记录从来不产生。原因:@Scheduled只是一个标记,Spring Boot默认不会开启对它的扫描,必须得在启动类上加上@EnableScheduling。解决:打开你的主类,确认是否带有@SpringBootApplication和@EnableScheduling两个注解。这是一个典型的「黑匣子」问题,因为代码不报错、日志也不提示,很容易让人去怀疑定时器语法写错了。
5.6 演示数据不足,答辩现场没东西可讲
现象:答辩时打开系统,台账页面上只有三五条记录,点一页就刷到底,想演示「超期预警」却找不到一个超期项目。原因:平时开发用的是自己乱填的数据,没按照「造成预警」的条件构造演示场景。解决:写一个DataInitializer类实现CommandLineRunner,应用启动时自动插入40条项目数据、200条进度数据、若干预警记录,特意让5个项目状态停留在「进行中」但截止日期已过。这样一来,打开预警页立马能看到效果,不需要手动造数。
6. 论文与答辩PPT怎么和代码互相证明:一个能扛住追问的讲法
6.1 论文结构跟着代码走,别让两套东西各说各话
论文最忌讳的是「需求分析写得天花乱坠,代码里根本没实现」。我见过太多学生论文里写模糊查询、数据可视化、多角色权限,结果系统里只有两张表。你的论文目录应该严格对应系统功能:第3章概要设计就画系统架构图和模块划分图,第4章详细设计把第四章代码里的每个接口逐一写清逻辑,第5章测试就放你在浏览器里真实操作的截图。老师翻阅论文时,会发现每一章都能在系统里找到实物,这是过审的基本盘。
6.2 答辩PPT的五页必讲和一个演示脚本
答辩PPT不用做二十几页,核心讲五页就行:一页讲背景与痛点,引入监管系统解决了什么问题;一页讲技术选型与架构图,把BS架构的优势和Spring Boot的快捷摆出来;一页讲数据库设计,展示四张表ER图;一页讲核心功能演示,对应台账分页、进度上报、预警生成三个界面;一页讲总结与不足。演示顺序我在答辩前建议练三遍:第一遍登录,第二遍从台账点进详情,第三遍特意展示一个超期预警项目。整套演示控制在8分钟左右,留时间给老师提问。
6.3 自己给自己当评委:提前写下老师会问的八个问题
答辩本质上是在考设计决策,不是考代码背诵。老师最可能问的八个问题是:为什么选BS架构、Spring Boot比JSP/Servlet好在哪、MyBatis-Plus和MyBatis有什么区别、如何保证数据一致性、超期预警是怎么触发的、如果并发上报怎么办、项目后期如何扩展、为什么不用Spring Security。这其实就是「java八股文」的变种,你只需要记住核心结论:BS为了免维护,Spring Boot为了简化配置,MyBatis-Plus为了提升开发效率,数据一致性靠事务加状态机,并发上报靠数据库乐观锁。答的时候先给结论再补充细节,比背诵教材原文更有说服力。我的习惯是正式答辩前一晚,对着镜子把这些问题问一遍自己,卡壳的地方就是第二天要补的漏洞。做项目这件事,多踩一次坑,答辩就少一分迟疑,希望帮到你。
本文还有配套的精品资源,点击获取