ARTICLE DETAIL

资讯详情

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

Java会议管理系统源码实战:Spring Boot+MySQL实现预约与权限管理

Java会议管理系统源码实战:Spring Boot+MySQL实现预约与权限管理 简介一份基于Java与MySQL8.0的会议管理系统完整源码面向需要掌握Java桌面应用开发的初学者与中级工程师。系统涵盖会议创建/修改/删除、参会者管理、日程安排、权限控制等模块采用JDK12与Swing/JFX图形界面后台接入MySQL并借助Maven管理依赖能够帮助读者从头搭建一套可运行的桌面管理软件。资源包共41个文件以25个Java源文件为主另有11张PNG界面与结构图、2个XML配置、1个SQL初始化脚本以及readme说明文档压缩后仅286KB目录结构清晰便于按模块阅读代码。该资源已有338人学习适合用于课程设计、毕业设计或Java进阶练习。借助meeting-master-mvn的主目录布局可直观看到Maven项目的标准结构结合ER图、SQL脚本及界面截图能快速还原数据库表设计与业务交互。代码中保留了对JDBC、MVC模式及Swing事件驱动的具体实现思路对希望深入理解Java GUI开发流程的读者来说是一份值得参考的实战样例。1. Java会议管理系统源码一份能当课程设计基座也能改造成内网工具的业务骨架如果你在搜Java会议管理系统源码大概率是两种情况要么是计算机专业的课程设计或毕业设计选题需要一个功能完整、能讲清楚代码逻辑的参考项目要么是刚入职不久被安排给团队内部做一个会议预定的小工具不想从零开始写登录、权限、预约这些重复功能。无论哪种你要找的都不是一个花哨的Demo而是一套能跑通、能答辩、能改的业务骨架——会议管理系统的核心价值在于预约冲突检测、参会人管理和状态流转这些逻辑才是面试和答辩时真正能讲出东西的地方。这篇文章就按我平时做这类项目的习惯从表结构设计、环境搭建、核心代码实现到踩坑记录完整拆一遍。2. 先定业务边界再拆模块会议管理系统的4个核心板块2.1 需求边界先定死只做会议室预订和会议管理别一上来就加审批流很多人在动手写会议管理系统之前习惯性地把需求想得很大要加部门审批、要加设备管理、要加会议纪要流转、要做移动端。如果你是为了完成课程设计这是致命的——功能越多代码越散答辩时每个模块都讲不深。会议管理系统这四个字最常见的、最稳妥的业务边界是四个用户登录与权限、会议室管理、会议预约与冲突检测、参会人管理。做到这四块系统已经是一个完整可演示的产品再加邮件通知算加分项。我一般建议的表结构是6张起步用户表、会议室表、会议表、参会人关联表、通知记录表再加一张字典表存会议状态之类的枚举值。不需要工作流引擎不需要消息队列一个Spring Boot项目加MySQL就能撑住。如果后续想扩展审批流在会议表里加一个approve_status字段就够了这比一开始就做复杂的流程设计要务实得多。这个取舍在课程设计答辩里反而是加分项——说明你清楚系统的核心难点在哪里。2.2 数据库设计6张表就能撑起一套可用的会议管理系统直接给出一份可以照着建库的DDL。这是最常见的MySQL 5.7/8.0写法字符集用utf8mb4引擎用InnoDB。CREATE DATABASE IF NOT EXISTS meeting_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE meeting_system; -- 用户表 CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, real_name VARCHAR(50) NOT NULL COMMENT 姓名, role TINYINT NOT NULL DEFAULT 1 COMMENT 1-普通用户 2-管理员, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-启用 0-禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT 用户表; -- 会议室表 CREATE TABLE meeting_room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_name VARCHAR(100) NOT NULL COMMENT 会议室名称, location VARCHAR(200) COMMENT 位置描述, capacity INT NOT NULL DEFAULT 10 COMMENT 容纳人数, equipment VARCHAR(500) COMMENT 设备清单逗号分隔, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-可用 0-维护中 ) ENGINEInnoDB COMMENT 会议室表; -- 会议表 CREATE TABLE meeting ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_id BIGINT NOT NULL COMMENT 会议室ID, subject VARCHAR(200) NOT NULL COMMENT 会议主题, organizer_id BIGINT NOT NULL COMMENT 发起人ID, start_time DATETIME NOT NULL COMMENT 开始时间, end_time DATETIME NOT NULL COMMENT 结束时间, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-待召开 1-进行中 2-已结束 3-已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_room_time (room_id, start_time, end_time), KEY idx_organizer (organizer_id) ) ENGINEInnoDB COMMENT 会议表; -- 参会人关联表 CREATE TABLE meeting_participant ( id BIGINT PRIMARY KEY AUTO_INCREMENT, meeting_id BIGINT NOT NULL, user_id BIGINT NOT NULL, attend_status TINYINT NOT NULL DEFAULT 0 COMMENT 0-未确认 1-已确认 2-已拒绝, UNIQUE KEY uk_meeting_user (meeting_id, user_id) ) ENGINEInnoDB COMMENT 参会人表; -- 通知记录表 CREATE TABLE notify_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, meeting_id BIGINT NOT NULL, user_id BIGINT NOT NULL, channel VARCHAR(20) NOT NULL COMMENT email/sms, content VARCHAR(1000), status TINYINT NOT NULL DEFAULT 0 COMMENT 0-待发送 1-成功 2-失败, retry_count INT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT 通知记录表;这份DDL有几个值得说一说的设计决策。meeting表里的联合索引idx_room_time(room_id, start_time, end_time)是冲突检测的命脉——后面查这个会议室这个时间段有没有被占用时这个索引能让查询走索引而不是全表扫描。meeting_participant表的联合唯一键uk_meeting_user保证了同一场会议不会重复添加同一个参会人这是数据库层的兜底约束。再说说字段类型的选择。时间字段用DATETIME而不是TIMESTAMP因为TIMESTAMP在2038年会溢出而且受时区影响排查问题的时候容易踩坑。状态字段用TINYINT加注释而不是用VARCHAR存待召开已结束这种中文——数据库里存数字Java代码里用枚举或常量类做映射这是最常见的做法原因很简单改状态名只改代码不用改数据库而且比较大小和索引效率都更好。2.3 状态机设计会议状态流转比想象中更容易被忽略会议状态字段status看似简单实际是系统里最容易出逻辑漏洞的地方。我见到过不少课设代码状态字段定义了却只在列表页显示用没有真正的状态流转逻辑——这等于白设计。常见的状态流转是创建会议后状态为待召开到了start_time系统自动或手动把状态改成进行中end_time过后改成已结束创建人可以取消未开始的会议状态变为已取消。这套流转不需要写复杂的状态机引擎用几个Service方法就能搞定但你要给每个方法定义清楚谁可以调、什么条件下可以调。我的做法是在MeetingService里写三个显式方法cancelMeeting取消会议、startMeeting开始会议、finishMeeting结束会议每个方法第一行先校验当前状态。比如取消会议的代码如下public void cancelMeeting(Long meetingId, Long operatorId) { Meeting meeting meetingMapper.selectById(meetingId); if (meeting null) { throw new BusinessException(会议不存在); } // 状态校验只有待召开的会议才能取消 if (meeting.getStatus() ! MeetingStatus.WAITING) { throw new BusinessException(当前状态不允许取消); } // 权限校验发起人或管理员才能取消 if (!meeting.getOrganizerId().equals(operatorId) userService.isAdmin(operatorId) false) { throw new BusinessException(无权取消该会议); } meeting.setStatus(MeetingStatus.CANCELED); meetingMapper.updateById(meeting); }注意这段代码里的两个校验状态校验和权限校验缺一不可。很多课程设计里只做了状态校验结果普通用户也能取消别人的会议这在答辩时被问到的概率极高。把这两个校验写在Service层而不是让前端控制按钮显隐是因为接口是可以被直接调用的——前端的隐藏只是体验后端的校验才是安全。3. 环境搭建与项目骨架Spring Boot MyBatis 的最小可运行配置3.1 技术栈选型为什么不是 Spring Cloud也不是纯 JDBC会议管理系统这个体量最合适的组合就是Spring Boot MyBatis MySQL这也是目前Java课程设计案例源码里最常见的技术栈。Spring Boot负责自动配置和Web层MyBatis负责数据访问前端用一套简单的Bootstrap或Vue页面就够。不需要引入Spring Cloud那套微服务组件——单机应用硬拆微服务只会让答辩变成灾难现场也不需要只用JDBC裸写——那又走回了10年前的Servlet时代数据访问代码会膨胀到没法维护。创建项目的方式很简单用Spring Initializr生成一个基础工程然后手动把依赖补全。核心依赖就是Web、MyBatis、MySQL驱动、Lombok这四个如果你的系统要做邮件通知再加一个Spring Mail。所谓架构在这个体量下就是包结构设计——controller、service、mapper、entity、common五层各司其职不要搞花活。3.2 配置文件三个必调参数时区、驼峰映射、SQL日志Spring Boot的application.yml是整个项目最先要正确的地方。给你一份我常用的最小配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/meeting_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password hikari: maximum-pool-size: 10 minimum-idle: 2 mail: host: smtp.example.com port: 465 username: noreplyexample.com password: your_password properties: mail: smtp: auth: true ssl: enable: true mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.meeting.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl logging: level: com.example.meeting.mapper: debug三个必调的参数单独说明一下。第一个是JDBC URL里的serverTimezoneAsia/ShanghaiMySQL 8.0以上版本不配这个参数驱动会报The server time zone valueй is unrecognized这是新手最常见的启动报错之一。第二个是map-underscore-to-camel-case: true它把数据库的real_name自动映射到Java实体的realName没有这个配置你就要在XML里给每个字段写resultMap那是纯粹的体力活。第三个是StdOutImpl这个SQL日志实现它能让你在控制台直接看到每条SQL的入参和返回结果排查问题时比断点调试高效得多。如果用了Lombok的Data注解实体类不需要写Getter/Setter。但要注意Lombok需要IDE安装插件而且版本要和编译环境匹配——这个坑我在避坑章节里详细展开。3.3 从启动到自测能不能说清楚请求是怎么走通的项目搭好之后我习惯先做一个最小的自测闭环创建一个TestController写一个接口返回当前登录用户的名称然后直接用浏览器访问。这能最快确认Spring容器、MyBatis、数据库三层都通了而不是浪费一整天时间写一堆代码后一次性翻车。自测接口的做法很直接。新建一个SysUserMapper写一个按用户名查用户的SQL然后通过SysUserService暴露一个查询方法最后在Controller里用一个简单的GET接口返回。注意这一步只是为了验证通路不要纠结业务逻辑能把一条数据从MySQL取出来显示在页面上环境就算通了。验证的标准是看两个东西控制台有没有打印出SQL语句以及浏览器里能不能看到JSON数据。看到SQL说明MyBatis的映射打通了看到JSON说明Web层和数据层之间的链路没有断。这一步跑通后后面写什么都顺。4. 会议预约与冲突检测核心业务逻辑的落地和五种边界情况4.1 预约会议的入参校验时间参数至少要做四个检查会议预约是系统的核心操作入参校验做得严格后面的业务逻辑才轻松。我见过太多代码把校验散落在各种if语句里这里补一个那里补一个后来自己都搞不清哪些参数是安全的。正确的做法是在Controller层或Service层入口统一做参数校验用一个方法来把关。public void createMeeting(MeetingCreateRequest request) { // 1. 基础字段非空校验 if (request.getRoomId() null || request.getSubject() null || request.getStartTime() null || request.getEndTime() null) { throw new BusinessException(必填参数不能为空); } // 2. 时间合法性开始时间必须在结束时间之前 if (!request.getStartTime().isBefore(request.getEndTime())) { throw new BusinessException(开始时间必须早于结束时间); } // 3. 时间合理性会议时长不能超过8小时防止误输入 if (Duration.between(request.getStartTime(), request.getEndTime()).toHours() 8) { throw new BusinessException(单次会议时长不能超过8小时); } // 4. 时间有效性开始时间必须在当前时间之后 if (request.getStartTime().isBefore(LocalDateTime.now())) { throw new BusinessException(不能预约过去的时间); } // 5. 会议室是否存在且可用 MeetingRoom room meetingRoomMapper.selectById(request.getRoomId()); if (room null || room.getStatus() ! 1) { throw new BusinessException(会议室不存在或不可用); } // 6. 核心冲突检测 checkRoomAvailable(request.getRoomId(), request.getStartTime(), request.getEndTime()); // 7. 插入会议 插入参会人同一事务 ... }这段代码最想强调的检查有四个开始时间必须在结束时间之前、会议时长上限、不能预约过去时间、冲突检测。前三个是数据合法性第四个是业务核心。很多系统只做了冲突检测忽略了前三个结果用户提交一个结束时间早于开始时间的反向会议冲突检测反而查不出来数据就这么脏了。顺带说一句LocalDateTime是JDK 8时间API永远不要用Date和SimpleDateFormat去做这些比较——Date的before和after方法语意不直观SimpleDateFormat的线程安全问题更是一颗定时炸弹。用LocalDateTime的isBefore和isAfter代码可读性高一个档次。这个细节在java面试题里也经常被问说清楚比背八股要强得多。4.2 冲突检测的两种实现SQL硬查与Java内存判断冲突检测是整个会议管理系统最核心的一段逻辑。判断规则不复杂两个会议冲突当且仅当它们在同一个会议室且一个的开始时间小于另一个的结束时间、结束时间大于另一个的开始时间。写成代码是newStart oldEnd newEnd oldStart。第一种做法是直接在MySQL里硬查查出来有记录就抛异常SELECT COUNT(*) FROM meeting WHERE room_id #{roomId} AND status ! 3 -- 排除已取消的会议 AND start_time #{endTime} AND end_time #{startTime}这条SQL就是标准的区间重叠判断配合meeting表上的idx_room_time联合索引性能没有问题。注意排除掉status 3已取消的会议否则一个被取消的时段仍然会阻塞新预约这属于业务逻辑层面的严谨性。第二种做法是把该会议室当天所有会议查出来在Java内存里用循环判断。这种做法适合会议室全天会议总数很少、但查询条件复杂的场景——比如你要同时判断参会人有没有空闲区间SQL越写越复杂的时候内存判断的代码反而更清晰。但要注意内存判断必须处理并发问题两个用户同时提交预约都查出没有冲突然后都插入成功。这就是典型的并发翻车现场。解决并发问题有三个层次最不靠谱的是靠synchronized锁住Service方法——在单实例部署时有效但锁的粒度太粗整个会议室都被锁住了性能很差做得好一点的是用数据库的SELECT ... FOR UPDATE把冲突检测的查询加排他锁锁住会议室记录让并发请求排队最彻底的是在meeting表加一个唯一约束的冗余字段来兜底。对于课程设计这个体量用FOR UPDATE就足够了代码后面会讲到。4.3 参会人关联与事务边界一条龙操作不能做一半创建会议时除了插入meeting表还要批量插入meeting_participant表。这两步必须在一个事务里否则就会出现会议建好了但参会人是空的这种脏数据。事务边界划在哪我的建议是创建会议、批量加参会人、写通知日志这三件事放一个Transactional方法里。Transactional(rollbackFor Exception.class) public void createMeetingWithParticipants(MeetingCreateRequest request, ListLong userIds) { Meeting meeting new Meeting(); meeting.setRoomId(request.getRoomId()); meeting.setSubject(request.getSubject()); meeting.setOrganizerId(request.getOrganizerId()); meeting.setStartTime(request.getStartTime()); meeting.setEndTime(request.getEndTime()); meeting.setStatus(MeetingStatus.WAITING); meetingMapper.insert(meeting); // 批量插入参会人 for (Long userId : userIds) { MeetingParticipant participant new MeetingParticipant(); participant.setMeetingId(meeting.getId()); participant.setUserId(userId); participant.setAttendStatus(AttendStatus.UNCONFIRMED); meetingParticipantMapper.insert(participant); } // 发送邮件通知 notifyService.sendMeetingNotification(meeting, userIds); }Transactional(rollbackFor Exception.class)里这个rollbackFor值得单独说。Spring的事务默认只在遇到RuntimeException时回滚Exception的子类默认不回滚。如果你在Service里抛了一个自定义的BusinessException而它继承的是Exception而不是RuntimeException事务就不会回滚——这是Java开发里一个极经典的坑。所以要么让你的BusinessException继承RuntimeException要么像上面这样显式声明rollbackFor Exception.class。两个方案都行但必须在项目里保持一致。邮件通知的方法在同一个事务里执行意味着如果邮件服务超时整个创建会议的流程都会回滚。我倾向于在这个方法内部捕获邮件异常打日志不让它影响主流程——参会人没收到邮件可以重发但会议数据不能丢。这个取舍要做好注释免得后人误改。5. Java会议管理系统开发避坑5个真实踩坑记录5.1 MySQL 8驱动的时区报错明明代码没问题启动却直接翻车现象Spring Boot项目启动正常第一次访问查询接口就报The server time zone value й is unrecognized后面还跟着一堆Communications link failure。看着像个玄学问题其实是MySQL驱动版本和连接参数不匹配。原因MySQL 8.0的驱动com.mysql.cj.jdbc.Driver强制要求JDBC URL里带serverTimezone参数。旧版本的com.mysql.jdbc.Driver没有这个要求所以网上很多旧教程的写法直接拷过来就报错。这也是为什么配置文件里那个serverTimezoneAsia/Shanghai这么重要。顺带说这个报错里的乱码是GBK编码的中文中国被错误显示不是数据库内容乱码。解决在JDBC URL末尾加上?serverTimezoneAsia/Shanghai完整写法参见3.2节的配置文件。另外把driver-class-name改成com.mysql.cj.jdbc.Driver这是官方推荐的驱动类名。5.2 MyBatis映射器扫描不到接口有注解XML却死活不生效现象Mapper接口上加了Mapper注解控制台里能看到Mapper bean被创建但调用方法时报Invalid bound statement (not found)。检查XML文件里的namespace明明和接口全限定名一致。原因Invalid bound statement的本质是MyBatis没找到与接口方法对应的SQL语句。最常见的两种情况一是XML文件没放在mapper-locations配置指定的目录下二是target/classes目录里根本没有XML文件——Maven默认只把src/main/resources下的文件打包进classpath如果你把XML放在了java包目录下就会漏掉。解决把XML文件放到src/main/resources/mapper/目录确保与application.yml里的mapper-locations: classpath:mapper/*.xml一致。如果你的XML确实在src/main/java下要在pom.xml里加build资源配置把XML也打进classpath——但这是绕路方案建议直接用标准目录结构。另一个排查技巧是启动后看target/classes目录确认XML文件在不在里面。5.3 时间字段的JSON序列化前端看到的2025-01-01T08:00:00和数据库对不上现象前端传了一个2025-01-01 08:00:00格式的时间字符串后端接收时报JSON parse error或者后端返回的LocalDateTime序列化成了2025-01-01T08:00:00前端拿到的格式不是预期的。原因Spring Boot默认使用Jackson做JSON序列化而Jackson对LocalDateTime的默认处理是输出ISO格式带字母T而且默认不支持从yyyy-MM-dd HH:mm:ss这种格式反序列化。数据库和Java代码里讨论时区问题刚解决完HTTP层又会冒出来一个格式问题。解决在application.yml里加一段全局的Jackson时间格式配置这是最省事的方案spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8同时如果用的是Spring Boot 2.xLocalDateTime默认走的是JSR310模块需要把序列化和反序列化都配好。我见过有人在每个实体字段上加JsonFormat注解能解决但很累——同一个项目里几十个时间字段全加注解是重复劳动全局配置才是正确解法。5.4 逻辑删除与唯一索引打架误删一条会议后再建同主题会议就报重复现象会议表有一个字段deleted做逻辑删除meeting表上建了subject的唯一索引或者meeting_participant表上建了meeting_id user_id唯一索引。删掉一条记录后重新插入相同数据时报Duplicate entry。原因软删的deleted字段比如0表示正常、1表示删除和数据库唯一索引发生冲突。唯一索引要求物理上的唯一但逻辑删除后记录还留在表里索引层面它依然存在新插入的数据就触发了唯一约束。解决两个常规做法。一是在唯一索引里包含deleted字段——但这只能在deleted存的是id值比如删除时把字段更新为该行id时生效如果存的只是0/1就无解。二是干脆把逻辑删除字段的默认值改成时间戳语义删除时写当前时间唯一索引改为(subject, deleted_time)。不过说实话对于会议管理系统这个体量我的建议是直接物理删除会议记录不是敏感数据取消的会议状态已经通过status 3标记了用不着软删。这个坑的本质是用了不该用的软删想清楚再动手。5.5 管理端的操作权限接口能做越权操作这是答辩最容易被问倒的地方现象普通用户登录后直接拼URL调用/admin/room/delete接口居然能把会议室删掉。课程设计验收或答辩时老师随便试一个越权操作你整个系统的评价直接降档。原因只做了登录拦截有没有登录没做权限判断登录的人有没有权限。Shiro、Spring Security这类安全框架没有引入Controller里也没有角色校验。解决在Web层配置一个简单的拦截器拦截所有/admin/**请求校验当前登录用户的role字段是否为管理员。这是最轻量的方案不要为了一个课程设计去引入完整的Spring Security——配置繁琐学起来成本高而且你未必讲得清楚。自定义拦截器的核心代码在30行以内就能实现public class AdminInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { SysUser user (SysUser) request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return false; } if (user.getRole() ! 2) { // 不是管理员 response.setStatus(403); return false; } return true; } }注册拦截器的代码在WebMvcConfigurer里把addPathPatterns(/admin/**)配好就行。权限校验放在拦截器里比在每个Controller方法开头写if判断要整洁得多也符合Spring MVC的设计习惯。6. 从课设到可交付验证清单和两个值得做的进阶方向先把一套自我验收的方法整理出来。写完代码别急着交按这个清单过一遍注册一个新用户用它登录创建会议预约一个会议室再用另一个账号登录尝试预约同一个会议室同一时间段确认系统报了该时段已被占用然后用发起人身份取消会议再用其他账号确认预约已被释放。这四条链路走通核心业务就算稳了如果时间有余再用两个浏览器同时打开预约页面同一个会议室同一个时间段快速提交两次看后端是否能拦截住并发——这测试的是冲突检测在POST请求层面的可靠性比单纯点两下单机按钮要真实得多。进阶方向里我最推荐做的是把冲突检测从应用层下推到数据库层这是从课程设计走向工程实践的关键一步。做法是在上面4.2节的FOR UPDATE方案基础上把预约接口改成先SELECT * FROM meeting_room WHERE id ? FOR UPDATE锁住会议室记录再做冲突查询和插入。这样即使两个请求同时进来数据库的锁机制会让第二个请求排队等待从根上解决并发预约的数据一致性问题。做这一步不光是代码变了一行你的设计思路也从我在应用层控制一切升级到了利用数据库的约束能力——这个认识面试的时候比具体技术栈值钱。收个尾。做了这么多次Java项目我最大的教训是无论用哪个框架、哪套代码生成器时间字段的统一、唯一索引的取舍、事务边界的划分这三件事必须在写第一行代码之前想清楚。尤其是会议系统这种和时间打交道的业务想不清楚就动手后面全是返工。希望这篇文章能帮你省下几个晚上的调试时间少踩几个我已经替你踩过的坑。本文还有配套的精品资源点击获取
返回列表