【博客系统】基于Spring全家桶的博客系统(下)


🎬 那我掉的头发算什么个人主页
🔥 个人专栏 : 《javaSE》《数据结构》《数据库》《javaEE》

⛺️待到苦尽甘来日


引言

在上一篇文章中,我们完成了项目的基础架构搭建,包括数据库设计、公共模块封装等。本篇将聚焦于核心业务代码的实现,从持久层开始,逐步实现用户登录、博客管理、权限控制等功能,最终完成一个可运行的博客系统

文章目录

持久层代码

持久层是整个系统的基石,负责与数据库进行交互。我们使用 MyBatis-Plus 来简化数据库操作,通过实体类映射数据库表,通过 Mapper 接口实现数据的增删改查。

实体类

实体类是数据库表在 Java 代码中的映射,我们将其放在 pojo.dataobject 包下,与数据库表一一对应。

java 复制代码
package com.hbu.springblogdemo.pojo.dataobject;


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

import java.time.LocalDateTime;
import java.util.Date;

@Data
public class BlogInfo {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    private Integer deleteFlag;
    private Date createTime;
    private LocalDateTime updateTime;
}

使用 @TableId(type = IdType.AUTO) 注解,指定主键为自增类型。

引入 deleteFlag 字段实现逻辑删除,避免物理删除数据,保证数据可恢复。

使用 Lombok 的 @Data 注解,自动生成 getter/setter 等方法,简化代码。

java 复制代码
package com.hbu.springblogdemo.pojo.dataobject;


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDate;

@Data
public class UserInfo {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String userName;
    private String password;
    private String githubUrl;
    private Integer deleteFlag;
    private LocalDate createTime;
    private LocalDate updateTime;
}

密码字段 password 存储的是加密后的密文,而非明文,保证安全性。

时间字段使用 LocalDate 类型,更符合 Java 8+ 的时间 API 规范。

Mapper

Mapper 接口继承 MyBatis-Plus 提供的 BaseMapper,即可获得单表的 CRUD 能力,无需编写繁琐的 SQL 语句。

java 复制代码
package com.hbu.springblogdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hbu.springblogdemo.pojo.dataobject.BlogInfo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface BlogMapper extends BaseMapper<BlogInfo> {
}
java 复制代码
package com.hbu.springblogdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hbu.springblogdemo.pojo.dataobject.UserInfo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<UserInfo> {
}

请求与响应模型

为了保证前后端交互的清晰和安全,我们对接口的输入和输出进行了严格的封装,分为请求类(Request)和响应类(Response)。

请求类(Request)

请求类用于接收前端传递的参数,并通过注解进行参数校验,确保数据的合法性。

用户登录请求

java 复制代码
package com.hbu.springblogdemo.pojo.request;

import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

@Data
public class UserLoginRequest {
    @NotNull(message = "用户名不能为空")
    @Length(max = 20)
    private String userName;
    
    @NotNull(message = "密码不能为空")
    @Length(min = 3, message = "密码不能小于3位")
    private String password;
}

发布博客请求

java 复制代码
package com.hbu.springblogdemo.pojo.request;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

@Data
public class AddBlogRequest {
    @NotNull(message = "用户ID不能为null")
    private Integer userId;
    
    @NotEmpty(message = "标题不能为空")
    private String title;
    
    @NotEmpty(message = "内容不能为空")
    private String content;
}

更新博客请求

java 复制代码
package com.hbu.springblogdemo.pojo.request;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

@Data
public class BlogInfoRequest {
    @NotNull(message = "博客ID不能为null")
    private Integer id;
    
    @NotEmpty(message = "标题不能为空")
    private String title;
    
    @NotEmpty(message = "内容不能为空")
    private String content;
}

响应类(Response)

响应类用于封装后端返回给前端的数据,统一数据结构,方便前端解析。

java 复制代码
package com.hbu.springblogdemo.pojo.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;

@Data
public class BlogInfoResponse {
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    
    // 格式化日期输出
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date createTime;
}
java 复制代码
package com.hbu.springblogdemo.pojo.response;

