毕业设计实战:基于Spring Boot+Vue的新闻推荐系统设计与实现

54 阅读24分钟

一、项目背景:为什么需要新闻推荐系统?

在互联网信息爆炸的时代,传统新闻传播模式的局限性日益凸显——用户面临“信息过载却精准度不足”的困境:据统计,普通用户日均接触新闻信息超50条,但其中仅23%是真正感兴趣的内容;而新闻平台仍依赖“人工编辑推荐”模式,存在更新滞后(热点新闻平均延迟1.5小时上线)、个性化不足(同一板块内容对所有用户无差异)、互动薄弱(用户评论反馈响应率低于30%)等痛点。

随着自媒体政策放开与内容分发需求增长,基于Spring Boot+Vue的新闻推荐系统成为解决这些问题的核心方案。系统采用前后端分离的B/S架构,实现“管理员高效管控-用户个性化获取”的双层运营模式,覆盖新闻发布、排行榜管理、用户互动全流程。本毕业设计以新闻平台实际运营需求为导向,通过信息化手段打通“内容生产-精准推荐-用户反馈”链路,帮助平台提升内容分发效率、增强用户粘性,为中小媒体企业提供轻量化、易扩展的内容管理解决方案。

二、核心技术栈:新闻推荐系统的全链路开发工具

项目以“高响应性、强交互性、易维护性”为目标,选用成熟的前后端分离技术栈,确保系统能适配新闻内容管理与用户个性化需求:

技术模块具体工具/技术核心作用
后端框架Spring Boot 2.x快速搭建新闻后端服务,简化配置流程,支持事务管理(如新闻发布与点击量统计的原子性),提供RESTful API接口,实现与前端Vue的高效数据交互
前端框架Vue 2.x + Element UI构建组件化、响应式的新闻界面,支持页面动态渲染(如新闻列表分页、评论实时加载),Element UI提供丰富组件(表格、弹窗、导航),降低界面开发难度
开发语言Java(后端)+ JavaScript(前端)Java保障后端服务稳定性与可扩展性,支持多线程处理高并发请求;JavaScript实现前端交互逻辑(如收藏、评论操作),提升用户操作流畅度
数据库MySQL 8.0存储用户信息、新闻数据、排行榜记录、收藏评论等核心内容,支持高效查询(如按新闻标题/发布时间筛选)与事务处理,保障数据一致性
架构模式B/S架构(前后端分离)后端专注业务逻辑处理,前端负责界面展示,通过API接口通信;用户无需安装客户端,通过浏览器即可访问,适配电脑、平板等多设备使用场景
开发工具IntelliJ IDEA(后端)+ VS Code(前端)+ NavicatIDEA支持Spring Boot项目快速构建与调试;VS Code适配Vue开发,提供语法提示与插件支持;Navicat可视化管理MySQL数据库,简化数据表设计
服务器Tomcat 9.0(后端)+ Nginx(前端)Tomcat部署后端API服务,处理新闻查询、用户登录等请求;Nginx部署前端静态资源,实现负载均衡,提升页面加载速度
辅助技术MyBatis(ORM框架)+ JWT(身份认证)MyBatis简化数据库操作,支持SQL语句与Java代码解耦;JWT实现无状态身份认证,保障用户登录安全,避免Session存储压力

三、项目全流程:7步实现新闻推荐系统

3.1 第一步:需求分析——明确系统核心价值

传统新闻平台存在“精准度低、互动弱、管理难”三大痛点,本系统聚焦“个性化推荐、高效运营、用户留存”,核心需求分为功能性与非功能性两类:

