
1. 项目背景与意义随着互联网信息量的爆炸式增长新闻资讯的获取方式已经从传统的门户网站逐步转向个性化、社区化的内容平台。用户不再满足于被动接收编辑推荐的内容而是希望参与讨论、发表观点、关注感兴趣的话题并与志同道合的用户进行互动。传统的新闻网站往往以单向信息发布为主缺乏用户参与和内容沉淀的机制难以满足现代用户对信息获取和社交互动的双重需求。新闻社区正是在这一背景下应运而生。它将新闻资讯的发布与用户社区互动相结合既保留了新闻内容的权威性和时效性又引入了评论、点赞、关注、收藏等社交化功能形成了内容生产与用户消费的良性循环。通过社区化的运营平台能够有效提升用户粘性、延长用户停留时间并借助用户生成内容UGC降低内容生产成本构建更具活力的内容生态。从技术层面看新闻社区系统涉及用户认证、内容管理、评论互动、搜索推荐、消息通知等多个核心模块是一个典型的全栈 Web 应用场景。基于 Spring Boot 进行开发能够充分利用其约定优于配置的设计理念、丰富的生态组件和成熟的社区支持快速构建出结构清晰、易于维护、可扩展性强的企业级应用。因此本课题的研究与实现不仅具有实际的应用价值也对理解现代 Web 应用的分层架构、缓存策略、安全认证等核心技术具有重要的学习意义。2. 系统需求分析在系统设计之前首先需要对新闻社区的核心业务需求进行梳理。本系统面向两类主要角色普通用户和管理员。2.1 用户端功能需求用户注册与登录支持邮箱或用户名注册登录后维护个人会话状态。新闻浏览按分类、热度、时间等维度浏览新闻列表支持关键词搜索。新闻详情与评论查看新闻全文发表评论回复他人评论并对评论进行点赞。互动功能对新闻进行点赞、收藏关注其他用户查看个人关注动态。个人中心管理个人资料、查看我的发布、我的收藏、我的评论记录。2.2 管理员端功能需求新闻管理发布、编辑、下架新闻内容支持富文本编辑和封面图上传。分类管理维护新闻分类调整分类排序与状态。用户管理查看用户列表禁用违规账号分配管理员角色。评论审核删除违规评论维护社区氛围。数据统计查看新闻发布量、用户增长量、热门新闻排行等基础统计信息。3. 技术栈选型本系统采用前后端分离的开发模式后端基于 Spring Boot 构建 RESTful API前端使用 Vue.js 配合 Element UI 组件库实现页面交互。整体技术栈选型如下层次技术选型说明后端框架Spring Boot 2.7.x快速构建独立运行的 Spring 应用简化配置持久层框架MyBatis-Plus增强 MyBatis提供通用 CRUD、分页插件减少样板代码数据库MySQL 8.0存储用户、新闻、评论、分类等核心业务数据缓存Redis缓存热点新闻、验证码、用户会话提升并发读取性能安全认证Spring Security JWT实现无状态认证与接口权限控制搜索引擎Elasticsearch可选提供全文检索能力优化新闻搜索体验前端框架Vue.js 3 Element Plus构建单页应用组件化开发提升开发效率构建工具Maven / npm后端依赖管理与前端工程化构建部署Docker Nginx容器化部署Nginx 反向代理与静态资源托管在架构设计上后端采用经典的分层架构Controller 层负责接收请求与参数校验Service 层承载业务逻辑Mapper 层DAO负责数据库交互Entity 层映射数据表结构。层与层之间通过接口解耦便于单元测试与后续扩展。4. 数据库设计根据需求分析系统核心数据表包括用户表、新闻表、分类表、评论表、点赞表、收藏表和关注表。下面给出主要表结构的简要设计。4.1 用户表user字段名类型说明idbigint主键自增usernamevarchar(50)用户名唯一passwordvarchar(100)加密后的密码nicknamevarchar(50)昵称avatarvarchar(255)头像 URLroletinyint角色0 普通用户1 管理员statustinyint状态0 正常1 禁用create_timedatetime注册时间4.2 新闻表news字段名类型说明idbigint主键自增category_idbigint所属分类 IDtitlevarchar(200)新闻标题summaryvarchar(500)新闻摘要contenttext新闻正文富文本 HTMLcover_imagevarchar(255)封面图 URLauthor_idbigint发布者 IDview_countint浏览量like_countint点赞数statustinyint状态0 草稿1 已发布2 已下架create_timedatetime发布时间4.3 评论表comment字段名类型说明idbigint主键自增news_idbigint所属新闻 IDuser_idbigint评论用户 IDparent_idbigint父评论 ID0 表示顶级评论contentvarchar(1000)评论内容like_countint点赞数create_timedatetime评论时间此外点赞表news_like、收藏表news_favorite和关注表user_follow均采用用户 ID 目标 ID的联合唯一索引设计用于记录用户与新闻、用户与用户之间的互动关系。分类表category结构较为简单包含分类名称、排序号和状态字段。5. 核心功能实现本节选取系统中几个具有代表性的核心功能给出关键代码实现与设计思路。5.1 基于 JWT 的用户认证系统采用 Spring Security 结合 JWT 实现无状态认证。用户登录成功后后端签发一个包含用户 ID 和角色信息的 JWT Token前端在后续请求中通过 Authorization 请求头携带该 Token。后端通过过滤器解析 Token 并设置安全上下文从而识别当前登录用户。首先定义一个 JWT 工具类负责 Token 的生成与解析import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expiration}) private Long expiration; /** 生成 JWT Token */ public String generateToken(Long userId, String username, Integer role) { Maplt;String, Objectgt; claims new HashMaplt;gt;(); claims.put(userId, userId); claims.put(username, username); claims.put(role, role); Date now new Date(); Date expireDate new Date(now.getTime() expiration * 1000); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } /** 解析 Token获取 Claims */ public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } /** 判断 Token 是否过期 */ public boolean isTokenExpired(String token) { return parseToken(token).getExpiration().before(new Date()); } }接下来编写 JWT 认证过滤器在请求进入 Controller 之前完成 Token 校验与用户身份注入import io.jsonwebtoken.Claims; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Autowired private JwtUtil jwtUtil; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token getTokenFromRequest(request); if (StringUtils.hasText(token) amp;amp; SecurityContextHolder.getContext().getAuthentication() null) { try { Claims claims jwtUtil.parseToken(token); Long userId claims.get(userId, Long.class); String username claims.get(username, String.class); Integer role claims.get(role, Integer.class); // 构造认证信息并放入安全上下文 UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); // 将 userId 和 role 存入 request 属性便于业务层获取 request.setAttribute(userId, userId); request.setAttribute(role, role); } catch (Exception e) { // Token 无效或过期不设置认证信息由后续安全规则处理 logger.warn(JWT 解析失败: e.getMessage()); } } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { String bearerToken request.getHeader(Authorization); if (StringUtils.hasText(bearerToken) amp;amp; bearerToken.startsWith(Bearer )) { return bearerToken.substring(7); } return null; } }在 SecurityConfig 中配置放行规则注册过滤器并指定密码加密方式import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; Configuration EnableWebSecurity public class SecurityConfig { Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/, /api/news/, /api/category/).permitAll() .antMatchers(/api/admin/).hasRole(ADMIN) .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }5.2 新闻发布与分页查询新闻发布是管理员的核心操作。Controller 接收前端提交的新闻表单数据Service 层完成数据校验、封面图处理和数据库写入。这里使用 MyBatis-Plus 提供的 IService 简化 CRUD 操作。import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; Service public class NewsServiceImpl extends ServiceImplNewsMapper, News implements NewsService { Autowired private NewsMapper newsMapper; Override Transactional(rollbackFor Exception.class) public boolean publishNews(News news, Long authorId) { news.setAuthorId(authorId); news.setStatus(1); // 1 表示已发布 news.setViewCount(0); news.setLikeCount(0); return save(news); } Override public PageResultlt;NewsVOgt; getNewsPage(int page, int size, Long categoryId, String keyword) { Pagelt;Newsgt; pageParam new Pagelt;gt;(page, size); LambdaQueryWrapperlt;Newsgt; wrapper new LambdaQueryWrapperlt;gt;(); if (categoryId ! null) { wrapper.eq(News::getCategoryId, categoryId); } if (StringUtils.hasText(keyword)) { wrapper.like(News::getTitle, keyword).or().like(News::getSummary, keyword); } wrapper.eq(News::getStatus, 1).orderByDesc(News::getCreateTime); Pagelt;Newsgt; result newsMapper.selectPage(pageParam, wrapper); // 转换为 VO补充分类名称和作者昵称等展示字段 Listlt;NewsVOgt; voList result.getRecords().stream().map(news -gt; { NewsVO vo new NewsVO(); BeanUtils.copyProperties(news, vo); return vo; }).collect(Collectors.toList()); return new PageResultlt;gt;(voList, result.getTotal()); } }对应的 Controller 层代码如下