import lombok.Data;

@Data
public class UserInfoResponse {
    Integer id;
    String userName;
    String githubUrl;
}
java 复制代码
package com.hbu.springblogdemo.pojo.response;

import lombok.AllArgsConstructor;
import lombok.Data;

@AllArgsConstructor
@Data
public class UserLoginResponse {
    private Integer userId;
    // 登录成功后返回的JWT令牌
    private String token;
}

业务层代码

业务层是系统的核心,负责处理具体的业务逻辑,如登录验证、博客发布、权限检查等。它调用持久层进行数据操作,并对结果进行加工处理后返回给控制层。

博客服务(BlogService)

接口定义

java 复制代码
package com.hbu.springblogdemo.service;

import com.hbu.springblogdemo.pojo.dataobject.BlogInfo;
import com.hbu.springblogdemo.pojo.request.AddBlogRequest;
import com.hbu.springblogdemo.pojo.request.BlogInfoRequest;
import com.hbu.springblogdemo.pojo.response.BlogInfoResponse;
import jakarta.validation.constraints.NotNull;

import java.util.List;

public interface BlogService {
    // 获取博客详情
    BlogInfoResponse getBlogDetail(Integer blogId);
    // 获取博客列表
    List<BlogInfoResponse> getList();
    // 根据博客ID获取作者信息(内部调用)
    BlogInfo getAuthorInfo(Integer blogId);
    // 发布新博客
    Boolean addBlog(AddBlogRequest addBlogRequest);
    // 更新博客
    Boolean updateBlog(BlogInfoRequest blogInfoRequest);
    // 删除博客(逻辑删除)
    Boolean deleteBlog(@NotNull(message = "博客ID不能为NULL") Integer blogId);
}

实现类:

java 复制代码
package com.hbu.springblogdemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hbu.springblogdemo.common.exception.BlogException;
import com.hbu.springblogdemo.mapper.BlogMapper;
import com.hbu.springblogdemo.pojo.dataobject.BlogInfo;
import com.hbu.springblogdemo.pojo.request.AddBlogRequest;
import com.hbu.springblogdemo.pojo.request.BlogInfoRequest;
import com.hbu.springblogdemo.pojo.response.BlogInfoResponse;
import com.hbu.springblogdemo.service.BlogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
public class BlogServiceImpl implements BlogService {

    @Autowired
    private BlogMapper blogMapper;

    @Override
    public BlogInfoResponse getBlogDetail(Integer blogId) {
        // 构建查询条件:未删除且ID匹配
        QueryWrapper<BlogInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BlogInfo::getDeleteFlag, 0).eq(BlogInfo::getId, blogId);
        BlogInfo blogInfo = blogMapper.selectOne(queryWrapper);
        