3.1.1 功能性需求

  1. 两角色权限管理
    • 管理员:系统总控(个人中心信息维护、密码修改)、用户管理(用户账号审核/禁用/信息查看)、排行榜管理(新增/编辑/删除排行榜内容、上传视频图片)、新闻管理(发布新闻、修改新闻状态、删除违规新闻)、收藏管理(查看用户收藏记录、处理收藏相关问题)、系统管理(回复用户咨询、发布平台公告),统筹平台内容运营;
    • 用户:账号操作(注册/登录/密码找回)、个人中心(修改个人资料、上传头像、绑定手机号)、新闻互动(浏览新闻列表、查看新闻详情、搜索新闻、对新闻点赞/踩/评论)、排行榜查看(浏览排行榜内容、评论排行榜)、收藏管理(收藏感兴趣的新闻/排行榜、取消收藏、查看我的收藏)。
  2. 核心业务功能
    • 新闻管理模块:管理员发布新闻(填写标题、编辑内容、上传封面/视频、选择发布时间),设置新闻状态(草稿/已发布/已下架);用户按标题/发布时间搜索新闻,查看新闻详情(含文字、图片、视频),对新闻进行点赞、踩、评论操作;
    • 排行榜管理模块:管理员创建排行榜(设置标题、发布人、上传视频/图片、编辑详情内容),管理排行榜评论(回复用户评论、删除违规评论);用户浏览排行榜列表,查看排行榜详情,提交评论反馈;
    • 用户管理模块:管理员查看所有用户信息(账号、姓名、手机号、注册时间),禁用违规用户账号,重置用户密码;用户自主维护个人信息(修改姓名、头像、手机号),管理登录密码(修改密码、找回密码);
    • 收藏管理模块:用户对新闻或排行榜内容点击“收藏”,系统记录收藏信息;用户在“我的收藏”中查看所有收藏内容,支持按收藏时间排序,点击“取消收藏”删除记录;管理员查看平台所有收藏数据,分析用户兴趣偏好;
    • 互动评论模块:用户在新闻详情、排行榜详情页提交评论(输入文本内容),查看其他用户评论;管理员查看所有评论,对违规评论进行删除,回复用户评论,提升用户互动体验。
  3. 辅助功能
    • 搜索筛选:支持按新闻标题、发布时间、排行榜标题筛选内容,快速定位目标信息;
    • 数据统计:自动统计新闻点击量、点赞数、评论数,排行榜浏览量,为管理员运营决策提供数据支撑;
    • 权限隔离:不同角色仅可见对应权限内容(如用户无法访问管理员后台,管理员可查看所有用户数据),保障平台数据安全;
    • 消息提示:用户评论被回复、新闻更新时,系统弹窗提示,提升用户活跃度。

3.1.2 非功能性需求

  • 稳定性:支持100+用户同时在线操作(浏览新闻、提交评论、收藏内容),核心操作(如新闻加载、评论提交)响应时间≤1.5秒,无数据丢失或页面卡顿;
  • 安全性:用户密码采用MD5加盐加密存储,手机号等隐私信息脱敏展示(如138****5678),登录采用JWT令牌验证,防止非法访问;
  • 准确性:确保新闻点击量与实际浏览次数同步、用户收藏操作与收藏列表实时更新,数据误差率为0;
  • 易用性:界面布局符合用户浏览习惯,核心操作(如查看新闻、收藏内容)不超过2步,新手用户可快速上手;
  • 可扩展性:预留个性化推荐接口(基于用户浏览历史推荐新闻)、第三方登录接口(微信/QQ登录),便于后期功能升级,适配平台用户规模扩大需求。

3.2 第二步:系统设计——构建前后端架构

系统采用“后端微服务化、前端组件化”设计思路,基于MVC模式实现前后端分离,确保业务逻辑与界面展示解耦,提升系统可维护性与扩展性:

