
🎬 那我掉的头发算什么 :个人主页
🔥 个人专栏 : 《javaSE》《数据结构》《数据库》《javaEE》
⛺️待到苦尽甘来日

引言
在上一篇文章中,我们完成了项目的基础架构搭建,包括数据库设计、公共模块封装等。本篇将聚焦于核心业务代码的实现,从持久层开始,逐步实现用户登录、博客管理、权限控制等功能,最终完成一个可运行的博客系统
文章目录
- 引言
- 持久层代码
- 请求与响应模型
- 业务层代码
- [控制层代码(Controller Layer)](#控制层代码(Controller Layer))
- 核心功能实现详解
-
- [用户登录与 JWT 令牌](#用户登录与 JWT 令牌)
- [博客 CRUD 操作](#博客 CRUD 操作)
- 权限控制(强制登录)
- 博客系统(加盐)加密
- 总结
持久层代码
持久层是整个系统的基石,负责与数据库进行交互。我们使用 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 开发中分层设计、安全防护等核心思想。