        // 将DO转换为Response
        BlogInfoResponse blogInfoResponse = new BlogInfoResponse();
        blogInfoResponse.setId(blogInfo.getId());
        blogInfoResponse.setTitle(blogInfo.getTitle());
        blogInfoResponse.setContent(blogInfo.getContent());
        blogInfoResponse.setCreateTime(blogInfo.getCreateTime());
        blogInfoResponse.setUserId(blogInfo.getUserId());
        return blogInfoResponse;
    }

    @Override
    public List<BlogInfoResponse> getList() {
        // 查询所有未删除的博客
        QueryWrapper<BlogInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BlogInfo::getDeleteFlag, 0);
        List<BlogInfo> blogInfos = blogMapper.selectList(queryWrapper);

        // 将DO列表转换为Response列表
        List<BlogInfoResponse> blogInfoResponses = new ArrayList<>();
        for (BlogInfo blogInfo : blogInfos) {
            BlogInfoResponse blogInfoResponse = new BlogInfoResponse();
            blogInfoResponse.setId(blogInfo.getId());
            blogInfoResponse.setTitle(blogInfo.getTitle());
            blogInfoResponse.setContent(blogInfo.getContent());
            blogInfoResponse.setCreateTime(blogInfo.getCreateTime());
            blogInfoResponse.setUserId(blogInfo.getUserId());
            blogInfoResponses.add(blogInfoResponse);
        }
        return blogInfoResponses;
    }

    @Override
    public BlogInfo getAuthorInfo(Integer blogId) {
        // 内部方法,用于获取博客对应的作者ID
        QueryWrapper<BlogInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(BlogInfo::getDeleteFlag, 0)
                .eq(BlogInfo::getId, blogId);
        return blogMapper.selectOne(queryWrapper);
    }

    @Override
    public Boolean addBlog(AddBlogRequest addBlogRequest) {
        // 将Request转换为DO
        BlogInfo blogInfo = new BlogInfo();
        BeanUtils.copyProperties(addBlogRequest, blogInfo);
        try {
            // 插入数据库
            Integer result = blogMapper.insert(blogInfo);
            return result == 1;
        } catch (Exception e) {
            log.error("插入博客失败,e:", e);
            throw new BlogException("内部错误,请联系管理员");
        }
    }

    @Override
    public Boolean updateBlog(BlogInfoRequest blogInfoRequest) {
        BlogInfo blogInfo = new BlogInfo();
        BeanUtils.copyProperties(blogInfoRequest, blogInfo);
        try {
            // 根据ID更新
            Integer result = blogMapper.updateById(blogInfo);
            return result == 1;
        } catch (Exception e) {
            throw new BlogException("更新博客失败");
        }
    }

    @Override
    public Boolean deleteBlog(Integer blogId) {
        // 逻辑删除:更新deleteFlag为1
        BlogInfo blogInfo = new BlogInfo();
        blogInfo.setId(blogId);
        blogInfo.setDeleteFlag(1);
        try {
            Integer result = blogMapper.updateById(blogInfo);
            return result == 1;
        } catch (Exception e) {
            throw new BlogException("删除博客失败");
        }
    }
}

核心逻辑:

查询过滤:所有查询都加上 deleteFlag = 0 的条件,确保只操作有效数据。

数据转换:使用 BeanUtils.copyProperties 或手动赋值,将数据库实体(DO)转换为前端响应(Response),避免暴露敏感字段。

异常处理:捕获数据库操作异常,抛出自定义 BlogException,由全局异常处理器统一处理。

用户服务(UserService)

接口定义

java 复制代码
package com.hbu.springblogdemo.service;

import com.hbu.springblogdemo.pojo.request.UserLoginRequest;
import com.hbu.springblogdemo.pojo.response.UserInfoResponse;
import com.hbu.springblogdemo.pojo.response.UserLoginResponse;

public interface UserService {
    // 校验密码并登录
    UserLoginResponse checkPassword(UserLoginRequest userLoginRequest);
    // 根据用户ID获取用户信息
    UserInfoResponse getUserInfo(Integer userId);
    // 根据博客ID获取作者信息
    UserInfoResponse getAuthorInfo(Integer blogId);
}

实现类