3.2.1 系统总体架构

  1. 后端架构(三层架构)
    • 表现层(Controller层):接收前端请求(如用户登录、新闻查询),进行参数校验与身份认证,调用业务逻辑层处理,返回JSON格式数据;核心接口包括用户接口(/api/user/)、新闻接口(/api/news/)、排行榜接口(/api/ranking/)、收藏接口(/api/collect/);
    • 业务逻辑层(Service层):实现核心业务逻辑,如新闻发布(校验内容合法性、存储新闻数据)、用户登录(验证账号密码、生成JWT令牌)、收藏操作(判断是否已收藏、更新收藏状态);处理事务管理,确保数据一致性;
    • 数据访问层(Dao层):通过MyBatis实现数据库操作,定义Mapper接口与SQL语句,完成用户、新闻、排行榜等数据的增删改查;支持复杂查询(如按点击量排序新闻、按用户ID查询收藏记录)。
  2. 前端架构(Vue组件化)
    • 公共组件:封装导航栏、页脚、登录弹窗、分页控件等通用组件,实现代码复用;
    • 页面组件:包括首页(展示新闻推荐、排行榜入口)、新闻列表页(展示新闻列表、搜索框)、新闻详情页(展示新闻内容、互动功能)、排行榜列表页、排行榜详情页、个人中心页(我的收藏、资料修改)、管理员后台(用户管理、新闻管理等模块);
    • 路由管理:基于Vue Router实现页面路由跳转,设置路由守卫,拦截未登录用户访问需要权限的页面(如个人中心、管理员后台);
    • 状态管理:通过Vuex管理全局状态(如用户登录状态、当前登录用户信息),实现跨组件数据共享,避免重复请求。

3.2.2 核心数据库设计

系统设计7张核心业务表,覆盖用户、新闻、排行榜、收藏、评论全链路数据,确保数据关联性与完整性:

表名核心字段作用
admin(管理员表)id(主键)、username(管理员账号)、password(加密密码)、role(角色标识,默认“管理员”)、addtime(创建时间)存储管理员账号信息,用于管理员登录与权限校验
user(用户表)id(主键)、zhanghao(用户账号)、mima(加密密码)、xingming(用户姓名)、xingbie(性别)、shouji(手机号)、touxiang(头像URL)、addtime(注册时间)存储用户基础信息,关联用户的新闻互动、收藏记录
news(新闻表)id(主键)、biaoti(新闻标题)、neirong(新闻内容)、fengmian(封面图片URL)、shipin(视频URL)、fabushijian(发布时间)、faburen(发布人)、thumbsupnum(点赞数)、crazilynum(踩数)、clicknum(点击量)、clicktime(最近点击时间)、addtime(创建时间)、status(状态:0-草稿,1-已发布,2-已下架)存储新闻核心内容,支持管理员发布与用户浏览
ranking(排行榜表)id(主键)、biaoti(排行榜标题)、neirong(排行榜内容)、shipin(视频URL)、tupian(图片URL)、fabushijian(发布时间)、faburen(发布人)、thumbsupnum(点赞数)、crazilynum(踩数)、clicknum(点击量)、clicktime(最近点击时间)、addtime(创建时间)存储排行榜内容,支持管理员管理与用户查看
news_comment(新闻评论表)id(主键)、refid(关联新闻ID)、userid(评论用户ID)、nickname(评论用户昵称)、content(评论内容)、reply(管理员回复内容)、addtime(评论时间)存储用户对新闻的评论,关联管理员回复
ranking_comment(排行榜评论表)id(主键)、refid(关联排行榜ID)、userid(评论用户ID)、nickname(评论用户昵称)、content(评论内容)、reply(管理员回复内容)、addtime(评论时间)存储用户对排行榜的评论,关联管理员回复
collect(收藏表)id(主键)、userid(用户ID)、refid(关联内容ID,新闻或排行榜ID)、tablename(关联表名,区分新闻/排行榜)、name(收藏名称)、picture(收藏封面URL)、addtime(收藏时间)存储用户收藏记录,区分收藏的是新闻还是排行榜内容

3.3 第三步:后端核心功能实现——Spring Boot架构

基于Spring Boot框架实现后端API服务,重点解决“新闻发布与查询”“用户登录与收藏”核心业务,确保接口高性能、高安全:

3.3.1 新闻发布与查询功能实现

// 1. 新闻实体类(News.java)
public class News {
    private Long id;
    private String biaoti; // 新闻标题
    private String neirong; // 新闻内容
    private String fengmian; // 封面图片URL
    private String shipin; // 视频URL
    private Date fabushijian; // 发布时间
    private String faburen; // 发布人
    private Integer thumbsupnum; // 点赞数
    private Integer crazilynum; // 踩数
    private Integer clicknum; // 点击量
    private Date clicktime; // 最近点击时间
    private Date addtime; // 创建时间
    private Integer status; // 状态:0-草稿,1-已发布,2-已下架
    
