免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

SpringBoot校园新闻网站毕设实战:从数据库设计到部署上线全解析

SpringBoot校园新闻网站毕设实战:从数据库设计到部署上线全解析 “校园新闻网站”大概是Java毕设里“性价比”最高的一类题目。学生信息管理系统太简单撑不起论文篇幅电商系统又太复杂做到中途很容易放弃而校园新闻网站刚好卡在中间——有用户注册登录、有管理员后台、有新闻的增删改查、有分类、有评论、有图片上传、有关键字搜索业务链路完整但每一步都不算太难。这篇文章不说虚的直接以一个SpringBoot校园新闻网站毕设源码项目的完整实现为主线把设计思路、数据库建模、核心代码实现、部署上线和常见坑位全部拆一遍。读完你不仅能把这个项目跑起来还能在答辩时把每个设计决策的来龙去脉讲清楚。1. 项目整体设计与技术选型逻辑1.1 这类选题为什么值得做适合谁来做先说结论SpringBoot校园新闻网站适合两类人——第一类是刚学完JavaWeb和Spring框架、想找完整项目练手的人第二类是时间紧张、要求“能跑通且能讲明白”的应届毕业生。它的业务模型足够真实不会像“学生管理系统”那样廉价也不会像“秒杀系统”那样对并发要求过高、做完短时间讲不透。更重要的是校园新闻网站天然具备“前台展示 后台管理”的双端结构。前台给普通用户看新闻列表、详情、评论后台给管理员维护新闻、分类、轮播图、用户。这种结构几乎是企业里CMS内容管理系统的一个缩影面试官或者答辩老师看到它的第一反应不会是“又一个玩具”而是“这个人有基本的前后端分离意识、有权限控制意识”。我自己看过的毕设代码里用SSM做的老项目比例仍然不小但它们普遍存在一个问题配置太繁琐。spring.xml、springmvc.xml、web.xml一坨堆在一起的XML连启动都要费半天劲。用SpringBoot最大的价值就是把这些配置化的东西全部赶进了起步依赖和自动装配里让新手把精力聚焦在业务本身而不是纠结Bean是怎么注册进去的。1.2 技术栈选择为什么是SpringBoot MyBatis-Plus项目技术栈我建议这样定SpringBoot 2.7.x MyBatis-Plus MySQL 8.0 Spring Security或简化版JWT拦截器 Vue或Thymeleaf。这套组合的核心思路是主流、好查资料、代码量适中。SpringBoot 版本选择是很多新手第一个会踩的坑。目前SpringBoot 3.x已经发布但它要求JDK 17及以上。如果你电脑里装的是JDK 8用IDEA新建项目时默认拉到的又是最新版那启动时就会直接报错。我的建议是直接用SpringBoot 2.7.x它是2.x最终维护版本既有非常成熟的中文资料又能兼容绝大多数学校要求的JDK 8环境。这不是“用老不用新”而是把出错的概率在源头降到最低。MyBatis-Plus在这个项目里的作用很容易被低估。它提供了内置的BaseMapper、分页插件和条件构造器让你可以不写XML就完成单表CRUD。新闻、分类、评论这些表基本都是单表操作用MyBatis-Plus可以少写七八成重复SQL把时间留给真正需要思考的业务逻辑。前端我建议有两种方案如果要求前后端分离可以用Vue3 Element-Plus Axios后端通过JSON交互如果想省事用Thymeleaf模板引擎直接渲染也可以。这篇文章的后端设计基于“接口只返回JSON”的分离模式来讲这也是目前大多数毕业设计论文里写的方案。1.3 后端目录结构与分层边界很多源码拿到手跑不起来原因不在代码逻辑而在目录结构混乱、包路径配错。我常用的分层方式是这样的com.example.news ├── config // 配置类跨域、分页插件、文件映射 ├── controller // 控制层接收参数、返回统一结果 ├── service // 业务层业务逻辑处理接口实现 ├── mapper // 数据访问层继承BaseMapper ├── entity // 实体类与数据库表对应 ├── dto // 前端请求参数封装 ├── vo // 响应给前端的对象 ├── common // 统一返回结果、异常处理 ├── utils // JWT工具、文件上传工具 └── NewsApplication.java这个结构最大的好处是职责边界明确controller不直接操作数据库service不直接处理Http请求。答辩时老师如果问“为什么controller层这么薄”你可以回答业务逻辑统一收口到service层方便事务控制和后续扩展。这一个回答就能看出你不是对着源码硬背的。2. 数据库设计五张表撑起整个系统2.1 功能模块拆解先定边界再建表在画ER图之前先想清楚系统里到底有哪些角色、每个角色能干什么。这个项目里就两类人普通用户和管理员。普通用户能看新闻、搜新闻、看分类、发评论、收藏如果做管理员在普通用户的基础上多了新闻发布、新闻审核如果需要、分类管理、用户禁用、轮播图维护。把这些功能列成一张表格后面建表就有依据了。模块功能点数据表用户注册、登录、个人信息user新闻分类分类列表、添加、修改、删除category新闻管理发布、编辑、删除、列表分页、关键字搜索news评论发表评论、评论列表comment轮播图首页轮播图管理banner2.2 五张核心表的结构与字段解释user表需要注意的地方是密码不能明文存。数据库里保存的是BCrypt加密后的哈希串长度要留够varchar(100)比较稳妥。字段大概是这样CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, nickname varchar(50) DEFAULT NULL, avatar varchar(255) DEFAULT NULL, role tinyint DEFAULT 0 COMMENT 0-普通用户 1-管理员, status tinyint DEFAULT 1 COMMENT 1-正常 0-禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;news表是整个系统的核心。我建议把“发布者”直接存用户ID而不是存用户名这样当管理员改名或做用户管理时不会影响历史数据。cover_image字段存的是图片的URL地址这里有一个容易忽视的点实际开发中图片往往存在独立目录或对象存储里数据库只存访问路径千万不要用longblob去存图片二进制内容否则数据库会迅速膨胀查询也会越来越慢。2.3 评论表为什么要冗余字段评论表的设计有一个经典问题要不要冗余文章标题和用户昵称我的建议是只存必要的外键ID查询时再做关联。校园新闻网站的数据量级很小一次联表查询性能完全够用没必要为了微乎其微的性能收益去破坏数据一致性。CREATE TABLE comment ( id int NOT NULL AUTO_INCREMENT, news_id int NOT NULL COMMENT 新闻ID, user_id int NOT NULL COMMENT 评论用户ID, content varchar(500) NOT NULL, parent_id int DEFAULT 0 COMMENT 父评论ID0表示一级评论, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_news_id (news_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里加一个parent_id字段是为了以后扩展“楼中楼”。很多新闻网站的评论都能回复别人的评论如果一开始不做这个字段后面需求一变就得改表很折腾。加了字段但不用最多浪费一点空间不加后面要改表结构在作业里可是要重写一大片代码的。2.4 用枚举还是用数字表示状态设计表的时候状态字段建议一律用tinyint数字不要直接存字符串。比如用户是否禁用存1和0新闻状态存0草稿、1发布、2下架。在代码里再用枚举类做一层映射既不破坏数据库简洁性又能让代码可读性更高。Getter public enum NewsStatus { DRAFT(0, 草稿), PUBLISHED(1, 已发布), OFFLINE(2, 已下架); // 构造方法和getter省略 }这种细节在答辩时很加分因为它展现了你对“数据字典”的理解。哪怕系统功能做少一点把已有功能做规范也比功能很多但代码一团乱麻要强。3. 核心代码实现从登录到新闻发布全流程3.1 项目初始化与公共配置用IDEA创建项目时Spring Initializr会让你选SpringBoot版本和依赖。这里直接选Java 8、SpringBoot 2.7.x依赖勾上Spring Web、MyBatis Framework、MySQL Driver、Lombok。SpringBoot 2.7.x版本会自动拉取对应的依赖版本所以不需要在pom.xml里手动给spring-boot-starter-web写版本号。真正需要管理额外版本的只有两个MyBatis-Plus和JWT。MyBatis-Plus用3.5.3版本比较稳版本太高有概率和SpringBoot 2.7的兼容性产生小问题。dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependencyapplication.yml里面有一个容易被忽略的配置map-underscore-to-camel-caseMyBatis-Plus默认就开启了驼峰映射所以数据库的create_time能自动映射到Java实体类的createTime字段这省掉了很多结果映射代码。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/campus_news?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 10MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImplurl里强制带上serverTimezoneAsia/Shanghai是MySQL 8.0的硬性要求不加它就会在连接数据库时报告时区错误。这个坑几乎每个新手都会踩一遍。3.2 JWT登录认证不依赖Session也能识别用户校园新闻网站涉及权限区分“用户登录后才能评论”、“管理员才能发布新闻”这些规则必须落地。最简单可靠的方式是JWT令牌认证。什么是JWT你可以把它理解成一个“自带身份信息的通行证”。服务器在登录成功后签发给客户端一串加密字符串之后客户端每次请求都带上它服务器不需要在内存里保存“这个人登录过没有”而是通过验签直接确认身份。在毕设里这种无状态认证方案写起来不复杂答辩时讲出来又非常上档次。登录接口的核心逻辑分三步校验用户名密码、生成token、返回给前端。Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override public String login(String username, String password) { LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, username); User user userMapper.selectOne(wrapper); if (user null) { throw new BusinessException(用户不存在); } if (!BCrypt.checkpw(password, user.getPassword())) { throw new BusinessException(密码错误); } if (user.getStatus() 0) { throw new BusinessException(账号已被禁用请联系管理员); } return JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); } }注册的时候用BCrypt.hashpw对密码做加密入库保存的是密文。这远比直接用MD5靠谱——MD5是摘要算法不是加盐哈希网上有大量的彩虹表可以直接反查。JWT工具类里生成token时要设置过期时间。校园新闻后台不算敏感系统过期时间设置成7天比较合理太短会让用户反复登录太长又会让安全问题堆积。public static String generateToken(Integer userId, String username, Integer role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }有了token之后还需要写一个拦截器把头部的token解析出来解析成功就放行失败就返回401。这一步就是Spring Security里“认证过滤器”最核心做的事。如果不想引入复杂的Spring Security过滤链自己用HandlerInterceptor实现完全够用也更容易在论文里写清楚。3.3 新闻管理分页查询与模糊搜索的细节新闻列表是前端访问最频繁的接口也是最能体现基础功的地方。一个完整的列表接口必须支持分页、分类筛选、关键字搜索另外还要避免把新闻正文content一次性查出来——列表页只需要标题、摘要、封面图、发布时间正文应该在详情页才加载。MyBatis-Plus的分页插件配置方式非常直接Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }分页参数pageNum从1开始pageSize每页条数。对应的service层代码public PageNewsVO pageNews(Integer pageNum, Integer pageSize, Integer categoryId, String keyword) { PageNews page new Page(pageNum, pageSize); LambdaQueryWrapperNews wrapper new LambdaQueryWrapper(); wrapper.eq(categoryId ! null, News::getCategoryId, categoryId) .like(StringUtils.hasText(keyword), News::getTitle, keyword) .eq(News::getStatus, NewsStatus.PUBLISHED.getCode()) .orderByDesc(News::getCreateTime); return newsMapper.selectPage(page, wrapper); }注意那个eq(categoryId ! null, ...)的写法这是MyBatis-Plus条件构造器里非常实用的动态拼接方式条件为false时自动忽略该查询条件省去了一堆if判断。模糊搜索用like会走全表扫描数据量小完全没问题但如果以后新闻量过了百万条就要考虑Elasticsearch了。做毕设不需要提前优化到这个程度但答辩时如果老师问到你能说出这个知识点的演进方向分数不会低。3.4 图片上传与静态资源映射新闻封面、轮播图、用户头像统统涉及文件上传。最简单的方案是上传到本地磁盘再把磁盘目录映射成URL访问。发布新闻时前端会先调上传接口拿到图片URL再把URL放进表单里一起提交。上传接口的代码逻辑是这样的检查文件是否为空检查扩展名是否在白名单里生成UUID文件名保存到指定目录返回访问URL。扩展名校验很重要很多人在自己电脑上随便传jpg没问题但如果开放了上传功能又不限制文件类型服务器分分钟被别人塞进乱七八糟的文件。public String upload(MultipartFile file) { String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); ListString allowSuffix Arrays.asList(.jpg, .jpeg, .png, .gif, .webp); if (!allowSuffix.contains(suffix.toLowerCase())) { throw new BusinessException(不支持的图片格式); } String fileName UUID.randomUUID().toString().replace(-, ) suffix; String datePath new SimpleDateFormat(yyyyMMdd).format(new Date()); File dir new File(uploadPath / datePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, fileName)); return /upload/ datePath / fileName; }在application.yml里配置上传目录然后在配置类中继承WebMvcConfigurer的addResourceHandlers方法把/upload/**映射到磁盘目录Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadPath /); }有一个细节Windows下这个uploadPath要写成D:/upload/这样以斜杠结尾的形式不能写成D:\upload否则资源映射会失效。这种小问题排查起来最费时间我当年在这里卡了整整一个下午。3.5 评论模块事务不是必需索引是关键评论的插入逻辑很简单前端传newsId和content后端从JWT里取userId插入数据库即可。有一点要注意评论功能在插入数据之后通常要重新查一遍这条评论带上用户昵称和头像返回给前端这样前端就能即时刷新。这里不能用前端传来的nickname而要从数据库里查防止用户伪造昵称刷屏。评论列表查询需要联表拿到user表的nickname和avatar。写SQL时给主查询字段加一个limit比如一次只查20条避免一次评论特别多时接口直接变慢。SELECT c.id, c.content, c.create_time, u.nickname, u.avatar FROM comment c LEFT JOIN user u ON c.user_id u.id WHERE c.news_id #{newsId} ORDER BY c.create_time ASC LIMIT 204. 前后端联调、部署上线与问题排查实录4.1 跨域和联调前后端分离的第一个拦路虎如果前端用了Vue开发服务器默认端口是5173后端是8080浏览器就会因为跨域拦截所有请求。解决方式有两种第一种是在后端配置全局跨域Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }第二种是走Nginx反向代理前端请求统一发到NginxNginx再把请求转发到后端。生产环境建议用第二种因为更规范但开发阶段用第一种最快。我实际调试过程中最常遇到的是“预请求”问题。当前端发送带有Authorization头的请求时浏览器会先发一个OPTIONS请求来探测后端是否允许跨域。如果你在Spring Security或拦截器里把OPTIONS请求直接拦截了前端就会一直报CORS错误但怎么都排查不到原因。所以拦截器里一定要对OPTIONS方法直接放行。4.2 常见报错与避坑速查表把几个高频问题整理成一张表每一条都是实际跑项目时真实出现过的照着排查能省很多时间报错或现象原因解决方法启动报错Unable to start web server8080端口被占用netstat -ano | findstr 8080找到PID后结束进程或改server.portJDBC连接失败Public Key Retrieval is not allowedMySQL 8.0默认认证插件问题JDBC url加上allowPublicKeyRetrievaltrue启动报错cannot determine embedded database driver class没有引入JDBC依赖pom里加上spring-boot-starter-jdbc或MyBatis相关依赖数据库查询结果中文乱码连接字符集没指定url加characterEncodingutf8建表用utf8mb4图片上传后访问404静态资源映射没生效检查addResourceHandlers里路径结尾是否有/时间比实际时间早8小时数据库连接没有指定时区url加serverTimezoneAsia/Shanghai前端拿到数据后日期都是时间戳没有配置日期序列化加spring.jackson.date-formatyyyy-MM-dd HH:mm:ss引入Lombok后getter/setter找不到IDEA没开启注解处理Settings - Build - Compiler - Annotation Processors 勾选Enable项目启动太慢非业务原因往往是依赖下载阻塞把maven镜像改成阿里云私服配置settings.xml这里面镜像配置应该算是初学者最痛的一环Maven中央仓库在国内访问速度极慢第一次拉依赖可能要卡半小时。在~/.m2/settings.xml里加上阿里云镜像之后速度立马上来很多“启动失败”其实根本不是代码问题而是依赖没下完。4.3 打包部署从IDEA里跑通到Linux服务器上线毕设答辩结束后总有人想把这个项目部署到服务器上给老师看或者作为找工作的演示项目。SpringBoot项目部署出奇简单因为它内嵌了Tomcat不需要单独安装Web容器。打包命令mvn clean package -DskipTests打出的jar包在target目录下传到服务器后执行nohup java -jar campus-news-0.0.1.jar --spring.profiles.activeprod app.log 21 这就是一套标准的发布流程没有环境变量没配、没有依赖版本不一致、不用装Tomcat一个jar包丢上去就能跑。生产环境里上传图片的路径要提前建好否则上传接口会报文件找不到目录。如果服务器上已经装了Nginx建议把Nginx作为80端口的入口做一层静态资源缓存和反向代理。数据库连接url和账号密码不要以明文写死在jar包配置文件里我用启动参数覆盖java -jar campus-news-0.0.1.jar \ --spring.datasource.urljdbc:mysql://localhost:3306/campus_news?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai \ --spring.datasource.usernameroot \ --spring.datasource.password你的密码这种方式既不修改代码又能按环境灵活切换比在application.yml里改来改去安全得多。4.4 版本兼容性SpringBoot版本高不代表好最后特别想说一下SpringBoot版本的问题。当前网上很多教程还停留在2.x而IDEA新建项目默认推荐的是3.x。如果你照着教程操作却用了3.x版本至少会有几个地方对不上一是JDK版本要求变了3.x最低要求JDK17二是javax.servlet变成了jakarta.servlet代码里导包路径会不一样三是很多老坑的解决方案在3.x里已经不适用了。我的建议是明确自己要用的JDK再去选对应的SpringBoot版本。不要无脑拉最新版也不要因噎废食守着1.x不放。网上资料最多、排坑最容易的是2.7.x对于毕设项目稳定比新功能重要得多。等以后你真参加工作了基于公司指定的版本去适配又是另一套玩法了。还有一种常见情况是本地开发的版本和最后部署的版本不一致开发调试全通过了部署到服务器上突然报错。排查一遍往往是生产环境的JDK版本比本地低代码里用了高版本JDK的语法。所以我一般会在pom.xml里明确指定Java编译版本properties java.version1.8/java.version /properties这个配置能保证项目编译和运行都基于同一个Java版本从源头杜绝环境差异带来的“灵异问题”。5. 写在最后的几个实操心得项目做完以后我有几个体会想特别分享。第一源码免费项目下载下来第一件事不是冲动地到处点开看而是先建库、再启动、再走通一遍流程。很多同学拿到别人给的SpringBoot校园新闻网站源码就急着改代码改到一半发现连登录都进不去回头查才发现建库脚本没执行。按“建库→跑通→读代码→改代码”的顺序来效率会高得多。第二答辩的时候与其花时间背下来整个项目的功能清单不如把三个关键点讲透架构怎么分层、登录认证怎么做、分页查询怎么写。这三个问题几乎每个答辩老师都会问你把底层逻辑讲明白远远好过从头到尾念PPT。第三关于“免费源码”这个噱头我多说一句不好听的。网上确实有很多免费项目但质量参差不齐。有的把数据库脚本弄丢了有的关键代码被扣了有的包结构乱到根本读不下去。我的建议是下载之后一定要自己重新梳理一遍把它改造成“你的东西”——至少要把包名改掉、表名前缀统一、接口命名规范统一。这不只是为了规避那些说不清道不明的版权风险更是因为答辩时老师问的问题基本都藏在你改造过的细节里。如果你正准备拿这个项目当毕设或者想用它来找工作我的建议是把主页的UI稍微改一改比如换个配色、调下布局改完截图放进论文里老师会觉得这是你亲自做出来的印象分完全不同。这个建议可能有点“投机取巧”但说实话一份结构清晰、能跑通、讲解有逻辑的毕设本来就应该得到认可。最后分享一个小技巧给新闻表加一个views字段每次看详情时views1。这个功能实现起来只要一两行代码却能在首页做出一个“热门新闻排行”板块让整个网站的使用逻辑更真实论文里也能多写一段“基于用户行为的推荐雏形”。以小成本撬动高展示度这笔买卖非常划算。
返回列表