java 复制代码
package com.hbu.springblogdemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hbu.springblogdemo.common.Utils.BeanTransUtils;
import com.hbu.springblogdemo.common.Utils.JwtUtils;
import com.hbu.springblogdemo.common.Utils.SecurityUtils;
import com.hbu.springblogdemo.common.exception.BlogException;
import com.hbu.springblogdemo.mapper.UserMapper;
import com.hbu.springblogdemo.pojo.dataobject.BlogInfo;
import com.hbu.springblogdemo.pojo.dataobject.UserInfo;
import com.hbu.springblogdemo.pojo.request.UserLoginRequest;
import com.hbu.springblogdemo.pojo.response.UserInfoResponse;
import com.hbu.springblogdemo.pojo.response.UserLoginResponse;
import com.hbu.springblogdemo.service.BlogService;
import com.hbu.springblogdemo.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Resource(name = "blogServiceImpl")
    private BlogService blogService;

    @Override
    public UserLoginResponse checkPassword(UserLoginRequest userLoginRequest) {
        // 1. 根据用户名查询用户
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(UserInfo::getDeleteFlag, 0)
                .eq(UserInfo::getUserName, userLoginRequest.getUserName());
        UserInfo userInfo = userMapper.selectOne(queryWrapper);
        
        if (userInfo == null) {
            throw new BlogException("用户不存在");
        }
        
        // 2. 校验密码(使用加密工具类验证)
        if (!SecurityUtils.verify(userLoginRequest.getPassword(), userInfo.getPassword())) {
            throw new BlogException("用户密码错误");
        }
        
        // 3. 生成JWT令牌
        Map<String, Object> map = new HashMap<>();
        map.put("userName", userLoginRequest.getUserName());
        String token = JwtUtils.genToken(map);
        
        // 4. 返回登录响应
        return new UserLoginResponse(userInfo.getId(), token);
    }

    @Override
    public UserInfoResponse getUserInfo(Integer userId) {
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(UserInfo::getDeleteFlag, 0)
                .eq(UserInfo::getId, userId);
        UserInfo userInfo = userMapper.selectOne(queryWrapper);
        // 使用工具类进行DO到Response的转换
        UserInfoResponse userInfoResponse = BeanTransUtils.trans(userInfo);
        return userInfoResponse;
    }

    @Override
    public UserInfoResponse getAuthorInfo(Integer blogId) {
        // 1. 先通过BlogService获取博客信息
        BlogInfo blogInfo = blogService.getAuthorInfo(blogId);
        if (blogInfo == null || blogInfo.getUserId() <= 0) {
            throw new BlogException("博客不存在");
        }
        // 2. 再根据用户ID获取作者信息
        return getUserInfo(blogInfo.getUserId());
    }
}

核心逻辑:

登录流程:查询用户 -> 校验密码 -> 生成 JWT 令牌 -> 返回响应。

密码安全:使用 SecurityUtils.verify 方法验证密码,确保密码在传输和存储过程中都是加密的。

服务间调用:getAuthorInfo 方法通过调用 BlogService 来获取博客对应的作者 ID,体现了服务的复用性。

控制层代码(Controller Layer)

控制层负责接收 HTTP 请求,调用相应的业务层方法,并将处理结果返回给前端。它是前后端交互的入口。

java 复制代码
package com.hbu.springblogdemo.controller;

import com.hbu.springblogdemo.pojo.request.AddBlogRequest;
import com.hbu.springblogdemo.pojo.request.BlogInfoRequest;
import com.hbu.springblogdemo.pojo.response.BlogInfoResponse;
import com.hbu.springblogdemo.service.BlogService;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/blog")
@Slf4j
public class BlogController {
    @Resource(name = "blogServiceImpl")
    private BlogService blogService;

    // 获取博客列表
    @RequestMapping("/getList")
    public List<BlogInfoResponse> getList() {
        log.info("获取博客列表......");
        return blogService.getList();
    }

    // 获取博客详情
    @RequestMapping("/getBlogDetail")
    public BlogInfoResponse getBlogDetail(@NotNull Integer blogId) {
        log.info("获取博客信息,博客ID:{}", blogId);
        return blogService.getBlogDetail(blogId);
    }

    // 发布博客
    @PostMapping("/add")
    public Boolean addBlog(@RequestBody @Validated AddBlogRequest addBlogRequest) {
        log.info("发布一篇新博客,作者ID:{},标题:{}", addBlogRequest.getUserId(), addBlogRequest.getTitle());
        return blogService.addBlog(addBlogRequest);
    }

    // 更新博客
    @PostMapping("/update")
    public Boolean update(@RequestBody @Validated BlogInfoRequest blogInfoRequest) {
        log.info("更新博客,博客ID:{},标题:{}", blogInfoRequest.getId(), blogInfoRequest.getTitle());
        return blogService.updateBlog(blogInfoRequest);
    }

    // 删除博客
    @RequestMapping("/delete")
    public Boolean delete(@NotNull(message = "博客ID不能为NULL") Integer blogId) {
        log.info("删除博客,博客ID:{}", blogId);
        return blogService.deleteBlog(blogId);
    }
}
java 复制代码
package com.hbu.springblogdemo.controller;