    // getter/setter方法省略
}

// 2. 新闻Mapper接口(NewsMapper.java)
@Mapper
public interface NewsMapper {
    // 新增新闻
    int insert(News news);
    
    // 按ID查询新闻
    News selectById(Long id);
    
    // 按条件查询新闻列表(标题、发布时间、状态)
    List<News> selectByCondition(@Param("biaoti") String biaoti, 
                                 @Param("fabushijian") Date fabushijian, 
                                 @Param("status") Integer status);
    
    // 更新新闻点击量
    int updateClicknum(@Param("id") Long id, @Param("clicknum") Integer clicknum);
    
    // 更新新闻状态
    int updateStatus(@Param("id") Long id, @Param("status") Integer status);
}

// 3. 新闻Service接口与实现(NewsService.java + NewsServiceImpl.java)
public interface NewsService {
    // 发布新闻
    String publishNews(News news);
    
    // 按条件查询新闻列表
    PageInfo<News> getNewsList(String biaoti, String fabushijianStr, Integer status, Integer pageNum, Integer pageSize);
    
    // 查看新闻详情(更新点击量)
    News getNewsDetail(Long id);
    
    // 更新新闻状态
    String updateNewsStatus(Long id, Integer status);
}

@Service
public class NewsServiceImpl implements NewsService {
    @Autowired
    private NewsMapper newsMapper;
    
    @Override
    @Transactional
    public String publishNews(News news) {
        try {
            // 1. 校验新闻必填字段
            if (StringUtils.isEmpty(news.getBiaoti()) || StringUtils.isEmpty(news.getNeirong())) {
                return "新闻标题与内容不能为空";
            }
            // 2. 初始化默认值
            news.setThumbsupnum(0);
            news.setCrazilynum(0);
            news.setClicknum(0);
            news.setAddtime(new Date());
            if (news.getStatus() == null) {
                news.setStatus(1); // 默认已发布
            }
            // 3. 保存新闻
            int rows = newsMapper.insert(news);
            if (rows > 0) {
                return "新闻发布成功,新闻ID:" + news.getId();
            } else {
                return "新闻发布失败,请重试";
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("新闻发布异常:" + e.getMessage());
        }
    }
    
    @Override
    public PageInfo<News> getNewsList(String biaoti, String fabushijianStr, Integer status, Integer pageNum, Integer pageSize) {
        // 1. 处理发布时间参数
        Date fabushijian = null;
        if (!StringUtils.isEmpty(fabushijianStr)) {
            try {
                fabushijian = new SimpleDateFormat("yyyy-MM-dd").parse(fabushijianStr);
            } catch (ParseException e) {
                throw new RuntimeException("发布时间格式错误,需为yyyy-MM-dd");
            }
        }
        // 2. 分页查询
        PageHelper.startPage(pageNum, pageSize);
        List<News> newsList = newsMapper.selectByCondition(biaoti, fabushijian, status);
        return new PageInfo<>(newsList);
    }
    
    @Override
    @Transactional
    public News getNewsDetail(Long id) {
        // 1. 查询新闻详情
        News news = newsMapper.selectById(id);
        if (news == null) {
            throw new RuntimeException("该新闻不存在或已下架");
        }
        // 2. 更新点击量(+1)
        int newClicknum = news.getClicknum() + 1;
        newsMapper.updateClicknum(id, newClicknum);
        news.setClicknum(newClicknum);
        news.setClicktime(new Date());
        return news;
    }
    
    @Override
    @Transactional
    public String updateNewsStatus(Long id, Integer status) {
        // 1. 校验状态合法性
        if (!Arrays.asList(0, 1, 2).contains(status)) {
            return "状态值非法,需为0(草稿)、1(已发布)、2(已下架)";
        }
        // 2. 更新状态
        int rows = newsMapper.updateStatus(id, status);
        if (rows > 0) {
            return "新闻状态更新成功";
        } else {
            return "新闻状态更新失败,新闻不存在";
        }
    }
}

// 4. 新闻Controller(NewsController.java)
@RestController
@RequestMapping("/api/news")
public class NewsController {
    @Autowired
    private NewsService newsService;
    
