ARTICLE DETAIL

资讯详情

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

Spring Boot游迹共享系统开发实战:数据库、鉴权与缓存设计经验

Spring Boot游迹共享系统开发实战:数据库、鉴权与缓存设计经验 开题答辩结束那天导师说了句功能挺全但你这不就是个带登录的CRUD吗我当时还有点不服气。后来自己把项目从头到尾重构了一遍把搜索、文件上传、权限校验、接口设计这些细节全部补齐再回头看最初的版本确实只能算个半成品。这篇就把我做完整个Spring Boot游迹共享系统后沉淀下来的东西写出来包括表结构怎么设计、MyBatis Plus怎么用省事、JWT登录踩了什么坑、Redis缓存怎么拆希望能帮到正在做类似选题的各位。1. 项目到底做什么从标题拆解核心需求游迹共享系统这六个字拆开看就三个关键词游、迹、共享。游是旅游场景迹是游记内容共享是整个系统的灵魂。很多同学拿到这类题目第一反应是旅游网站然后就开始堆景点介绍页面、旅游攻略文章做着做着发现和内容管理系统没什么区别。这就是需求理解不到位。1.1 游迹共享系统要解决的三个核心问题第一个问题让用户能发布自己的旅行经历。不是管理员发文章而是每个注册用户都能创建游记、上传图片、编辑内容、删改自己的作品。这就意味着系统必须有完整的用户体系和权限控制。第二个问题让用户能找到想看的内容。游记本质是内容内容平台的核心体验就是找。按目的地分类、按关键词搜索、按热门程度排序、按发布时间排序这些都不能少。做得再细一点还可以按标签筛选、按旅行天数筛选、按人均消费筛选。第三个问题让用户之间产生互动。这是共享二字最直接的体现。点赞、收藏、评论这三件套构成了用户和用户之间最基本的互动链路。没有互动的系统只能叫个人记事本有了互动才算共享平台。说白了这个项目的本质是一个垂直领域的UGC内容社区类似简化版的小红书或者马蜂窝。把这个定位想清楚后面所有功能设计、表结构设计、接口设计就都有了方向。1.2 核心功能清单与用户角色定义系统就两类角色普通用户和管理员。普通用户能注册登录、浏览游记、搜索内容、发布游记、编辑删除自己的游记、上传图片、点赞收藏评论。管理员比普通用户多了用户管理和内容管理权限可以禁用违规账号、下架违规游记。功能清单拉出来大概是这样的模块功能点说明用户模块注册、登录、个人信息查看修改登录要用JWT签发Token游记模块发布、编辑、删除、详情分页列表核心业务模块图片模块上传、访问、删除本地存储方案配置虚拟路径映射互动模块点赞、收藏、评论用户间互动核心功能搜索模块关键词搜索、标签分类筛选可按目的地、标题、内容匹配这里要特别强调一点这个项目的最佳定位是Spring Boot单体应用不是微服务。我见过不少同学一上来就想着用Spring Cloud拆服务结果把自己的毕业设计活活做成了一台吞时间的机器。单体应用把所有业务模块放进一个工程里用包名区分不同功能域开发调试部署都简单对于个人项目来说足够清晰了。2. 技术选型为什么是Spring Boot MyBatis Plus这套组合技术选型决定了项目的骨架。当时我在Spring Boot JPA和Spring Boot MyBatis Plus两套方案之间纠结了很久最后选了MyBatis Plus。理由其实很现实这个项目涉及多表关联查询、动态条件拼接、分页查询MyBatis Plus对这些场景的支持更顺手。2.1 为什么不用SSH框架和原生MyBatis说句不好听的现在做Java后端项目还在用SSHSpring Struts Hibernate属于给自己找不痛快。Struts的XML配置繁琐到让人崩溃Hibernate的HQL调试起来也够呛。Spring Boot把这堆东西全部干掉了约定优于配置一套自动配置机制让大部分场景直接零配置跑起来。原生MyBatis确实灵活但SQL都要自己手写Mapper XML文件维护起来特别累。MyBatis Plus在MyBatis基础上把单表的增删改查全部封装好了连分页插件、乐观锁插件、逻辑删除这些都是现成的代码量能少三分之一。对于那些确实需要手写SQL的复杂查询MyBatis Plus照样支持自定义Mapper接口和XML映射灵活性一点没丢。2.2 为什么不用RestFul风格和JWT并存其实要的就是这个组合登录方案选择上如果用session前端后端都得处理Cookie跨域问题。要部署到服务器上还得考虑session保存到Redis还是内存分布式场景下又有一堆事。JWT方案就简单得多用户登录成功后后端签发一个带过期时间的Token前端每次请求放在请求头里带过来后端做拦截器解析校验就行。不需要在服务端保存会话状态天生适合前后端分离架构。数据库用MySQL缓存用Redis。MySQL存结构化业务数据Redis存验证码、热门游记列表、用户信息缓存这类访问频率高、实时性要求不是那么高的数据。项目规模大了之后Redis还能做分布式锁的底层存储这属于后续扩展空间了。2.3 前后端分离还是不分离两种典型做法前后端分离是当前企业级项目的主流方案也是市面上技术博客、面试题讨论得最多的方案。我按照Spring Boot做纯后端接口 Vue做前端页面 Nginx做静态资源托管的方案来设计接口统一返回JSON前端通过Axios调用。当然也有一种做法是Vue项目打包后丢进Spring Boot的static目录由Spring Boot同时托管接口和前端页面。这种方案省去了部署Nginx的步骤可以直接打成jar包一键运行。我实测下来觉得这方案适合纯演示场景但如果项目要作为毕设展示面试官肯定会问为什么不用Nginx做动静分离到时候就不好回答。所以还是老老实实前后端分离。2.4 版本选型别让版本坑了你Spring Boot的版本选择是很多新手栽跟头的地方。我当时用Spring Initializr生成项目默认拉的是最新版本Spring Boot 3.xJDK也从8升到了17。JDK 17用起来确实没问题但很多同学之前装的环境是JDK 8这就尴尬了。Spring Boot 2.x对应JDK 8Spring Boot 3.x对应JDK 17这两个版本线的兼容性是完全不同的。我的建议是千万不要盲追最新版。如果你是做毕业设计用Spring Boot 2.7系列是最稳的教程多、坑少、遇到问题随便搜都有答案。你做一个社区项目不是在做前沿技术研究稳定压倒一切。3. 核心模块与数据库设计数据库表的设计功底基本决定了后面写代码的体验。很多同学上来就建表建表的时候不想清楚关联关系写的时候发现要么少字段要么多表关联一团乱麻返工改表又牵连一堆代码苦不堪言。我这次总共设计了7张核心表围着用户和游记两个中心转。3.1 七张核心表的设计思路用户表user存账号密码和基础信息。这里有个细节密码不要明文存用BCrypt加密。很多同学会用MD5加盐但Spring Security自带的BCryptPasswordEncoder直接就能用比MD5安全得多。游记表travel_note是核心业务表包含标题、封面图URL、目的地、出发时间、旅行天数、人均消费、内容正文、浏览数、点赞数、收藏数、发布状态等字段。正文用TEXT类型因为游记正文往往几千字VARCHAR根本扛不住。图片表image单独拆出来的原因在于一篇游记可以传多张图片如果图片地址都拼在游记表的一个字段里后续做图片管理会非常恶心。拆成单独的表每次上传生成一条记录用note_id关联到游记逻辑清晰。互动相关的三张表是点赞表like_record、收藏表favorite_record、评论表comment_record。前两张都采用用户ID加游记ID的唯一索引约束防止重复点赞、重复收藏。分类标签表category就两个字段分类名和描述。游记表里用category_id外键关联加上一个专门的标签字段用逗号分隔多个自填标签。城市筛选功能更实用我在游记表里直接存destination字段来支持按目的地搜索。表结构和字段明细我列一下表名核心字段备注userid, username, password, nickname, avatar_url, bio, status用户表travel_noteid, user_id, category_id, title, cover_url, destination, days, cost, content, likes, favorites, views, status, create_time游记表imageid, note_id, user_id, image_url, create_time图片表like_recordid, user_id, note_id, create_time点赞表favorite_recordid, user_id, note_id, create_time收藏表comment_recordid, note_id, user_id, content, parent_id, create_time评论表categoryid, name, description分类表3.2 数据库表结构设计详解先看用户表。username是核心索引字段登录时经常按用户名查询用户必须在username上建唯一索引免得注册时并发插入两条相同用户名。password字段长度设60刚好匹配BCrypt加密后输出字符串的长度。头像字段avatar_url允许为空因为用户可以后期再上传头像。游记表travel_note里title加普通索引配合搜索场景。destination字段也要加索引因为按目的地筛选是高频率操作。status字段有特殊含义0是草稿1是已发布2是已下架。这几层状态决定了前台展示逻辑和后台审核逻辑。likes和favorites这两个字段是冗余字段用来在列表页直接展示统计数据避免每次查询都去统计点赞表。这里有一个取舍问题点赞数到底是实时统计好还是冗余存好我的做法是两者结合。游记列表页展示的是冗余字段性能快游记详情页刚打开也是用冗余字段的缓存后台异步任务定期重算一次点赞表的真实计数更新冗余字段。只是这样一个简单项目没必要引入消息队列用一个定时任务就能解决我后面会讲。评论表里parent_id字段用来做回复评论的场景。如果一条评论是顶级评论parent_id为NULL如果是回复另一条评论parent_id指向被回复的评论ID。这样设计可以方便地在前端做楼层效果。4. 从零搭建项目骨架4.1 用Spring Initializr初始化项目创建项目直接用IDEA里自带的Spring Initializr就可以也可以在官网start.spring.io上在线生成。关键看勾选哪些依赖。我这边的清单是Spring WebMySQL Driver或者你用的MySQLMyBatis Plus FrameworkLombokSpring Data RedisValidationJWT相关依赖要手动加Initializr里没有生成完项目后需要手动在pom.xml里添加MyBatis Plus和JWT的依赖。MyBatis Plus当前最新的3.5.x版本已经给了starter只要一个坐标就能引入注意要在artifactId里区分mybatis-plus-boot-starter对应的Spring Boot版本。4.2 配置文件详解application.yml是我这种项目最重要的配置文件。先看数据源配置。使用Spring Boot 2.7时MySQL驱动坐标是mysql:mysql-connector-java配置的driver-class-name是com.mysql.cj.jdbc.Driver。如果你是Spring Boot 3.x驱动坐标换成了com.mysql:mysql-connector-j注意别搞混。Redis配置要注意Redis 6之前默认没有密码Redis 6之后默认配置了requirepass需要在配置里写上spring.redis.password。如果连不上Redis第一件事就检查这里。文件上传的配置也要提前设好。Spring Boot默认单个文件上传上限是1MB写游记传个配图绝对不够用。我设置了spring.servlet.multipart.max-file-size: 10MBmax-request-size: 20MB同时还要在WebMvcConfig里配置静态资源映射。4.3 集成MyBatis Plus的正确姿势集成MyBatis Plus有几个核心步骤。第一步是在启动类上加上MapperScan注解扫描Mapper接口所在包。第二步是继承BaseMapper单表的基础CRUD就全有了。第三步是配置分页插件在配置类里new一个MybatisPlusInterceptor添加PaginationInnerInterceptor。这里必须强调一个细节没有配置分页插件的话调用selectPage方法是不生效的它会查全表再内存分页数据量一大就直接卡死。很多人代码看起来没问题做个分页发现数据不对多半就是这个原因。另外MyBatis Plus的默认行为里updateById不会把NULL字段更新进去。这是设计如此因为MyBatis Plus默认用了FieldStrategy.NOT_NULL策略。但如果你确实需要把一个字段置为NULL就得在字段上加TableField(updateStrategy FieldStrategy.IGNORED)注解。我当时在做游记编辑功能时用户清空封面图就遇到了这个坑。5. 核心功能模块实现解析骨架搭好了接下来就是往里填肉。这部分我挑几个核心模块的重点实现来讲代码逻辑、参数取舍、权限控制都在里面。5.1 用户注册登录与JWT鉴权注册流程不复杂但要严谨。用户传过来username、password、nickname后端先查重用户名是否已存在存在就抛异常不存在就用BCryptPasswordEncoder加密密码后入库。邮箱和手机号这种敏感字段如果系统没有验证码机制不建议注册时硬性要求填写要不然你还要接短信服务自己给自己找事。登录流程是核心中的核心。用户密码校验通过后生成JWT返回给前端。JWT里我放了userId和username两个信息。生成JWT需要设置过期时间一般设为2小时太短体验差太长安全性差。我选的是2小时用户可以无感续签后续可以加刷新机制。JWT的校验怎么做我写了一个拦截器实现HandlerInterceptor接口preHandle方法里从请求头取Authorization字段。这个字段的格式约定是Bearer 空格token前端必须按这个格式传。取到token后调用JWT工具类解析如果解析失败直接通过response返回401状态码同时在拦截其中将解析出的userId放入request的Attribute里方便后续业务代码取用。拦截器要注册到WebMvcConfig里这里要注意排除路径配置。注册、登录、首页展示列表这些接口是不需要登录就能访问的必须全部加进排除列表。像用户信息修改、发布游记、点赞评论这些操作必须登录后才能进行路径模式设置成/api/user/**这类需要登录的路径。5.2 游记的增删改查操作发布游记是这个小项目最复杂的接口。前端提交表单数据包含标题、分类、目的地、出发时间、旅行天数、人均消费、内容正文、图片地址列表等一堆字段。那意味着后端Controller接收参数就得多处理一层不能简单用一个实体类接收。我的做法是设计一个TravelNoteDTOData Transfer Object专门用来接收前端提交的表单信息。DTO和实体类之间用BeanUtils的copyProperties方法做属性拷贝。发布接口的逻辑是新建TravelNote对象补充userId、status字段把创建时间设为当前时间插入数据库。再把前端传过来的图片URL列表组装成Image对象列表批量插入图片表。同时记得给游记设置一个封面图我直接约定取第一张图片的URL作为封面图用户也可以手动指定。游记列表展示也值得讲一讲。我用的是MyBatis Plus的Page对象new Page(current, size)表示当前页和每页条数然后在包装条件查询中调用orderByDesc(create_time)按时间倒序。为了让列表页能展示作者昵称而不只是userId用自定义SQL联表查询写了自定义Mapper接口方法。这种场景MyBatis Plus的LambdaQueryWrapper只能做单表查询搞不定的部分就要自己写SQL了。详情页的浏览量累加就是一个updateById调用先根据id查游记把views字段加1后再更新。高并发场景下可能有一致性问题但个人项目没关系。如果你的项目想展示一点并发意识可以直接写一条SQLUPDATE travel_note SET views views 1 WHERE id #{id}通过MySQL原子更新避免并发覆盖。5.3 图片上传与静态资源映射图片上传用了Spring MVC的MultipartFile机制。前端用ElementUI的Upload组件或者原生表单提交都行后端接口接收MultipartFile参数。我建议的保存路径是项目运行目录下的/upload/2024/05/这种按日期分目录的层级结构用UUID生成随机文件名避免文件名冲突。文件保存后访问路径和存储路径是两个概念。我在WebMvcConfig里配置了静态资源映射把/upload/**的URL路径映射到本地磁盘的/upload目录这样用户就能通过http://localhost:8080/upload/2024/05/xxx.jpg直接访问图片。图片格式和大小校验我放在了上传处理逻辑里检查后缀名是不是jpg、png后缀检查文件大小不超过10MB。不校验会出大问题如果用户上传一个exe文件或者超大视频既浪费服务器存储又容易被安全扫描发现问题。还有一个小细节建议对图片文件名做个安全处理防止路径穿越漏洞这里用的是UUID天然免疫。5.4 搜索与筛选功能实现搜索功能最常见的实现方式是MySQL的LIKE模糊查询。在MyBatis Plus中用like(title, keyword)就能生成title LIKE %keyword%的SQL。我设置了多个字段的匹配标题、内容、目的地这几个字段只要有一个匹配就算命中。需要提醒的是LIKE查询有个很现实的性能问题以通配符开头的%keyword%格式无法走索引MySQL只能全表扫。数据量到了几十万条后就会明显慢。因此游客项目的场景更好的方案是引入开源ElasticSearch来实现真正的全文检索。但这就又引入了分布式组件考虑到项目的可维护性和毕设工作量先使用MySQL的LIKE方案实现在论文里写明优化方向就好。标签筛选功能相对简单游记表里存了一个tags字段逗号分隔多个标签按标签筛选时用FIND_IN_SET函数匹配即可。目的地筛选用destination字段直接等值匹配。6. 常见问题与排查实录这部分大坑小坑我都踩过挑几个有代表性的分享出来能帮大家省不少时间。6.1 版本太高引发的连环坑有位同学用的Spring Boot 3.2集成MyBatis Plus的starter时发现版本不对循环依赖配置也全都变了。Spring Boot 2.x和3.x很多启动器坐标都不一样比如MySQL驱动从mysql-connector-java改成mysql-connector-jjavax.servlet改成jakarta.servlet。如果网上的教程是2.x的你拿着3.x配一步一个错每个报错都要搜半天。我的建议很直接毕设和普通项目用Spring Boot 2.7系列。JDK 8稳定网上资料最多。涉及的依赖版本都好配。Spring Boot 2.7已经是很成熟的稳定版本安全性问题也有很长时间的基础积累。6.2 Redis连接不上的排查流程Redis连接不上是项目里出现频率最高的报错之一。我的排查流程是这样的先确认Redis服务是否启动然后用redis-cli ping看返回是不是PONG。确认服务启动后检查配置文件里spring.redis.host和port确认端口默认6379没被占用或没连错。再看密码如果redis.conf里设置了requirepassapplication.yml里必须配密码没配的话直接连接失败。最后检查防火墙如果是云服务器要在安全组规则里放行6379端口。6.3 跨域问题排查记录我的项目前端跑在8081端口后端跑在8080端口典型的跨域场景。没有做任何配置的时候打开浏览器控制台满是CORS报错。解决方法是写一个CorsFilter注册为Bean并设置允许来源、允许方法、允许请求头。需要注意一个细节如果你同时配置了拦截器和CORS过滤器要注意它们的执行顺序。Spring Boot里CORS配置比拦截器先执行的如果你遇到请求被拦截器拦截了返回401后前端还是收到CORS错误很可能就是跨域配置没有生效或者被拦截器的顺序问题影响了。6.4 文件上传失败的排查之路文件上传失败常常出现两种报错。一种是因为超出了Spring MVC默认的上传大小限制控制台会抛出MaxUploadSizeExceededException。解决方法是调整配置项spring.servlet.multipart.max-file-size和spring.servlet.multipart.max-request-size。另一种是文件大小和上传接口的参数对不上前端FormData里没有用append把文件放进去或者MultipartFile的参数名和前端表单的name不一致也会导致后端收到的是一个空值。6.5 JWT依赖引入与过期时间设置的细节JWT相关的Java库最常用的是io.jsonwebtoken:jjwt引入依赖时要注意版本兼容。我用的0.9.1版本和JDK正常兼容。如果你引入之后出现ClassNotFoundException先检查是不是缺了依赖再检查是不是版本冲突。JWT的过期时间要结合业务考虑。我这个项目登录态的过期时间设置为2小时。实测下来如果用户长时间停在网页写过长的游记到发布时发现Token过期了也是影响体验的问题。建议可以在前端加上Token过期前的提示提醒用户会话即将过期请及时保存。或者干脆在用户提交表单前做个自动刷新Token的逻辑避免写半天发布时掉线。7. 再完善的几个优化点做系统最忌讳功能堆完就收工。优化点花不了太多时间但对项目完成度帮助很大。7.1 定时任务重算热度数据关于点赞数、收藏数、浏览数这三个字段在高并发下的准确性问题我用了一个简单可靠的方案设置一个定时任务每10分钟跑一次。任务内容很简单分别统计点赞表、收藏表并按note_id分组把计算出的真实计数更新到游记表的冗余字段。这样实现了用户展示层的极速读取和后台统计的真实性兼顾。注意给定时任务加上EnableScheduling注解定时任务类上要加Component注解标注为Spring管理的Bean。7.2 Redis缓存热游记列表首页作为流量最大的页面每次都查数据库必然浪费资源。我对首页列表做了一个缓存设计第一次请求时从数据库查询列表然后将结果序列化为JSON存入Redis缓存时间设为10分钟。第二次请求直接读Redis如果缓存命中就返回缓存结果。这样显著降低数据库压力也为项目增加了可讲的内容。我实测下来大约能把首页接口的响应时间从200毫秒降到50毫秒左右。缓存存在一个一致性问题如果用户发布了新游记Redis中的缓存数据还是老数据。这里我采用的是最简单的缓存过期策略游客系统的实时性要求不高10分钟延迟完全可接受。7.3 统一返回体和全局异常处理很多新手的接口返回格式是各写各的发布成功返回true查询成功返回List报错返回null。前端对接时需要对不同类型做不同处理非常痛苦。我的做法是封装一个统一返回类Result包含状态码、消息、数据三个字段。所有成功响应返回Result.success(data)所有错误响应返回Result.error(code, msg)。Controller的返回类型就统一为ResultT。配合统一返回体必须做全局异常处理。用RestControllerAdvice和ExceptionHandler注解把校验异常MethodArgumentNotValidException、业务异常BusinessException等统一捕获转换成规范的Result返回。这样即使代码里忘记处理异常前端收到的仍然是标准JSON而不是一坨堆栈信息。这个项目的整体开发时间如果按照我上述的完整方案来做一个熟悉Java基础的人大概需要三到四周。其中数据库设计和表关系理清占一周Spring Boot MyBatis Plus Redis的代码实现占两周前端联调和部署收尾占一周。我在做这个项目时最大的体会是技术点其实都不难难点在于每一步决策都有依据这样毕业设计才能既有功能又有深度。希望这份经验能帮大家少走一些弯路。
返回列表