import com.hbu.springblogdemo.pojo.request.UserLoginRequest;
import com.hbu.springblogdemo.pojo.response.UserInfoResponse;
import com.hbu.springblogdemo.pojo.response.UserLoginResponse;
import com.hbu.springblogdemo.service.UserService;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
    @Resource(name = "userServiceImpl")
    private UserService userService;

    // 用户登录
    @PostMapping("/login")
    public UserLoginResponse login(@Validated @RequestBody UserLoginRequest userLoginRequest) {
        log.info("用户请求登录,用户名:{}", userLoginRequest.getUserName());
        return userService.checkPassword(userLoginRequest);
    }

    // 获取当前登录用户信息
    @RequestMapping("/getUserInfo")
    public UserInfoResponse getUserInfo(@NotNull(message = "用户ID不能为空") Integer userId) {
        log.info("获取用户信息,用户ID:{}", userId);
        return userService.getUserInfo(userId);
    }

    // 获取博客作者信息
    @RequestMapping("/getAuthorInfo")
    public UserInfoResponse getAuthorInfo(@NotNull(message = "博客ID不能为空") Integer blogId) {
        log.info("获取作者信息,博客ID:{}", blogId);
        return userService.getAuthorInfo(blogId);
    }
}

设计要点:

使用 @RestController 注解,表明这是一个 RESTful 控制器,所有方法的返回值都会自动序列化为 JSON。

使用 @Validated 和 @RequestBody 注解,对前端传递的 JSON 数据进行自动校验和绑定。

通过 @Slf4j 注解记录关键操作日志,便于问题排查。

核心功能实现详解

用户登录与 JWT 令牌

登录流程:用户提交用户名和密码 -> 控制器接收并校验 -> 服务层查询用户 -> 校验密码 -> 生成 JWT 令牌 -> 返回令牌和用户 ID。

JWT 作用:登录成功后,前端将 JWT 令牌存储在本地,后续请求(如发布博客)时在请求头中携带此令牌。后端通过拦截器验证令牌的有效性,实现无状态的身份认证。

博客 CRUD 操作

列表查询:查询所有未删除的博客,按创建时间倒序(可在 QueryWrapper 中添加 orderByCreateTime)。

详情查看:根据博客 ID 查询单条记录。

发布博客:接收前端数据,转换为 DO 插入数据库。

编辑博客:根据 ID 更新博客标题和内容。

删除博客:执行逻辑删除,将 deleteFlag 置为 1。

权限控制(强制登录)

通过实现一个 登录拦截器(LoginInterceptor),可以对 /blog/add、/blog/update 等需要权限的接口进行拦截,检查请求头中是否存在有效的 JWT 令牌。如果不存在或令牌无效,则拒绝访问,返回未登录错误。

java 复制代码
// 拦截器核心逻辑(伪代码)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String token = request.getHeader("token");
    if (token == null || !JwtUtils.validateToken(token)) {
        throw new BlogException("用户未登录");
    }
    return true;
}

博客系统(加盐)加密

在用户登录和注册环节,密码的安全存储是核心要点。我们通过「加盐 + MD5 加密」的方式存储密码,避免明文存储导致的安全风险,以下是核心工具类的实现与解析:

完整工具类代码:

java 复制代码
package com.hbu.springblogdemo.common.Utils;

import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.util.UUID;

/**
 * 密码加密与验证工具类(加盐MD5)
 * 核心:每个密码生成唯一盐值,加密后存储「盐值+密文」,验证时通过盐值反向校验
 */
public class SecurityUtils {
    /**
     * 密码加密(注册/修改密码时调用)
     * @param password 明文密码
     * @return 加密后的密码(32位盐值 + 32位MD5密文 = 64位字符串)
     */
    public static String encrypt(String password){
        // 1. 生成唯一盐值(UUID去除横线,32位随机字符串)
        String salt = UUID.randomUUID().toString().replace("-","");
        // 2. 盐值 + 明文密码 拼接后做MD5加密
        String secretPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
        // 3. 返回「盐值+密文」,方便验证时提取盐值
        return salt + secretPassword;
    }