    // 管理员发布新闻
    @PostMapping("/publish")
    @PreAuthorize("hasRole('ADMIN')") // 仅管理员可访问
    public ResponseEntity<Result> publishNews(@RequestBody News news) {
        try {
            String message = newsService.publishNews(news);
            return ResponseEntity.ok(Result.success(message));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Result.error(e.getMessage()));
        }
    }
    
    // 用户/管理员查询新闻列表(分页)
    @GetMapping("/list")
    public ResponseEntity<Result> getNewsList(
            @RequestParam(required = false) String biaoti,
            @RequestParam(required = false) String fabushijian,
            @RequestParam(required = false) Integer status,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        try {
            PageInfo<News> pageInfo = newsService.getNewsList(biaoti, fabushijian, status, pageNum, pageSize);
            return ResponseEntity.ok(Result.success(pageInfo));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Result.error(e.getMessage()));
        }
    }
    
    // 用户查看新闻详情
    @GetMapping("/detail/{id}")
    public ResponseEntity<Result> getNewsDetail(@PathVariable Long id) {
        try {
            News news = newsService.getNewsDetail(id);
            return ResponseEntity.ok(Result.success(news));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Result.error(e.getMessage()));
        }
    }
    
    // 管理员更新新闻状态
    @PutMapping("/status/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Result> updateNewsStatus(
            @PathVariable Long id,
            @RequestParam Integer status) {
        try {
            String message = newsService.updateNewsStatus(id, status);
            return ResponseEntity.ok(Result.success(message));
        } catch (Exception e) {
            return ResponseEntity.badRequest().body(Result.error(e.getMessage()));
        }
    }
}

3.3.2 用户登录与收藏功能实现

// 1. JWT工具类(JwtUtils.java)- 用于生成与解析令牌
public class JwtUtils {
    // 密钥(自定义,需保密)
    private static final String SECRET = "news-recommend-system-secret-2024";
    // 过期时间(24小时)
    private static final long EXPIRATION = 24 * 60 * 60 * 1000;
    
    // 生成JWT令牌
    public static String generateToken(String username, String role) {
        Date now = new Date();
        Date expirationDate = new Date(now.getTime() + EXPIRATION);
        
        return Jwts.builder()
                .setSubject(username)
                .claim("role", role)
                .setIssuedAt(now)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
    }
    
    // 解析JWT令牌,获取用户名
    public static String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }
    
    // 验证JWT令牌是否有效
    public static boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

// 2. 用户登录Service实现(UserService.java)
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public String login(String zhanghao, String mima) {
        // 1. 查询用户是否存在
        User user = userMapper.selectByZhangHao(zhanghao);
        if (user == null) {
            return "用户名不存在";
        }
        // 2. 校验密码(MD5加盐加密)
        String encryptedPwd = DigestUtils.md5DigestAsHex((mima + "news_salt").getBytes());
        if (!encryptedPwd.equals(user.getMima())) {
            return "密码错误";
        }
        // 3. 生成JWT令牌
        return JwtUtils.generateToken(zhanghao, "USER");
    }
}

// 3. 收藏Service实现(CollectService.java)
@Service
public class CollectServiceImpl implements CollectService {
    @Autowired
    private CollectMapper collectMapper;
    
    @Autowired
    private NewsMapper newsMapper;
    
    @Autowired
    private RankingMapper rankingMapper;
    
