ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot智慧校园平台实战:从架构设计到部署避坑全记录

SpringBoot智慧校园平台实战:从架构设计到部署避坑全记录 做智慧校园这个方向之前我其实已经带团队接过不少类似的管理系统项目但很多都是“小切口”式的做一个失物招领、做一个课程表、做一个报修单。真正把“综合服务平台”这个概念落地SpringBoot才是我绕不开的选择。这篇不是来科普SpringBoot基础语法的而是把我自己从框架选型、数据库建模、权限设计、功能拆解到最终打包部署、整理文档和答辩材料这一整条链路踩过的坑和最终沉淀下来的方案系统地讲一遍。如果你正在做毕设、或者公司想快速搭一套内部校园服务平台这篇文章应该能让你少走至少两周的弯路。1. 项目整体定位与架构设计思路1.1 先想清楚这个平台到底要解决什么问题很多同学一上来就急着建表、写接口结果做着做着发现模块之间互相打架或者用户角色根本分不清。我一开始定的目标是面向高校师生提供一个统一的线上服务入口把教务、生活、后勤、通知这些碎片化场景聚合到一个平台里。具体来说平台要支撑三类用户学生端查课表、查成绩、报修、预约场地、在线缴费、失物招领、接收通知。教师端课程管理、成绩录入、监考安排、审批学生申请。管理员端用户管理、系统配置、数据统计、内容发布、工单分派。从复杂度来看这本质是一个多角色、多模块、高扩展性的业务系统。选型的时候我考虑过用若依这样的快速开发框架但后来弃了原因很简单若依确实快但代码耦合度高且很多底层逻辑不适合二次深度定制尤其是权限这块毕设或者商业项目里导师或者甲方一旦追问底层细节你很难讲清楚。最终我确定的技术栈是后端SpringBoot 2.7.x MyBatis-Plus Spring Security JWT缓存RedisToken存储、热点数据缓存、Caffeine本地缓存数据库MySQL 8.0InnoDB引擎utf8mb4文件存储本地磁盘 Nginx静态映射前端Vue 3 Element Plus Axios这里前端不是主角但联调逻辑我会讲接口文档Knife4j基于Swagger的增强这套组合的好处很直接SpringBoot提供自动化配置和生态支持MyBatis-Plus解决单表CRUD效率问题Spring Security做安全管控JWT做无状态认证Redis扛住高频读取。整个过程不需要引入太重的东西每一层都可以讲清楚原理。1.2 分层架构与模块边界划分这个项目我没有用微服务没必要。一个高校的综合服务平台初期用户量撑死几千并发单体应用完全够用关键是模块边界要清晰保证后续可以平滑拆分。我采用的是标准的分层架构Controller层只做参数接收、调用Service、返回统一结果集Service层业务逻辑处理事务控制在这里Mapper层基于MyBatis-Plus的BaseMapper复杂查询用XML/注解SQLcommon模块统一返回体、全局异常、常量定义、工具类config模块安全配置、Redis配置、跨域配置、文件上传配置模块划分上我按业务域拆包com.campus.platform ├── common // 通用工具、返回体 ├── config // 配置类 ├── security // JWT过滤器、权限注解 ├── module │ ├── auth // 认证模块 │ ├── user // 用户中心 │ ├── course // 教务课程 │ ├── repair // 报修模块 │ ├── booking // 场地预约 │ ├── payment // 缴费模块 │ ├── notice // 公告通知 │ └── statistics // 数据看板这样分包有一个非常实际的好处每个模块都是独立的小项目写代码的时候不会在几十个Controller里迷路。我曾经接过一个项目所有Controller堆在一个包里每次改需求光找文件就要五分钟那种痛你能懂吧。1.3 为什么认证方案选中JWT而不是Session这是导师和面试官问得最多的问题。Session依赖服务端存储集群环境下就得引入Session共享比如Spring Session Redis麻烦不说还有CSRF防护的问题。智慧校园这个场景用户可能同时用Web端和移动端前端的域名和后端的域名大概率还不一样跨域情况下Session的Cookie处理更头疼。JWT就简单直白多了用户登录成功后服务端生成一个包含用户ID、角色、过期时间的Token前端存起来每次请求放在Authorization头里。服务端不需要存储会话状态天然支持水平扩展。当然JWT不是没有弱点主要就是无法主动失效。解决方式我用了双Token机制AccessToken有效期设置2小时RefreshToken有效期7天AccessToken过期后前端用RefreshToken去刷新既保证了安全又避免了频繁登录带来的糟糕体验。关键配置大家参考一下:jwt: secret: ${JWT_SECRET:your-512bit-secret-key-here} access-token-expire: 7200 # 秒 refresh-token-expire: 604800 # 7天注意JWT的Secret在实际生产环境必须放到环境变量或者配置中心绝不能硬编码在yml里提交到Git仓库。这个坑我踩过某次项目代码泄露别人拿着你的Secret分分钟伪造管理员Token。2. 数据建模与核心模块设计2.1 数据库设计一切都是围绕“人”和“事”展开的综合服务平台说白了就是让数据在正确的人之间流转。我的数据库设计原则只有一条高内聚低耦合凡是能独立维护的数据一律拆表凡是经常连表查询的字段合理冗余。核心表设计如下sys_user用户主表存通用字段用户名、密码、手机号、邮箱、状态身份证、学号这类信息放扩展表sys_role角色表学生、教师、管理员、宿管、财务等sys_user_role用户角色关联表一个用户可以有多个角色比如学生兼助教sys_permission权限表菜单/按钮/接口sys_role_permission角色权限关联表course_info课程表course_schedule课表含学期、周次、节次、教室repair_order报修单booking_record预约记录payment_order缴费订单notice_info公告通知attachment_info附件表统管所有上传文件我见过很多学生项目喜欢用一张大宽表把东西全塞进去表面上看省事实际上后面统计报表、权限隔离、字段扩展全都会痛苦不堪。举个例子报修单如果单独搞一张表状态流转时加字段很容易如果跟预约、缴费混在一张表里光是表结构维护就是一场灾难。2.2 用户认证与权限模型权限模型我选的是经典的RBAC基于角色的访问控制三层用户-角色-权限。这可能是很多人觉得“老土”的方案但在校园平台这种角色固定、权限粒度不需要到字段级的场景里RBAC反而是最稳的。那些花里胡哨的ABAC、PBAC模型没有可视化配置界面支持的话维护成本极高。接口权限的实现方式我用的Spring Security 自定义注解PreAuthorizePreAuthorize(hasRole(ADMIN) or hasAnyAuthority(repair:order:audit)) PostMapping(/audit) public ResultString audit(RequestBody RepairAuditDTO dto) { repairService.audit(dto); return Result.success(); }这里要强调一个细节权限字符串的命名规范。我统一用模块:操作:资源的格式比如repair:order:create、course:score:update。这样的好处是后面做权限配置页面的时候可以按模块自动分组展示管理员配置起来非常直观。如果你随便命名比如update或者coursemanage后面维护权限列表绝对原地爆炸。另外一个非常重要的经验登录时一次性加载权限并放入Redis。千万不要每次请求都查一遍数据库权限表高并发下数据库扛不住的。我在Redis里存的是用户角色和权限字符串的Set集合Key格式为login:user:permission:{userId}过期时间跟AccessToken对齐。2.3 课程与课表模块的时序模型课表是教务模块里最容易做砸的。很多人设计课表表结构就真是“星期几第几节第几周”三个字段组合然后写出来各种Bug比如调课、补课、单双周、节假日顺延一改调度逻辑全乱。我的处理方式是用时间维度拆解学期表维护学期开始日期、结束日期、教学周数课程定义表课程名称、学分、授课教师、课程性质教学计划表关联课程与班级/专业确定这门课在哪个学期开具体排课表学期ID 教学班ID 周次 星期 节次 教室这样设计的好处是调课和补课本质上是对具体排课表做变更不会影响其他维度的数据。前端课表展示时后端按学期参数一次性返回整个学期的所有排课记录前端按周次和节次二维渲染。刚开始我觉得这样会一次查太多数据实测下来一个学期的记录数大概也就几百条根本不算压力。2.4 报修与预约模块的状态机设计报修、预约这类流程型模块一定要把状态机提前设计好。我吃过的亏是报修单一开始只设计了待处理/已处理两个状态结果后来发现还需要派单、维修中、待评价、已关闭、已驳回。改表结构倒不难难的是所有牵扯到状态判断的前后端逻辑全部要跟着改那个工作量谁改谁知道。这次我一开始就定义清楚报修单状态待派单 - 已派单 - 维修中 - 待验收 - 已完成 - 已关闭预约状态待确认 - 已确认 - 已取消 - 已使用 - 已过期状态流转的核心逻辑放在Service层不允许前端直接修改状态字段所有状态变更必须通过对应的业务方法触发。比如assignOrder()、startRepair()、completeOrder()。这样的好处是每个状态节点都可以插入校验逻辑比如派单必须要有维修工ID完成必须上传维修图片。代码示例Transactional(rollbackFor Exception.class) public void assignOrder(Long orderId, Long workerId) { RepairOrder order getById(orderId); if (order.getStatus() ! RepairStatus.PENDING_ASSIGN.getCode()) { throw new BizException(当前状态不可派单); } order.setStatus(RepairStatus.ASSIGNED.getCode()); order.setWorkerId(workerId); updateById(order); // 发送站内信通知维修工人 notifyService.send(workerId, 您有新的报修任务); }这里Transactional必须加因为派单涉及到更新订单、发送通知两个动作要么全成功要么全失败。3. 后端关键功能实现与联调细节3.1 统一返回体与全局异常一个成熟的项目接口返回格式必须是统一的。我的格式很简单{ code: 200, message: success, data: {} }前端Axios拦截器中统一判断code如果等于200就返回data否则弹错误提示。这里有一个细节很容易被忽略关于code的设计。我用的是HTTP状态码之外的业务码200是成功401是未认证403是无权限500是服务端异常业务各模块从1001开始编排。为什么不用HTTP原生的状态码因为HTTP 200在网关层就是成功而业务上可能需要传业务错误给前端前端也不用所有错误都理解为网络层错误。全局异常处理用RestControllerAdvice分成三类BizException业务异常比如“库存不足”“当前状态不可操作”HTTP状态码还是200code为业务码AccessDeniedException权限不足统一返回403Exception兜底记录日志并返回500这个设计看着简单但能保证前端拿到的永远是同一套JSON结构。我之前接过一个项目有的接口返回{message: error}有的返回{msg: xxx}前端联调时只能一个个接口去适配痛苦至极。3.2 多角色数据的权限隔离智慧校园里最难处理的往往不是功能多少而是同一份数据不同角色看到的范围不同。举几个实际场景学生只能看到自己的课表和成绩但辅导员可以看到整个班级的维修工只能看到分派给自己的报修单但后勤管理员能看到全校的教师只能看到自己授课班级的名单系主任可以看全系MyBatis-Plus有一个很好用的功能叫DataPermissionInterceptor或者你也可以自己在SQL层面手动拼接权限条件。我这里的做法更加直白每个Mapper方法的SQL都强制接收一个权限过滤上下文。比如查询报修单列表public IPageRepairOrderVO selectRepairPage(PageRepairOrder page, Param(query) RepairQuery query, Param(scope) DataScope scope) { // scope中封装了当前用户ID、角色、可见范围 }对应的XML SQL里select idselectRepairPage resultTypecom.campus.platform.module.repair.vo.RepairOrderVO SELECT r.*, u.nick_name AS workerName FROM repair_order r LEFT JOIN sys_user u ON r.worker_id u.id where if testquery.status ! null AND r.status #{query.status} /if if testscope.isAdmin false AND ( r.create_by #{scope.userId} OR r.worker_id #{scope.userId} OR r.dept_id IN (SELECT dept_id FROM sys_user_dept WHERE user_id #{scope.userId}) ) /if /where ORDER BY r.create_time DESC /select这个方案的核心思路是永远不要在Service里先查全部数据再到内存里过滤而是把过滤条件下推到SQL层。一旦数据量上万内存过滤的性能问题就会直接暴露出来。3.3 文件上传与访问的最佳实践校园平台一定会涉及图片上传报修照片、用户头像、失物招领图片、活动海报。我刚开始用的是直接把Base64传给后端结果一个图片几兆的Base64字符串动辄几十MB接口直接卡死。后来规规矩矩做了multipart/form-data上传。这里的关键配置spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB存储路径我建议不要放在项目的src目录下而是放在服务器的一个独立目录比如/data/campus/upload。然后通过配置类映射为静态资源Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }生产环境更合理的方式是Nginx直接映射location /upload/ { alias /data/campus/upload/; expires 30d; }一个核心经验文件名不要用原始文件名。一是中文文件名会有URL编码问题二是同一文件名的重复覆盖问题。我用的是UUID重命名保留原始扩展名同时把原始文件名存数据库。这样磁盘上的文件永远不重复数据库里记录原文件信息用于展示和下载。3.4 防重复提交与接口幂等性这个问题几乎每个真实系统都会遇到但看毕设项目很少见到有人认真处理。场景是这样的学生在缴费页面点了一下“确认支付”因为网络慢了又点了一下结果生成了两笔订单。或者报修表单双击了提交按钮产生了重复工单。我的通用方案是基于Redis的分布式锁做接口幂等public T T executeWithIdempotency(String key, long timeout, SupplierT supplier) { String lockKey idempotent:lock: key; Boolean locked redisTemplate.opsForValue().setIfAbsent(lockKey, 1, Duration.ofSeconds(timeout)); if (Boolean.TRUE.equals(locked)) { try { return supplier.get(); } finally { redisTemplate.delete(lockKey); } } throw new BizException(请勿重复提交); }然后每次需要幂等保护的操作前端在调用时传入一个唯一业务编号后端用这个编号加锁PostMapping(/create) public ResultString createOrder(RequestBody Valid CreateOrderDTO dto) { String idempotencyKey dto.getClientToken(); if (StrUtil.isBlank(idempotencyKey)) { throw new BizException(缺少幂等标识); } return idempotencyService.executeWithIdempotency(idempotencyKey, 10, () - { paymentService.createOrder(dto); return Result.success(); }); }clientToken在前端由UUID生成每次进入页面或者每次点击按钮时重新生成一个一次请求用完就作废。这是我在实际项目里验证过最实用、最不折腾前端的一种方案。3.5 数据看板模块的统计SQL优化数据看板是很多毕设的加分项但也是性能重灾区。我的看板包含全校报修数量趋势、各类型报修占比、预约热门场地Top5、各学院使用活跃度排行。第一版我直接在业务库上写统计SQL结果每次打开看板数据库CPU直接飙高因为很多统计需要全表扫描。后来改成了汇总表的方案建立一个statistics_daily表按天粒度记录每个模块的核心指标每天凌晨用定时任务Spring的Scheduled或者XXL-Job跑前一天的数据汇总看板接口只查询汇总表不碰业务表Component public class StatisticsJob { Scheduled(cron 0 30 1 * * ?) // 每天凌晨1:30执行 public void generateDailyStatistics() { // 统计昨日报修数量按类型分组 // 统计昨日预约数量按场地分组 // 统计昨日新增用户数按角色分组 // 写入 statistics_daily } }这里有个小细节值得说同一时刻不要同时跑多个任务不然数据库压力还是很大。我配置了一个scheduler.lock的Redis分布式锁保证集群部署时同一时刻只有一个实例在跑汇总任务。4. 项目部署上线与文档沉淀4.1 从开发到部署的全流程梳理很多同学的SpringBoot项目在IDEA里跑得飞起一上服务器就各种花式报错。这里我把我推荐的部署流程完整写出来照抄就行。第一步本地打包mvn clean package -DskipTests如果用了多环境配置打包时指定生产环境profilemvn clean package -DskipTests -Pprod第二步服务器环境准备服务器的Java环境版本务必与本地保持一致我用的JDK 1.8阿里云或者腾讯云的CentOS 7/8都自带或者可以方便安装。java -version第三步上传并启动nohup java -jar campus-platform.jar \ --spring.profiles.activeprod \ --server.port8080 \ /data/logs/campus.log 21 这里nohup和是必须的否则SSH断开进程就死了。第四步Nginx反向代理前端打包后的dist目录放到/data/campus/dist然后配置Nginx把/api路径转发到后端8080端口location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }4.2 Docker部署方案推荐如果服务器上装了Docker部署会舒服很多。我的做法是直接一个Dockerfile搞定FROM openjdk:8-jre-alpine WORKDIR /app COPY target/campus-platform.jar app.jar EXPOSE 8080 ENV JAVA_OPTS-Xms256m -Xmx512m -Dfile.encodingUTF-8 ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar app.jar --spring.profiles.activeprod]加上docker-compose编排MySQL、Redis、后端一次性拉起version: 3 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: yourpassword MYSQL_DATABASE: campus_db volumes: - /data/mysql:/var/lib/mysql ports: - 3306:3306 redis: image: redis:6.2 ports: - 6379:6379 app: build: . depends_on: - mysql - redis ports: - 8080:8080 environment: DB_HOST: mysql DB_PASSWORD: yourpassword REDIS_HOST: redis注意Docker容器之间通信不能用localhost要用depends_on里定义的service名。这个坑我第一次用Docker部署时踩了一晚上一直报数据库连接失败。4.3 lw论文和技术文档的写作框架标题里带了“lw”也就是论文。我这里简单说一下怎么结合代码把论文写扎实。一个SpringBoot项目的毕业论文核心框架一般这样搭第一章 绪论研究背景、意义、国内外现状第二章 相关技术介绍SpringBoot、MyBatis-Plus、JWT、Redis等不要写口语要写原理第三章 系统分析可行性分析技术、经济、操作、需求分析功能性非功能性、用例图第四章 系统设计总体架构图、功能模块图、数据库E-R图、数据表设计、类图第五章 系统实现按模块写关键功能的实现过程配核心代码和截图第六章 系统测试功能测试每个模块的测试用例表格、性能测试、兼容性测试论文一定不要只贴代码要把设计的思路写清楚。比如你为什么要用RBAC模型JWT相比Session的优势在哪MySQL的索引在哪个表上建了为什么这些才是答辩老师真正关心的。另外部署文档要写清楚每个步骤尤其写清楚服务器环境要求、数据库初始化的SQL文件怎么导入、配置文件里的哪些参数需要改。不要默认读者什么都会毕设文档的阅读对象是老师不是你团队的人。我的部署文档目录大概是这样的docs/ ├── 00_环境要求.md ├── 01_数据库初始化.md ├── 02_后端部署.md ├── 03_前端部署.md ├── 04_Nginx配置.md ├── 05_常见问题.md4.4 答辩讲解时的核心逻辑如果你需要给老师或者面试官讲解这个项目我建议你不要按模块一个个讲那样太散讲完别人也抓不住重点。要按一条主线讲下来用户登录 - 携带JWT访问接口 - 后端用Spring Security校验权限 - 业务层处理 - 返回统一格式。主线中间穿插三个亮点权限管理的精细度RBAC 数据权限下推SQL不同角色看到不同范围的数据幂等性设计防止重复缴费和重复报修缓存设计Redis存储Token和热点数据数据库压力大幅降低这个逻辑讲完整个项目的架构、业务流程、技术深度基本就全覆盖了。5. 常见问题排查与避坑实录5.1 我实际遇到的高频问题做这个项目时我自己记录了一个问题排查表这里贴出来给大家参考。现象可能原因解决方式启动报Failed to configure a DataSource没有配置数据源或注解扫描路径错了检查application.yml或检查启动类SpringBootApplication扫描范围请求接口报401Token缺失/过期或Security放行配置不对检查前端是否携带Authorization头检查SecurityConfig中放行的白名单跨域请求被拦截未配置Cors跨域加CrossOrigin或全局CorsFilter注意allowedOriginPatterns别用*新版Spring Security会拒绝文件上传报MaxUploadSizeExceededException上传大小超过限制检查spring.servlet.multipart.max-file-size配置定时任务不执行忘了加EnableScheduling启动类或配置类加注解数据库连接被拒服务器防火墙没开端口或MySQL bind-address设置不对firewall-cmd --add-port3306/tcp检查my.cnf中bind-address是否为0.0.0.0Redis连接失败没设密码或开启保护模式设置requirepass连接时带密码生产环境改用专用Redis实例除了表格里的问题还有一个我每次都会强调的数据库连接字符串一定要加时区和SSL参数否则高版本MySQL驱动直接拒绝连接。url: jdbc:mysql://localhost:3306/campus_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai5.2 那些看不见但会坑你的细节第一个坑是Spring Security的放行配置。登录接口本身是需要放行的但是接口文档Knife4j也需要放行否则前端连文档都看不了。另外就是/upload/**这些静态资源路径必须放行不然图片全部403。我的SecurityConfig中放行列表是这样.antMatchers( /auth/login, /auth/refresh, /doc.html, /webjars/**, /v3/api-docs/**, /swagger-resources/**, /upload/** ).permitAll()第二个坑是MyBatis-Plus的逻辑删除。我用的是全局逻辑删除配置字段叫deleted配置完成后所有deleteById操作都会自动变成UPDATE ... SET deleted1查询也会自动带上deleted0条件。但要注意唯一索引和逻辑删除会产生冲突。比如用户名有唯一索引用户删除后记录还在重建同名用户就插不进去。解决方案是让deleted字段参与联合唯一索引或者物理删除。第三个坑是Jackson的时间格式化。默认情况下SpringBoot返回的Date是时间戳格式前端不好解析。统一配置一下Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); }; } }不配置的话前端显示的“2025-03-12”就会变成“Mar 12, 2025 10:00:00 AM”极其难看。5.3 性能调优的几点经验这个项目我做了压测简单调优之后接口吞吐量提升了不少。经验不多但都很管用。第一数据库连接池。默认的HikariCP参数可以微调spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000连接池大小不是越大越好公式大致是(核心数*2 磁盘数)搞太大反而增加开销。第二Redis缓存热点数据。我做了两个级别的缓存Caffeine本地缓存一级 Redis分布式缓存二级。读取流程是先查本地没有再查Redis还没有就查数据库并回填。这种设计能扛住非常大的读并发而且代码也不复杂public CourseVO getCourseDetail(Long id) { // 一级缓存 CourseVO course caffeineCache.getIfPresent(course: id); if (course ! null) return course; // 二级缓存 Object obj redisTemplate.opsForValue().get(course: id); if (obj ! null) { course JSONUtil.toBean(obj.toString(), CourseVO.class); caffeineCache.put(course: id, course); return course; } // 数据库 course courseMapper.selectDetailById(id); redisTemplate.opsForValue().set(course: id, JSONUtil.toJsonStr(course), Duration.ofMinutes(30)); caffeineCache.put(course: id, course); return course; }这里有个取舍课程信息这种变化不频繁的数据适合缓存但报修单状态这种实时性要求高的数据不要走缓存否则用户提交了报修列表里一直看不到体验极差。第三SQL层面的慢查询优化。我给所有常用查询字段都加了索引比如repair_order.create_by、repair_order.status、payment_order.user_id、course_schedule.term_id等。然后用EXPLAIN关键字检查执行计划确保没有全表扫描。EXPLAIN SELECT * FROM repair_order WHERE create_by 10001 AND status 1;如果发现type是ALL而不是ref或者range就说明索引建少了或者条件写法有问题需要调整。写在最后的一点体会整个项目从头到尾做下来我最大的感受是一个看起来“普通”的智慧校园系统真正把它做扎实需要关注的细节实在太多了。从技术选型时的克制不盲目上微服务到数据库建模时面向业务的思考状态机、权限下推、汇总表再到部署时对环境和配置的敬畏每一环都藏着大量经验积累。我个人觉得最有价值的一个决定就是在设计阶段把各模块的状态机和权限边界都画清楚了才使得后面两个月开发基本没有返工。如果你也要做类似的项目我的建议是先花一周时间把架构设计文档写好把每个模块的边界、状态流转、权限控制都写清楚再动手写代码。真的写代码永远不是最耗时的最耗时的永远是“想清楚再做”。最后分享一个跟部署文档相关的小技巧你可以在项目里加一个docs/sql/manual_test.sql文件把核心模块的测试SQL全部写进去。这样不管是答辩演示还是交给别人部署都能快速验证系统功能是否正常省去在界面上一个个点按钮的时间。这也是我在多次交付中摸索出的、非常实用的习惯。
返回列表