    /**
     * 密码验证(登录时调用)
     * @param password 前端传入的明文密码
     * @param finalPassword 数据库存储的64位加密密码(盐值+密文)
     * @return 密码是否匹配
     */
    public static Boolean verify(String password,String finalPassword){
        // 校验参数合法性:密码非空 + 加密后密码长度必须为64位
        if(!StringUtils.hasLength(password)||finalPassword.length()!=64){
            return false;
        }
        // 1. 提取存储的盐值(前32位)
        String salt = finalPassword.substring(0,32);
        // 2. 用相同盐值加密前端传入的明文密码
        String securityPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
        // 3. 对比「盐值+新密文」与数据库存储的加密密码是否一致
        return (salt + securityPassword).equals(finalPassword);
    }
}

核心原理解析:

注册用户时:调用 encrypt() 方法加密明文密码,将返回的 64 位字符串存入数据库 user_info.password 字段;

登录验证时:调用 verify() 方法,用数据库存储的加密密码校验前端传入的明文密码(对应 UserServiceImpl 中 checkPassword 方法)。

安全优势

「加盐」避免彩虹表破解:即使两个用户密码相同,盐值不同则加密结果不同;

MD5 不可逆:无法从密文反推明文,即使数据库泄露,密码也无法被破解;

盐值与密文绑定:无需额外存储盐值,从 64 位密文中即可提取,简化存储逻辑。

总结

本次「基于 Spring 全家桶的博客系统」系列内容,从基础搭建到核心业务落地,完整实现了一个轻量且规范的博客系统。我们先完成了项目基础架构搭建,包括数据库设计、统一返回结果封装、全局异常处理等公共模块,为后续开发奠定了规范的架构基础;接着聚焦核心业务,基于 Spring Boot + MyBatis-Plus 实现了用户登录(JWT 鉴权 + MD5 加盐加密保障密码安全)、博客 CRUD(列表查询、发布、编辑、逻辑删除)等核心功能,覆盖了博客系统的核心使用场景。

整个开发过程遵循分层架构设计,通过 Controller、Service、Mapper 的职责拆分,配合 Request/Response 数据模型封装,保证了代码的可读性和可扩展性;同时在细节上兼顾安全性与健壮性,比如参数校验、密码加密、逻辑删除、异常兜底等设计,既贴合企业级开发规范,也能有效避免常见的系统漏洞。

目前系统已具备基础可用能力,后续可通过添加登录拦截器完善权限控制、对接前端页面完成交互、补充分页 / 评论等高级功能,或是将项目部署到服务器实现公网访问,进一步丰富系统的实用性。这个练手项目不仅能巩固 Spring 全家桶的核心用法,也能帮助理解 Java Web 开发中分层设计、安全防护等核心思想。

相关推荐
独自破碎E2 小时前
【面试真题拆解】Spring中的注解
数据库·spring·面试
不吃香菜学java2 小时前
苍穹外卖-新增菜品需求分析
java·spring boot·spring·tomcat·maven·ssm
Oneslide2 小时前
Pod启动失败: /var/lib/kubelet/xxx/kube-api-access/ :no space left on device
后端
xiaohe072 小时前
自己编译RustDesk,并将自建ID服务器和key信息写入客户端
java
南方的耳朵2 小时前
Neutron VLAN 网络模型 + Linux bridge 驱动 + 集中式路由 完整实现方案整理
后端
smile_life_2 小时前
使用idea查看maven依赖
java·maven·intellij-idea
Predestination王瀞潞2 小时前
1. Java SE到底是什么:不仅仅是面向对象
java·开发语言
MekoLi292 小时前
Arthas 安装与使用全流程教程
后端·面试
小王不爱笑1322 小时前
MyBatis-Plus 入门到实战,极简实现单表 CRUD
mybatis