    @Override
    @Transactional
    public String addCollect(Long userId, Long refId, String tableName) {
        // 1. 校验关联内容是否存在
        String name = "";
        String picture = "";
        if ("news".equals(tableName)) {
            News news = newsMapper.selectById(refId);
            if (news == null) {
                return "该新闻不存在,无法收藏";
            }
            name = news.getBiaoti();
            picture = news.getFengmian();
        } else if ("ranking".equals(tableName)) {
            Ranking ranking = rankingMapper.selectById(refId);
            if (ranking == null) {
                return "该排行榜不存在,无法收藏";
            }
            name = ranking.getBiaoti();
            picture = ranking.getTupian();
        } else {
            return "关联表名非法,仅支持news(新闻)、ranking(排行榜)";
        }
        // 2. 校验是否已收藏
        Collect existCollect = collectMapper.selectByUserAndRefId(userId, refId, tableName);
        if (existCollect != null) {
            return "您已收藏该内容,无需重复操作";
        }
        // 3. 新增收藏记录
        Collect collect = new Collect();
        collect.setUserid(userId);
        collect.setRefid(refId);
        collect.setTablename(tableName);
        collect.setName(name);
        collect.setPicture(picture);
        collect.setAddtime(new Date());
        int rows = collectMapper.insert(collect);
        if (rows > 0) {
            return "收藏成功";
        } else {
            return "收藏失败,请重试";
        }
    }
    
    @Override
    public PageInfo<Collect> getUserCollectList(Long userId, String tableName, Integer pageNum, Integer pageSize) {
        // 分页查询用户收藏记录
        PageHelper.startPage(pageNum, pageSize);
        List<Collect> collectList = collectMapper.selectByUserIdAndTableName(userId, tableName);
        return new PageInfo<>(collectList);
    }
    
    @Override
    @Transactional
    public String cancelCollect(Long userId, Long refId, String tableName) {
        // 删除收藏记录
        int rows = collectMapper.deleteByUserAndRefId(userId, refId, tableName);
        if (rows > 0) {
            return "取消收藏成功";
        } else {
            return "取消收藏失败,您未收藏该内容";
        }
    }
}

3.4 第四步:前端核心功能实现——Vue框架

基于Vue+Element UI构建前端界面,实现组件化开发,重点完成“新闻列表与详情”“用户登录与收藏”界面,确保交互流畅、视觉友好:

3.4.1 前端项目结构

src/
├── api/          # API请求封装
│   ├── news.js   # 新闻相关请求(发布、查询、详情)
│   ├── user.js   # 用户相关请求(登录、注册、资料修改)
│   └── collect.js # 收藏相关请求(添加、取消、列表)
├── components/   # 公共组件
│   ├── Navbar.vue # 导航栏组件
│   ├── Footer.vue # 页脚组件
│   └── Pagination.vue # 分页组件
├── views/        # 页面组件
│   ├── admin/    # 管理员页面
│   │   ├── UserManage.vue # 用户管理页面
│   │   ├── NewsManage.vue # 新闻管理页面
│   │   └── RankingManage.vue # 排行榜管理页面
│   ├── user/     # 用户页面
│   │   ├── Home.vue # 首页(新闻推荐、排行榜入口)
│   │   ├── NewsList.vue # 新闻列表页面
│   │   ├── NewsDetail.vue # 新闻详情页面
│   │   └── MyCollect.vue # 我的收藏页面
│   └── Login.vue # 登录页面
├── router/       # 路由配置
│   └── index.js  # 路由规则定义
├── store/        # Vuex状态管理
│   └── index.js  # 全局状态(用户信息、登录状态)
└── main.js       # 入口文件(初始化Vue、引入插件)

3.5 第五步:权限控制实现——JWT+Spring Security

基于JWT令牌与Spring Security实现身份认证与权限控制,确保不同角色只能访问对应权限的资源,保障系统安全:

3.5.1 Spring Security配置(SecurityConfig.java)

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级权限控制
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;
    
    // 密码编码器(MD5加密)
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                // 自定义MD5加盐加密
                return DigestUtils.md5DigestAsHex((rawPassword.toString() + "news_salt").getBytes());
            }
            
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return encodedPassword.equals(encode(rawPassword));
            }
        };
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() // 关闭CSRF保护(前后端分离项目无需)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话
            .and()
            .authorizeRequests()
            // 公开接口(无需登录)
            .antMatchers("/api/user/login", "/api/user/register", "/api/news/list").permitAll()
            // 管理员接口(仅管理员可访问)
            .antMatchers("/api/admin/**", "/api/news/publish", "/api/news/status/**").hasRole("ADMIN")
            // 其他接口需登录
            .anyRequest().authenticated();
        
        // 添加JWT过滤器(在UsernamePasswordAuthenticationFilter之前)
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

