写这类毕业设计源码解析其实挺有意思的我之前帮学弟学妹改过不少类似项目发现很多人的问题不是写不出来而是根本不知道一个完整的旅游信息管理系统该从哪儿下手、表怎么建、权限怎么做、代码怎么组织。这篇文章就把Spring Boot旅游信息管理系统从需求拆解、技术选型、数据库设计到核心功能实现和常见坑位铺开聊一遍内容足够当毕设参考也适合Java后端入门的人拿来练手。1. 项目定位与功能需求拆解1.1 旅游信息管理系统到底要做什么很多人拿到这个题目第一反应是不就是景点增删改查吗这么想就窄了。一个能拿来当毕业设计的旅游信息管理系统至少要覆盖信息展示、用户交互、后台管理、数据统计这几层内容否则工作量撑不起一篇论文。拆开来说系统可以分成前台用户端和后台管理端两个视角。用户端面向普通游客能注册登录、浏览景点列表和详情、按分类或关键词搜索线路、收藏心仪景点、下订单、写评论、管理个人信息。管理端面向运营人员和系统管理员能维护景点、线路、酒店、餐饮等基础资源能处理用户订单状态能管理用户评论还能看到访问量、订单量、热门景点排行等统计数据。业务流也很清晰举个例子一个用户注册登录后先去景点列表页逛一圈点进某个景点详情页看看介绍和用户评价觉得不错就把它加进自己的行程计划然后选择出行日期和人数生成订单支付或提交订单后等待管理员确认。管理员在后台看到订单审核通过或拒绝用户就能在订单列表里看到状态变化行程结束后还能给这个景点写一条评论。整套链路就是旅游信息系统的核心闭环。1.2 角色划分与权限控制这里涉及两个角色实际开发中要做成两套入口权限必须隔离。用户角色走前台接口能访问景点查询、线路查看、订单创建和个人信息修改。管理员角色走后台接口能操作用户管理、景点管理、线路管理、订单审核、评论审核、数据统计。权限控制的实现方案最简单的就是做一个前置拦截器把请求的URL和当前登录用户的角色匹配一遍。比如访问/admin/**的请求必须要求roleADMIN访问/user/**的请求要求roleUSER并且已登录。如果要做安全边界更高一点的可以直接引入Spring Security但说实话对毕设来说拦截器加会话判断已经够用了而且更好讲解。注意不要在前后端代码里把管理员密码写得过于简单至少用BCrypt加密存储登录校验也走加密比对这种东西在答辩时被问到的概率很高。2. 技术选型为什么是Spring Boot MyBatis Plus MySQL2.1 各层技术选择与理由毕设选型有一个核心原则既要体现技术含量又要保证你能在两个月内写完。用最稳的组合而不是炫技。后端框架选Spring Boot理由很实在。它在Spring生态上做了大量自动化配置启动器机制让项目依赖管理非常顺手内嵌Tomcat让你不用单独部署容器而且网上资料量极大遇到问题几乎都能搜到现成答案。版本建议选2.7.x或3.x都可以3.x要求JDK172.7用JDK8就能跑看自己电脑环境决定。持久层我推荐MyBatis Plus而不是原生MyBatis或JPA。MyBatis Plus把常用的单表CRUD封装好了写代码效率高同时保留XML自定义SQL的能力复杂多表查询依然可以完全控制。对比JPA那种全自动的ORMMyBatis Plus更容易让人理解SQL和数据表的关系答辩时讲起来也更踏实。数据库选MySQL不用犹豫免费、常用、资料多。可视化工具用Navicat或者DBeaver都行我比较推荐DBeaver开源免费而且跨平台。前端方案这条要单独说。我见过很多同学直接用Thymeleaf模板引擎写服务端渲染页面一个项目下来全是Controller直接返回ModelAndView。这种方式也不是不行但页面效果确实比较简陋而且在2024年还这么做会显得技术栈有点老。另一个极端是纯前后端分离Vue3加Element Plus搭管理后台用户端写一个H5页面。这种方式效果好但工作量不小对没系统学过前端的人来说有点吃力。折中方案是后台管理端用Vue3加Element Plus通过接口调用后端前台用户端用Thymeleaf做服务端渲染兼顾SEO展示和开发效率。两种方式在一个项目里共存没什么冲突Spring Boot对静态资源和模板引擎的支持本来就是混合式的。2.2 项目分层与包结构规划好的项目结构不是随手建的它是可维护性的基础。我的建议是标准的分层架构src/main/java/com/example/tourism/ ├── controller/ │ ├── admin/ # 后台管理端接口 │ └── user/ # 前台用户端接口 ├── service/ │ ├── impl/ ├── mapper/ ├── entity/ ├── dto/ # 数据传输对象 ├── vo/ # 视图对象 ├── config/ # 配置类 ├── interceptor/ # 拦截器 ├── common/ # 通用返回结果类、常量等 └── TourismApplication.javaController层只做参数接收和结果封装不写业务逻辑。Service层放具体业务规则比如下单时要校验库存、评论时需要校验用户是否已购买过。Mapper层做数据库操作简单的单表操作用MyBatis Plus的BaseMapper复杂统计查询用XML写SQL。还有一点容易被忽略统一返回结果类。我一般定义一个ResultT包含code、message、data三个字段所有接口都返回这个结构前端判断code是否为200就能知道请求是否成功。不要一会儿返回Map、一会儿返回实体、一会儿又直接返回String那会把前后端联调变成一个灾难。3. 数据库设计从旅游业务到数据表3.1 核心表结构与关系数据库设计是旅游信息系统的基石表建不好后面改起来会非常痛苦。我先给出一个比较完整的最小表集合再挑关键表展开说明。用户表t_user主键ID、用户名、密码BCrypt加密、昵称、头像、手机号、邮箱、角色USER/ADMIN、创建时间、状态正常/禁用。景点表t_scenic主键ID、景点名称、封面图URL、详细描述、所在城市、地址、门票价格、开放时间、评分冗余字段用来展示、浏览量、状态上架/下架、创建时间。线路表t_route主键ID、线路名称、封面图、线路描述、行程天数、价格、出发城市、目的地、包含景点IDs用逗号分隔存储需要联表展示时再拆、状态、创建时间。订单表t_order主键ID、订单号唯一需要生成规则、用户ID、线路ID关联线路、出行日期、人数、总金额、联系人姓名、联系电话、订单状态待支付/待确认/已确认/已完成/已取消、创建时间、更新时间。评论表t_comment主键ID、用户ID、景点ID、订单ID用来校验消费真实性、内容、评分、状态待审核/通过/拒绝、创建时间。收藏表t_favorite主键ID、用户ID、景点ID、创建时间用户收藏景点和线路也可以用同一张表加个类型字段区分。这几张表之间的关联关系不算复杂。用户在景点详情页能看评论评论需要通过景点ID查评论表再关联用户表取出评论人昵称和头像。订单表同时关联用户和线路主要用于个人订单列表和管理员审核。收藏表是典型的多对多中间表一个用户可以收藏多个景点一个景点可以有很多用户收藏。光看字段可能不够直观我再把最关键的表结构写成建表SQL放出来。注意SQL里我故意精简了一些备注和索引实际项目里一定要补全。CREATE TABLE t_scenic ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 景点名称, cover_image varchar(255) DEFAULT NULL COMMENT 封面图URL, description text COMMENT 详细描述, city varchar(50) DEFAULT NULL COMMENT 所在城市, address varchar(255) DEFAULT NULL COMMENT 详细地址, ticket_price decimal(10,2) DEFAULT 0.00 COMMENT 门票价格, open_time varchar(100) DEFAULT NULL COMMENT 开放时间, score decimal(2,1) DEFAULT 5.0 COMMENT 评分, views int(11) DEFAULT 0 COMMENT 浏览量, status tinyint(4) DEFAULT 1 COMMENT 状态1上架 0下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT景点信息表;订单号生成规则建议用一个全局唯一的方案不要用自增ID直接当订单号展示容易暴露数据量。最简单的方案是用时间戳 随机数或者yyyyMMddHHmmss 用户ID 3位随机数生成后加唯一索引防止冲突。3.2 表字段设计的几个坑表设计里最容易踩的坑我总结一下这些都是实际改过很多遍才悟出来的。第一个是字符串长度。景点描述、线路行程这些字段不要用varchar(255)硬扛长文本直接上text类型。用户名、手机号这些反而要设合理长度比如手机号varchar(11)不要图省事统一设255虽然不会出错但答辩时老师问一句为什么手机号要用255就尴尬了。第二个是时间字段类型。创建时间用datetime就够不要用timestamp因为timestamp有时间范围限制2038年会出问题。也不用刻意用bigint存毫秒数纯属给自己添麻烦。所有表都应该有create_time和update_time两个字段这在后面做数据统计时太有用了。第三个是金额字段用decimal而不是float或double。float和double在计算时会有精度丢失虽然景区门票这种大多两位小数看起来不敏感但你做订单金额合计时就会暴露问题。decimal(10,2)可以支持到千万级别的金额毕设项目绝对够用。第四个是冗余字段的使用。我在t_scenic表里放了score和views两个冗余字段表示景点评分和浏览量。这两个值在设计上可以直接从评论表和日志表实时计算但每次列表页加载都要做聚合查询数据库压力很大体验也不好。所以实际做法是用户提交新评论时更新景点的score值为最新平均分用户访问景点详情页时对views加一。这就是典型的空间换时间牺牲一点写入性能换取稳定的查询性能。第五个是关于软删除。delete逻辑不要真删数据加一个deleted字段区分逻辑删除。用户收藏了某个景点结果景点被物理删除了用户收藏列表里就会出现脏数据甚至关联报错。用逻辑删除之后查询时统一过滤deleted 0就行。提示在MyBatis Plus中可以直接使用TableLogic注解实现逻辑删除这样所有通过框架执行的删除操作都会自动变成UPDATE语句不用手动过滤。4. 核心功能模块实现4.1 登录认证与权限控制登录认证是每个系统的门面做完这个模块整个项目的骨架基本就立起来了。我采用的方案是Session 拦截器和Spring Security比起来更加轻量、可控。先写一个LoginInterceptor实现HandlerInterceptor接口在preHandle方法里检查当前Session中是否存在loginUser对象。如果不存在直接返回统一结果未登录并禁止继续访问。如果用户的角色不匹配请求的前缀路径也直接拦截。Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行一些公共接口登录、注册、景点列表等 if (handler instanceof HandlerMethod) { HandlerMethod method (HandlerMethod) handler; if (method.hasMethodAnnotation(NoAuth.class)) { return true; } } HttpSession session request.getSession(); Object loginUser session.getAttribute(loginUser); if (loginUser null) { response.setContentType(application/json;charsetUTF-8); response.getWriter().write(JSON.toJSONString(ResultHelper.fail(401, 未登录))); return false; } return true; } }这里用了一个自定义注解NoAuth来标记免登录接口比如登录、注册、景点列表这些。这个设计的好处是默认情况下所有接口都需要登录只有明确标注了才放行安全边界更严格不会出现漏配置导致的未授权访问。密码处理也要说清楚。用户注册时用BCryptPasswordEncoder加密密码登录时用matches方法比对原始密码和加密串。不要用MD5加盐那套老方案了BCrypt每次生成的密文都不一样安全性更高。Autowired private PasswordEncoder passwordEncoder; // 注册 user.setPassword(passwordEncoder.encode(user.getPassword())); // 登录 if (!passwordEncoder.matches(rawPassword, user.getPassword())) { return ResultHelper.fail(用户名或密码错误); }角色权限这块我在前端登录成功后会把用户角色存到LocalStorage路由跳转根据角色来判断是否允许进入管理页。后端拦截器再兜底校验双保险。4.2 景点与线路的增删改查景点和线路管理是后台管理端最主要的工作也是前端表格页面的核心。这里涉及列表查询、分页、条件搜索、新增、编辑、删除、上下架几个操作。列表查询用MyBatis Plus的Page类配合LambdaQueryWrapper来实现。查询条件一般包括名称关键词、城市、状态这些条件用StringUtils.hasText和eq方法动态拼接。public PageScenic getScenicPage(int page, int size, String keyword, String city, Integer status) { PageScenic p new Page(page, size); LambdaQueryWrapperScenic wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), Scenic::getName, keyword) .eq(StringUtils.hasText(city), Scenic::getCity, city) .eq(status ! null, Scenic::getStatus, status) .orderByDesc(Scenic::getCreateTime); return scenicMapper.selectPage(p, wrapper); }这个写法要提一下动态SQL拼接的好处参数为空时条件不加入参数非空时条件加入一套代码满足多种不同筛选场景不用为每种情况各写一套SQL。之前用StringBuilder手拼SQL的方式太容易出错了还有SQL注入风险用LambdaQueryWrapper以后这些问题都消失了。景点详情页需要累积浏览量接口里做一次UPDATE操作这一点我在前面表设计时讲过这里就不重复了。图片上传也是一个常见的功能点。最简单的方式是后端接收MultipartFile文件存储到本地服务器的一个upload目录然后返回文件的访问路径。注意要配置静态资源映射把/upload/**路径映射到你存放图片的物理目录上。Value(${file.upload-path}) private String uploadPath; PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return ResultHelper.fail(文件为空); } String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString().replaceAll(-, ) ext; File dest new File(uploadPath / fileName); file.transferTo(dest); return ResultHelper.success(/upload/ fileName); }文件名用UUID重命名避免中文名和重名导致的路径问题。这个方案适合毕设场景生产环境会改用云存储但原理是一样的就是返回一个可访问的URL。线路表有一个包含景点IDs的字段这个在新增线路时接收一个数组存储时用String.join(,, ids)拼成字符串。展示线路详情时再把字符串拆开用selectBatchIds查景点对象。这个设计在毕设里足够了不用正经做人家的关联表或JSON字段答辩讲解也不复杂。4.3 订单与评论业务实现订单是旅游系统里业务逻辑最重的模块因为涉及多个表的联动还要做状态机管理。用户端下订单的流程是从线路详情页发起下单携带线路ID、出行日期、人数、联系人信息和备注后端创建订单记录。创建前要校验线路是否上架、库存是否充足。由于订单服务里没有实际支付渠道这里做一个模拟支付用户点击立即支付之后订单状态从待支付变成待确认然后管理员在后台做最终确认或取消。Transactional public ResultString createOrder(OrderCreateDTO dto, User loginUser) { Route route routeService.getById(dto.getRouteId()); if (route null || route.getStatus() ! 1) { return ResultHelper.fail(线路不存在或已下架); } Order order new Order(); order.setOrderNo(generateOrderNo(loginUser.getId())); order.setUserId(loginUser.getId()); order.setRouteId(route.getId()); order.setTravelDate(dto.getTravelDate()); order.setPeopleCount(dto.getPeopleCount()); order.setTotalAmount(route.getPrice().multiply(new BigDecimal(dto.getPeopleCount()))); order.setContactName(dto.getContactName()); order.setContactPhone(dto.getContactPhone()); order.setStatus(0); // 待支付 orderService.save(order); return ResultHelper.success(下单成功, order.getOrderNo()); }看到那个Transactional注解了吧。数据库操作涉及多张表时一定要加事务这里先查线路、再生成订单、后面可能还要扣减库存任何一个环节失败都应该让整个操作回滚。不要忘了在这个注解上配置好事务管理器Spring Boot默认支持加了基本就能生效。将订单设为已完成这个动作我建议做成一个定时任务每天凌晨把超过出行日期两天的待确认订单自动置为已完成。用户自己也可以在订单列表页手动确认完成。评论功能比较容易被小瞧但实际有很多细节要处理。用户在景点详情页写评论后端接口要校验两点这个用户是否下过该景点的线路订单且订单已完成。没有消费记录一律不允许评论否则刷评论太容易了。评论提交后在后台列表看一看管理员可以审核通过或拒绝通过后景点的平均分重新计算并更新到t_scenic.score字段。public ResultString addComment(CommentCreateDTO dto, User loginUser) { // 校验订单是否存在且已完成 long orderCount orderService.lambdaQuery() .eq(Order::getUserId, loginUser.getId()) .eq(Order::getRouteId, dto.getRouteId()) .eq(Order::getStatus, 3) // 已完成 .count(); if (orderCount 0) { return ResultHelper.fail(您尚未完成该线路的行程无法评论); } // 保存评论状态默认待审核 Comment comment new Comment(); comment.setUserId(loginUser.getId()); comment.setScenicId(dto.getScenicId()); comment.setContent(dto.getContent()); comment.setScore(dto.getScore()); comment.setStatus(0); commentService.save(comment); return ResultHelper.success(评论成功等待审核); }我建议评论区默认只展示审核通过的评论审核不通过的评论只有管理员能看到。这既减小了前端展示的垃圾内容也让管理员审核这个功能有了实际意义。4.4 数据统计与报表数据统计这个模块是很多毕设的加分项也是让论文能升华的关键点。不需要搞很复杂的数据可视化大屏简单几个统计卡片加图表就足够。统计内容一般包含用户总数、景点总数、订单总数、本月订单数、近七天订单量趋势、热门景点Top5。订单量趋势可以用MySQL的日期函数来实现。按天分组统计订单数量找出过去七天的订单数再和日期一一对应前端用ECharts画一个折线图。SELECT DATE(create_time) AS day, COUNT(*) AS orderCount FROM t_order WHERE create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(create_time) ORDER BY day;热门景点Top5其实查景点表的views字段就够了这个冗余字段在这里派上了用场。按浏览量倒序排列取前五条前端展示一个横向柱状图。统计接口最好单独写一个Controller比如/admin/statistics/overview返回所有统计指标前端管理页进入时一次请求全部加载。查询逻辑不复杂就是各种count和sum注意SQL别写错了尤其多表关联时要小心分组错误导致的重复计数。5. 开发环境准备与项目启动5.1 环境依赖清单把整个项目从零跑起来需要准备的环境依赖如下。这一节我按毕设答辩时最常见的检查顺序来写如果有一项缺失项目启动就会报错排查起来非常头疼。首先是JDK。Spring Boot 2.x用JDK8就能跑Spring Boot 3.x要求JDK17及以上。建议直接用JDK8跑2.7.x版本因为很多学校机房电脑和论文里使用的环境都是JDK8兼容性最好。JDK配好后命令行执行java -version确认版本。然后是Maven。本地环境要有MavenIDE用IDEA的话直接用内置的Maven也行但要注意仓库镜像。如果依赖下载很慢或失败换阿里云镜像在settings.xml里配置阿里云仓库地址。这一步几乎所有第一次跑Spring Boot项目的人都会卡住默认为repo.maven.apache.org太慢经常下到一半超时。MySQL是最重要的一环。我建议用MySQL 8.0建库时注意字符集和排序规则要用utf8mb4和utf8mb4_general_ci数据库连接驱动用com.mysql.cj.jdbc.DriverURL里加上serverTimezoneAsia/Shanghai解决时区问题。最后是Redis如果你的项目用到了缓存或验证码功能才需要。很多学生的毕设其实用不到Redis但为了体现技术深度硬加进去结果配置不熟项目反复启动失败。我的建议是如果对Redis足够熟悉就加不熟悉就用本地Session解决登录和验证码需求不要为了加而加。5.2 启动配置与应用配置Spring Boot项目的核心配置文件是application.yml启动报错九成和这里的配置有关。下面是一份最基础但大概率能直接跑起来的配置模板server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/tourism_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 100MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 file: upload-path: /opt/tourism/upload前面加粗的map-underscore-to-camel-case必须打开这样数据库字段create_time才能自动映射到Java实体类的createTime否则查询结果全是null。在项目里需要把启动类上的注解加好标准做法是SpringBootApplication加MapperScan指明Mapper接口所在的包路径。不要在MapperScan里写错包名否则所有Mapper都无法注入。启动项目后控制台出现Tomcat started on port(s): 8080字样表示启动成功。如果端口被占用可以在配置里改成别的端口或者杀掉占用进程。接口测试推荐用Apifox或者Postman先测试登录接口、再带token访问受保护接口。前端管理台是用Vue3搭建的需要单独启动前端开发服务器通过代理配置把/api前缀的请求转发到后端8080端口。6. 常见问题与排查实录6.1 项目启动失败的几种典型原因把常见的启动失败原因按出现频率排个序你看完基本能自己定位。第一是数据库连不上。启动报错信息里出现Communications link failure或者Access denied for user大概率是数据库服务没启动、密码错了、数据库名不对。自己搭初始化SQL的时候先手动在Navicat里执行一遍建表脚本确认库已在并能正常查询再启动后端项目。第二是端口冲突。报错Port 8080 was already in use在命令行输入netstat -ano | findstr 8080找到占用进程并结束掉或者直接在application.yml里换端口。第三是依赖引入问题。IDEA里爆红找不到类或者Could not resolve dependencies先用mvn clean install重新下载依赖再检查是否忘了给项目配Maven仓库。第四是Java版本和Spring Boot版本不匹配。Spring Boot 3.1以上的版本要求JDK17如果坚持用JDK8就老老实实把版本降到2.7.x。6.2 中文乱码问题开发中的中文乱码分成两种一种是页面展示乱码一种是存储到数据库后乱码。页面展示乱码排查思路是看前后端交互编码是否一致。后端在application.yml里设置server.servlet.encoding.forcetrueSpring Boot默认使用UTF-8一般不会出问题。前端HTML页面如果用了模板引擎记得在meta标签或者模板里指定charsetUTF-8。数据库存储乱码基本上就是建库时字符集不是utf8mb4导致。如果你在Navicat里看到数据库表的字符集是latin1或者utf8那存进去的中文大概率变成问号。彻底解决方案是把整个库的默认字符集改成utf8mb4这个在创建数据库时就要定好后续再改表级别字符集比较麻烦。6.3 前端跨域问题前后端分离开发中跨域问题是出现频率最高的前端相关报错。浏览器报错Access to XMLHttpRequest has been blocked by CORS policy核心原因就是前端页面在5173端口、后端接口在8080端口请求跨了域。解决办法是在后端加一个CorsFilter或者使用CrossOrigin注解但我更推荐统一配置一个跨域过滤器一劳永逸。Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); CorsConfiguration config new CorsConfiguration(); config.addAllowedOrigin(http://localhost:5173); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); }如果不希望在前端显式指定接口端口也可以在前端配置代理比如Vite的server.proxy配/api转发到8080这样浏览器访问的路径实际是同源的不会触发跨域。6.4 部署到服务器的几点经验毕设作品大多需要部署到服务器给答辩老师演示这里有几个容易踩的点说一下。服务器上部署Spring Boot项目最简单的方式是打成jar包放服务器上用nohup java -jar xxx.jar 后台运行。注意生产环境的数据库连接串要改成服务器的MySQL地址不要还是localhost指向本地。服务器带宽和对访问速度的影响要提前做好预期。上传图片尽量压缩景点详情页图片一多服务器带宽小会卡得没法演示。部署前在本地多上传几张测试图确认页面加载速度能接受。如果服务器安装了云安全防护策略记得把8080端口加入安全组白名单并开放访问否则外网访问不到。这个坑特别常见本地跑通、服务器也跑起来了就是外网访问不到最后发现是安全组权限问题。还有一个部署小技巧上线时把MyBatis Plus的log-impl从StdOutImpl改成全关闭或生产实现否则控制台一直刷SQL日志不仅看着乱还可能占磁盘空间。结尾的一点实际心得这套项目我前前后后帮人搭过和改过好几回最大的感受是毕业设计不要贪功能多把数据库设计清楚、把核心链路走通、把常见异常处理好比硬堆功能更有竞争力。我当时给朋友调一个订单状态混乱的bug最后查到原因是在订单状态变更时没有做幂等判断同一个支付回调被触发了两次状态被覆盖了。后来我在所有状态变更接口里都加了一个前置状态校验这种问题就彻底消失了。最后再分享一个小技巧写论文的时候把数据库表之间的关系画清楚把每个核心功能的流程图理出来答辩时被问倒的概率会低非常多。祝正在做这个题目的人少走弯路一次通过。
阅读完成 · 觉得有帮助?