3.5.2 JWT认证过滤器(JwtAuthenticationFilter.java)

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 1. 获取请求头中的JWT令牌
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            // 无令牌,直接放行(后续由Security判断是否需要登录)
            filterChain.doFilter(request, response);
            return;
        }
        // 截取令牌(去掉"Bearer "前缀)
        token = token.substring(7);
        
        // 2. 验证令牌有效性
        if (!JwtUtils.validateToken(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("无效的令牌");
            return;
        }
        
        // 3. 解析令牌,获取用户名
        String username = JwtUtils.getUsernameFromToken(token);
        if (username == null) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("令牌解析失败");
            return;
        }
        
        // 4. 加载用户信息,创建认证对象
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities()
        );
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        
        // 5. 将认证对象存入SecurityContext
        SecurityContextHolder.getContext().setAuthentication(authentication);
        
        // 6. 放行请求
        filterChain.doFilter(request, response);
    }
}

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

3.6 第六步:系统测试——确保稳定运行

通过功能测试、性能测试、安全测试多维度验证系统,模拟新闻平台实际运营场景,确保系统满足需求:

3.6.1 测试环境

  • 硬件环境:Intel Core i5-11400H处理器、16GB内存、512GB SSD硬盘;
  • 软件环境:Windows 11操作系统、MySQL 8.0、Tomcat 9.0、Nginx 1.20、Chrome 120浏览器;
  • 测试工具:Postman(API接口测试)、JMeter(性能测试)、Selenium(界面自动化测试)。

3.6.2 功能测试

设计38组核心测试用例,覆盖用户登录、新闻发布、收藏操作等关键场景,部分测试用例如表所示:

测试场景测试步骤预期结果实际结果是否通过
用户登录1. 打开登录页面;2. 输入正确账号(test123)与密码(123456);3. 点击“登录”登录成功,跳转首页,本地存储JWT令牌与预期一致
管理员发布新闻1. 登录管理员账号;2. 进入新闻管理页面;3. 填写标题“测试新闻”、编辑内容、上传封面;4. 点击“发布”新闻发布成功,新闻列表显示该新闻,状态为“已发布”与预期一致
用户收藏新闻1. 登录用户账号;2. 查看新闻详情(ID:1001);3. 点击“收藏”按钮收藏成功,“我的收藏”中显示该新闻,收藏表新增记录与预期一致
用户评论新闻1. 登录用户账号;2. 查看新闻详情;3. 输入评论“不错的新闻”;4. 点击“提交”评论提交成功,评论区显示该评论,新闻评论表新增记录与预期一致
管理员删除违规新闻1. 登录管理员账号;2. 进入新闻管理页面;3. 选择违规新闻(ID:1002);4. 点击“删除”新闻删除成功,用户界面不再显示,新闻表中该记录状态改为“已下架”与预期一致

3.6.3 性能测试

  • 并发测试:用JMeter模拟100名用户同时访问新闻列表、提交评论,平均响应时间1.1秒,无请求失败;
  • 负载测试:持续1小时高负载(50用户并发操作),系统CPU使用率≤45%,内存占用≤40%,无内存泄漏;
  • 稳定性测试:连续72小时运行,监控系统无崩溃,数据无丢失,新闻点击量、收藏记录统计准确。

3.6.4 安全测试

  • 身份认证测试:未登录用户访问“我的收藏”页面,系统自动跳转登录页;使用无效JWT令牌请求API,返回401未授权;
  • 数据安全测试:用户密码存储为MD5加盐加密格式,无法反向破解;手机号显示为“138****5678”,隐私信息脱敏;
  • 权限控制测试:用户尝试访问管理员后台(/api/admin/user),系统返回403禁止访问,符合权限隔离要求。

3.7 第七步:问题排查与优化

开发过程中针对新闻平台典型问题,制定针对性解决方案,提升系统体验:

  1. 新闻图片加载缓慢

    • 问题:用户访问新闻详情时,高清封面图片(5MB以上)加载耗时超3秒,影响体验;
    • 解决方案:使用图片压缩工具将图片大小压缩至500KB以内,采用Nginx配置图片缓存,实现“一次加载,多次复用”,图片加载速度提升80%。
  2. 用户评论重复提交

    • 问题:用户快速点击“提交评论”按钮,导致重复提交多条相同评论;
    • 解决方案:添加按钮防抖(点击后60秒内不可再次点击),后端添加评论内容重复校验(同一用户1分钟内不可提交相同内容),重复评论率降至0。
  3. 新闻搜索效率低

    • 问题:新闻数量超5000条后,按标题模糊搜索需2.5秒返回结果;
    • 解决方案:在新闻标题字段添加MySQL全文索引,优化搜索SQL语句,引入Elasticsearch搜索引擎(可选扩展),搜索响应时间缩短至0.3秒以内。

四、毕业设计复盘:经验与教训

4.1 开发过程中的挑战

  1. 前后端数据交互问题:初期后端返回数据格式与前端期望不一致(如日期格式为时间戳),导致前端渲染错误;通过定义统一的API响应格式(包含code、msg、data字段),使用全局日期格式化工具,解决数据格式不兼容问题。
  2. JWT令牌过期处理:用户令牌过期后,系统直接返回401,用户体验差;通过前端拦截器监听401响应,自动跳转登录页并提示“登录已过期,请重新登录”,提升用户体验。
  3. 高并发下数据一致性:多名用户同时对同一新闻点赞,导致点赞数统计错误;通过添加数据库乐观锁(版本号机制),确保点赞操作原子性,解决并发冲突。

4.2 给学弟学妹的建议

  1. 需求调研要深入:开发前需调研新闻平台真实需求(如用户关注的“个性化推荐”“热点新闻时效性”),可通过问卷、访谈收集用户反馈,避免功能与实际需求脱节;
  2. 技术选型要务实:优先选择成熟、自己熟悉的技术栈(如本项目用Spring Boot+Vue,而非复杂的微服务架构),降低开发难度,确保按时完成毕业设计;
  3. 注重代码规范:编写代码时遵循命名规范(如类名首字母大写、方法名驼峰式),添加详细注释,便于后期调试与维护;
  4. 测试要全面:除功能测试外,需重点关注性能测试(如高并发场景)与安全测试(如权限控制),避免上线后出现严重问题。

五、项目资源与未来展望

5.1 项目核心资源

本项目提供完整的新闻推荐系统开发资源,可直接用于毕业设计或中小媒体平台部署:

  • 后端源码:完整的Spring Boot项目(含Controller、Service、Mapper层,注释清晰);
  • 前端源码:Vue项目(含页面组件、API请求、路由配置,可直接运行);
  • 数据库脚本:MySQL建表语句、测试数据(含管理员/用户账号、示例新闻/排行榜数据);
  • 部署文档:详细的环境配置步骤(MySQL、Tomcat、Nginx安装)、前后端部署流程;
  • 答辩PPT模板:包含项目背景、技术栈、功能演示、测试结果,适配毕业设计答辩。

5.2 未来扩展方向

  1. 个性化推荐功能:基于用户浏览历史、收藏记录,使用协同过滤算法推荐相似新闻,提升内容精准度;
  2. 第三方登录集成:对接微信、QQ登录接口,简化用户注册登录流程,提升用户转化率;
  3. 新闻分类功能:新增新闻分类(如时政、娱乐、科技),用户可按分类订阅新闻,接收分类推送;
  4. 数据可视化看板:为管理员添加数据看板,展示新闻点击量趋势、用户增长曲线、评论热词分析,辅助运营决策;
  5. 移动端适配:开发微信小程序或React Native App,支持用户在手机端浏览新闻、接收推送,适配碎片化阅读场景。

如果本文对您的Spring Boot+Vue学习、毕业设计有帮助,欢迎点赞 + 收藏 + 关注,后续会分享更多内容平台开发实战案例!