当前位置: 首页 > news >正文

Spring Boot 博客项目深度分析报告

Spring Boot 博客项目深度分析报告

本文档对 spring-blog-demo-2 项目进行全面深入的分析,涵盖项目概述、核心功能模块、关键技术点、数据库设计以及部署与运行等方面。

阅读本文章需要1.5小时

项目链接

1. 项目概述与框架

1.1 项目主要功能和目标

spring-blog-demo-2 项目是一个基于 Spring Boot 技术栈实现的博客系统。其主要目标是提供一个简洁、易用的在线内容发布和管理平台。核心功能预计包括用户注册与登录、个人信息管理、博客文章的发布、编辑、删除、列表展示以及详情查看等。项目旨在演示如何使用现代 Java Web 技术栈构建一个功能完整的 Web 应用程序。

1.2 项目核心框架和技术

根据项目配置文件 (pom.xml) 和代码结构分析,该项目采用了以下核心框架和技术:

  • 后端框架:
    • Spring Boot (3.4.5): 作为项目的基础框架,简化了 Spring 应用的初始搭建以及开发过程,提供了自动配置、起步依赖等特性。
    • Spring MVC: 用于构建 Web 应用程序,处理 HTTP 请求和响应,实现 RESTful API 接口。
  • 数据持久层:
    • MyBatis-Plus (3.5.5 for Spring Boot 3): 一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化了 CRUD 操作,提供了代码生成器、分页插件等功能。
    • MySQL Connector/J: 用于 Java 应用程序连接 MySQL 数据库。
  • 工具库与辅助技术:
    • Lombok: 通过注解简化 Java 代码,如自动生成 Getter/Setter, Constructor, toString() 等方法,减少样板代码。
    • JSON Web Token (JWT - jjwt-api, jjwt-impl, jjwt-jackson 0.11.5): 用于实现用户认证和授权机制,通过 Token 进行无状态的身份验证。
    • Spring Boot Starter Validation: 提供数据校验功能,确保输入数据的有效性。
    • Thymeleaf (推测,通常与 Spring Boot 结合用于服务端渲染,待确认): 虽然 pom.xml 中未直接列出 spring-boot-starter-thymeleaf,但通常博客类项目会使用模板引擎渲染前端页面。后续分析 HTML 文件可以确认。
  • 开发与构建:
    • Java 17: 项目使用的 JDK 版本。
    • Maven: 作为项目管理和构建工具,负责依赖管理、项目编译、打包等。

1.3 项目整体架构和模块划分

该项目遵循了典型的分层架构设计模式,主要可以划分为以下几个层次和模块:

  • 表现层 (Controller):

    • 位于 org.example.springblogdemo2.controller 包下。
    • 负责接收前端 HTTP 请求,调用业务逻辑层处理请求,并返回响应数据给前端。
    • 主要包含 UserController.java (处理用户相关操作) 和 BlogController.java (处理博客文章相关操作)。
  • 业务逻辑层 (Service):

    • 接口定义在 org.example.springblogdemo2.service 包下 (如 UserService.java, BlogService.java)。
    • 具体实现在 org.example.springblogdemo2.service.impl 包下 (如 UserServiceImpl.java, BlogServiceImpl.java)。
    • 负责处理核心业务逻辑,对数据进行加工处理,并调用数据访问层与数据库交互。
  • 数据访问层 (Mapper/DAO):

    • 位于 org.example.springblogdemo2.mapper 包下 (如 UserInfoMapper.java, BlogInfoMapper.java)。
    • 基于 MyBatis-Plus 实现,定义了与数据库表进行交互的接口方法,负责数据的持久化操作。
  • 数据对象层 (POJO/Entity/DTO):

    • 位于 org.example.springblogdemo2.pojo 包下,并进一步细分为:
      • dataobject:存放与数据库表结构对应的实体类 (如 UserInfo.java, BlogInfo.java)。
      • request:存放用于接收前端请求参数的数据传输对象 (DTO),如 UserLoginRequest.java, AddBlogRequest.java
      • response:存放用于向前端返回结果的数据传输对象 (DTO),如 UserLoginResponse.java, BlogInfoResponse.java, 以及通用的结果封装类 Result.java
  • 通用模块 (Common):

    • 位于 org.example.springblogdemo2.common 包下,包含项目通用的组件和工具类:
      • advice:全局异常处理 (ExceptionAdvice.java) 和统一响应体封装 (ResponseAdvice.java)。
      • config:项目配置类,如 WebConfig.java 用于配置拦截器等。
      • constants:定义项目中使用的常量 (Constants.java)。
      • exception:自定义业务异常类 (BlogException.java)。
      • interceptor:拦截器,如 LoginInterceptor.java 用于实现登录验证。
      • util:工具类集合,如 BeanTransUtils.java (对象属性复制)、DateUtils.java (日期处理)、JwtUtils.java (JWT 生成与校验)、SecurityUtils.java (可能包含密码加密等安全相关工具)。
  • 枚举类 (Enums):

    • 位于 org.example.springblogdemo2.enums 包下,如 ResultCodeEnum.java 定义了操作结果的状态码和消息。
  • 启动类:

    • org.example.springblogdemo2.SpringBlogDemo2Application.java:Spring Boot 应用的入口启动类。

整体来看,项目结构清晰,模块划分合理,符合 Spring Boot 项目的典型开发规范,易于理解和维护。


2. 核心功能模块分析

2.1 模块一:用户模块

用户模块是博客系统的基础,负责处理用户注册 (本项目中似乎未直接体现注册功能,而是预置用户)、登录、信息获取等功能。

2.1.1 代码实现

Controller (UserController.java):

package org.example.springblogdemo2.controller;import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.example.springblogdemo2.pojo.request.UserLoginRequest;
import org.example.springblogdemo2.pojo.response.UserInfoResponse;
import org.example.springblogdemo2.pojo.response.UserLoginResponse;
import org.example.springblogdemo2.service.UserService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {@Resource(name = "userServiceImpl")private UserService userService;@RequestMapping("/login")public UserLoginResponse login(@RequestBody @Validated UserLoginRequest userLoginRequest) {log.info("用户登录,用户名:" + userLoginRequest.getUserName());return userService.checkPassword(userLoginRequest);}@RequestMapping("/getUserInfo")public UserInfoResponse getUserInfo(@NotNull(message = "userId 不能为 null") Integer userId) {log.info("获取用户信息,userId: {}", userId);return userService.getUserInfo(userId);}@RequestMapping("/getAuthorInfo")public UserInfoResponse getAuthorInfo(@NotNull(message = "blogId 不能为 null") Integer blogId) {log.info("获取当前文章作者的用户信息,blogId: {}", blogId);return userService.getAuthorInfo(blogId);}
}

Service Interface (UserService.java):

package org.example.springblogdemo2.service;import org.example.springblogdemo2.pojo.request.UserLoginRequest;
import org.example.springblogdemo2.pojo.response.UserInfoResponse;
import org.example.springblogdemo2.pojo.response.UserLoginResponse;public interface UserService {UserLoginResponse checkPassword(UserLoginRequest userLoginRequest);UserInfoResponse getUserInfo(Integer userId);UserInfoResponse getAuthorInfo(Integer blogId);
}

Service Implementation (UserServiceImpl.java):

package org.example.springblogdemo2.service.impl;// ... imports ...
import org.example.springblogdemo2.common.util.JwtUtils;
import org.example.springblogdemo2.common.util.SecurityUtils;
import org.example.springblogdemo2.mapper.UserInfoMapper;
import org.example.springblogdemo2.pojo.dataobject.BlogInfo;
import org.example.springblogdemo2.pojo.dataobject.UserInfo;
import org.example.springblogdemo2.pojo.request.UserLoginRequest;
import org.example.springblogdemo2.pojo.response.UserInfoResponse;
import org.example.springblogdemo2.pojo.response.UserLoginResponse;
import org.example.springblogdemo2.service.BlogService;
import org.example.springblogdemo2.service.UserService;
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 {@Autowiredprivate UserInfoMapper userInfoMapper;@Resource(name = "blogServiceImpl")private BlogService blogService;@Overridepublic UserLoginResponse checkPassword(UserLoginRequest userLoginRequest) {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(UserInfo::getUserName, userLoginRequest.getUserName()).eq(UserInfo::getDeleteFlag, 0);UserInfo userInfo = null;try {userInfo = userInfoMapper.selectOne(queryWrapper);} catch (Exception e) {throw new BlogException("数据库查询失败:" + e.getMessage());}if (userInfo == null) {throw new BlogException("用户不存在");}if (!SecurityUtils.verify(userLoginRequest.getPassword(), userInfo.getPassword())) {throw new BlogException("用户密码错误");}Map<String ,Object> map = new HashMap<>();map.put("userId", userInfo.getId());map.put("name", userInfo.getUserName());String token = JwtUtils.genToken(map);return new UserLoginResponse(userInfo.getId(), token);}@Overridepublic UserInfoResponse getUserInfo(Integer userId) {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(UserInfo::getDeleteFlag, 0).eq(UserInfo::getId, userId);UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);return BeanTransUtils.trans(userInfo); // Converts UserInfo to UserInfoResponse}@Overridepublic UserInfoResponse getAuthorInfo(Integer blogId) {BlogInfo blogInfo = blogService.getBlogInfo(blogId);if (blogInfo == null || blogInfo.getUserId() < 1) {throw new BlogException("博客不存在");}return getUserInfo(blogInfo.getUserId());}
}

Data Object (UserInfo.java):

package org.example.springblogdemo2.pojo.dataobject;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDate;@Data
public class UserInfo {@TableId(type = IdType.AUTO)private Integer id;private String userName;private String password; // Stored as hashed valueprivate String githubUrl;private Integer deleteFlag;private LocalDate createTime;private LocalDate updateTime;
}

Request DTO (UserLoginRequest.java):

package org.example.springblogdemo2.pojo.request;import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;@Data
public class UserLoginRequest {@NotNull(message = "用户名不能为 null")private String userName;@NotNull(message = "密码不能为 null")@Length(min = 5, max = 11)private String password;
}

Response DTOs (UserInfoResponse.java, UserLoginResponse.java):

// UserInfoResponse.java
package org.example.springblogdemo2.pojo.response;
import lombok.Data;
@Data
public class UserInfoResponse {private Integer id;private String userName;private String githubUrl;
}// UserLoginResponse.java
package org.example.springblogdemo2.pojo.response;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class UserLoginResponse {private Integer userId;private String token;
}

Mapper (UserInfoMapper.java):

package org.example.springblogdemo2.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.example.springblogdemo2.pojo.dataobject.UserInfo;@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
2.1.2 设计思路/逻辑

用户模块的核心逻辑围绕用户身份验证和信息查询展开:

  1. 用户登录 (/user/login):

    • 接收客户端通过 JSON 格式提交的用户名 (userName) 和密码 (password)。
    • 使用 @ValidatedUserLoginRequest 对象进行参数校验 (用户名非空,密码非空且长度在5-11位之间)。
    • UserServiceImpl.checkPassword 方法处理登录逻辑:
      • 根据用户名查询 user_info 表中未被删除 (delete_flag = 0) 的用户记录。
      • 如果用户不存在,抛出 BlogException
      • 如果用户存在,使用 SecurityUtils.verify 方法校验客户端提交的密码与数据库中存储的加密密码是否匹配。本项目中 SecurityUtils 可能是对密码哈希和校验的封装 (例如 BCrypt)。
      • 密码校验成功后,将用户 ID (userId) 和用户名 (name) 存入一个 Map
      • 调用 JwtUtils.genToken 方法,使用该 Map 作为 payload 生成 JWT (JSON Web Token)。
      • 返回包含用户 ID 和生成的 JWT 的 UserLoginResponse 对象。
  2. 获取用户信息 (/user/getUserInfo):

    • 接收客户端传递的 userId 参数。
    • UserServiceImpl.getUserInfo 方法处理:
      • 根据 userId 查询 user_info 表中未被删除的用户记录。
      • 使用 BeanTransUtils.trans 方法将查询到的 UserInfo (包含密码等敏感信息) 转换为 UserInfoResponse (只包含 ID、用户名、GitHub URL 等公开信息) 后返回。
  3. 获取文章作者信息 (/user/getAuthorInfo):

    • 接收客户端传递的 blogId 参数。
    • UserServiceImpl.getAuthorInfo 方法处理:
      • 首先调用 BlogService.getBlogInfo 方法,根据 blogId 获取博客文章信息,从而得到文章的作者 ID (userId)。
      • 如果博客不存在或作者 ID 无效,抛出 BlogException
      • 然后调用本模块的 getUserInfo 方法,根据获取到的作者 ID 查询并返回作者的公开信息 (UserInfoResponse)。

关键决策点:

  • 密码存储: 数据库中存储的是加密后的密码,登录时进行密文比对,增强了安全性。具体加密方式由 SecurityUtils 实现。
  • 身份验证: 采用 JWT 进行无状态身份验证。登录成功后颁发 Token,后续请求通过 Token 验证用户身份 (通常由拦截器 LoginInterceptor 实现,后续会分析)。
  • 数据校验: 使用 Spring Validation (@Validated, @NotNull, @Length) 对输入参数进行校验,保证数据的合法性。
  • 数据转换: 使用 BeanTransUtils 工具类将包含敏感信息的 UserInfo DO (Data Object) 转换为不含敏感信息的 UserInfoResponse DTO (Data Transfer Object),避免敏感数据泄露到前端。
  • 异常处理: 统一使用自定义的 BlogException 抛出业务异常,便于全局异常处理器 (ExceptionAdvice) 捕获和处理。
2.1.3 功能说明

用户模块主要实现了以下功能:

  • 用户登录: 验证用户凭据,成功后生成并返回 JWT,用于后续的身份认证。
  • 获取指定用户信息: 根据用户 ID 查询并返回用户的基本公开信息。
  • 获取文章作者信息: 根据博客文章 ID,间接查询并返回该文章作者的基本公开信息。

该模块为整个博客系统的用户身份管理和信息展示提供了基础支撑。


2.2 模块二:博客文章模块

博客文章模块是系统的核心,负责博客文章的创建、读取、更新和删除 (CRUD) 操作,以及文章列表的展示。

2.2.1 代码实现

Controller (BlogController.java):

package org.example.springblogdemo2.controller;import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.example.springblogdemo2.pojo.request.AddBlogRequest;
import org.example.springblogdemo2.pojo.request.UpdateRequest;
import org.example.springblogdemo2.pojo.response.BlogInfoResponse;
import org.example.springblogdemo2.service.BlogService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@Slf4j
@RequestMapping("/blog")
@RestController
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(message = "blogId 不能为 null") Integer blogId) {log.info("获取博客详情,blogId: {}", blogId);return blogService.getBlogDetail(blogId);}@RequestMapping("/addBlog")public Boolean addBlog(@RequestBody @Validated AddBlogRequest addBlogRequest) {log.info("发布博客,userId: {}, title: {}", addBlogRequest.getUserId(), addBlogRequest.getTitle());return blogService.addBlog(addBlogRequest);}@RequestMapping("/update")public Boolean update(@RequestBody @Validated UpdateRequest updateRequest) {log.info("更新博客,request:{}", updateRequest);return blogService.updateBlog(updateRequest);}@RequestMapping("/delete")public Boolean delete(@NotNull(message = "blogId 不能为 null") Integer blogId) {log.info("删除博客,blogId: {}", blogId);return blogService.deleteBlog(blogId);}
}

Service Interface (BlogService.java):

package org.example.springblogdemo2.service;import org.example.springblogdemo2.pojo.dataobject.BlogInfo;
import org.example.springblogdemo2.pojo.request.AddBlogRequest;
import org.example.springblogdemo2.pojo.request.UpdateRequest;
import org.example.springblogdemo2.pojo.response.BlogInfoResponse;import java.util.List;public interface BlogService {List<BlogInfoResponse> getList();BlogInfoResponse getBlogDetail(Integer blogId);BlogInfo getBlogInfo(Integer blogId); // Internal method to get raw BlogInfoboolean addBlog(AddBlogRequest addBlogRequest);Boolean updateBlog(UpdateRequest updateRequest);Boolean deleteBlog(Integer blogId);
}

Service Implementation (BlogServiceImpl.java):

package org.example.springblogdemo2.service.impl;// ... imports ...
import org.example.springblogdemo2.common.constants.Constants;
import org.example.springblogdemo2.mapper.BlogInfoMapper;
import org.example.springblogdemo2.pojo.dataobject.BlogInfo;
import org.example.springblogdemo2.pojo.request.AddBlogRequest;
import org.example.springblogdemo2.pojo.request.UpdateRequest;
import org.example.springblogdemo2.pojo.response.BlogInfoResponse;
import org.example.springblogdemo2.service.BlogService;
import org.example.springblogdemo2.common.util.BeanTransUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.stream.Collectors;@Slf4j
@Service
public class BlogServiceImpl implements BlogService {@Autowiredprivate BlogInfoMapper blogInfoMapper;@Overridepublic List<BlogInfoResponse> getList() {QueryWrapper<BlogInfo> queryWrapper  =new QueryWrapper<>();queryWrapper.lambda().eq(BlogInfo::getDeleteFlag, Constants.BLOG_NORMAL);List<BlogInfo> blogInfos = blogInfoMapper.selectList(queryWrapper);return blogInfos.stream().map(blogInfo -> BeanTransUtils.trans(blogInfo)).collect(Collectors.toList());}@Overridepublic BlogInfoResponse getBlogDetail(Integer blogId) {return BeanTransUtils.trans(getBlogInfo(blogId));}@Overridepublic BlogInfo getBlogInfo(Integer blogId){QueryWrapper<BlogInfo> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(BlogInfo::getDeleteFlag, Constants.BLOG_NORMAL).eq(BlogInfo::getId, blogId);return blogInfoMapper.selectOne(queryWrapper);}@Overridepublic boolean addBlog(AddBlogRequest addBlogRequest) {BlogInfo blogInfo = new BlogInfo();BeanUtils.copyProperties(addBlogRequest, blogInfo);try {Integer result = blogInfoMapper.insert(blogInfo);return result == 1;} catch (Exception e) {log.error("博客插入失败,e: ", e);throw new BlogException("内部错误,请联系管理员");}}@Overridepublic Boolean updateBlog(UpdateRequest updateRequest) {BlogInfo blogInfo = BeanTransUtils.trans(updateRequest); // Converts UpdateRequest to BlogInfotry {Integer result = blogInfoMapper.updateById(blogInfo);return result == 1;} catch (Exception exception) {log.error("编辑博客失败,e: ", exception);throw new BlogException("内部错误,请联系管理员");}}@Overridepublic Boolean deleteBlog(Integer blogId) {BlogInfo blogInfo = new BlogInfo();blogInfo.setId(blogId);blogInfo.setDeleteFlag(Constants.BLOG_DELETE); // Logical deletetry {Integer result = blogInfoMapper.updateById(blogInfo);return result == 1;} catch (Exception exception) {log.error("删除博客失败,e: ", exception);throw new BlogException("内部错误,请联系管理员");}}
}

Data Object (BlogInfo.java):

package org.example.springblogdemo2.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; // Author's IDprivate Integer deleteFlag; // 0 for normal, 1 for deletedprivate Date createTime;private LocalDateTime updateTime;
}

Request DTOs (AddBlogRequest.java, UpdateRequest.java):

// AddBlogRequest.java
package org.example.springblogdemo2.pojo.request;import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;@Data
public class AddBlogRequest {@NotNull(message = "userId 不能为 null")private Integer userId;@NotBlank(message = "标题不能为空")private String title;@NotBlank(message = "内容不能为空")private String content;
}// UpdateRequest.java
package org.example.springblogdemo2.pojo.request;import jakarta.validation.constraints.NotNull;
import lombok.Data;@Data
public class UpdateRequest {@NotNull(message = "id 不能为 null")private Integer id;private String title;private String content;
}

Response DTO (BlogInfoResponse.java):

package org.example.springblogdemo2.pojo.response;import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.example.springblogdemo2.common.util.DateUtils;
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;// Custom getter to format date stringpublic String getCreateTime() {return DateUtils.dateFormat(createTime);}// This method seems to be for display purposes, might not be directly from DB fieldpublic String getCurrentTime() {return DateUtils.dateFormat(new Date());}
}

Mapper (BlogInfoMapper.java):

package org.example.springblogdemo2.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.example.springblogdemo2.pojo.dataobject.BlogInfo;@Mapper
public interface BlogInfoMapper extends BaseMapper<BlogInfo> {
}
2.2.2 设计思路/逻辑

博客文章模块提供了文章管理的核心功能:

  1. 获取博客列表 (/blog/getList):

    • BlogServiceImpl.getList 方法处理:
      • 查询 blog_info 表中所有未被逻辑删除 (delete_flag = Constants.BLOG_NORMAL) 的博客文章。
      • 将查询到的 BlogInfo 列表通过 stream().map() 转换为 BlogInfoResponse 列表。BeanTransUtils.trans 用于此转换,可能还包含日期格式化等处理。
      • 返回 BlogInfoResponse 列表。
  2. 获取博客详情 (/blog/getBlogDetail):

    • 接收客户端传递的 blogId 参数。
    • BlogServiceImpl.getBlogDetail 方法处理:
      • 内部调用 getBlogInfo(blogId) 方法获取原始的 BlogInfo 对象。
      • getBlogInfo 方法根据 blogId 查询 blog_info 表中未被逻辑删除的特定博客文章。
      • 将获取到的 BlogInfo 对象通过 BeanTransUtils.trans 转换为 BlogInfoResponse 后返回。
  3. 添加博客 (/blog/addBlog):

    • 接收客户端通过 JSON 格式提交的 AddBlogRequest (包含 userId, title, content)。
    • 使用 @Validated 对请求参数进行校验。
    • BlogServiceImpl.addBlog 方法处理:
      • 创建一个新的 BlogInfo 对象。
      • 使用 BeanUtils.copyPropertiesAddBlogRequest 中的属性复制到 BlogInfo 对象。
      • 调用 blogInfoMapper.insert 方法将新的博客文章插入数据库。
      • 返回操作成功与否的布尔值。
  4. 更新博客 (/blog/update):

    • 接收客户端通过 JSON 格式提交的 UpdateRequest (包含 id, title, content)。
    • 使用 @Validated 对请求参数进行校验 (ID 不能为空)。
    • BlogServiceImpl.updateBlog 方法处理:
      • 使用 BeanTransUtils.trans (或 BeanUtils.copyProperties) 将 UpdateRequest 转换为 BlogInfo 对象。
      • 调用 blogInfoMapper.updateById 方法根据博客 ID 更新数据库中的文章信息。
      • 返回操作成功与否的布尔值。
  5. 删除博客 (/blog/delete):

    • 接收客户端传递的 blogId 参数。
    • BlogServiceImpl.deleteBlog 方法处理:
      • 创建一个 BlogInfo 对象,并设置其 id 为要删除的 blogId
      • 设置 deleteFlagConstants.BLOG_DELETE (表示逻辑删除)。
      • 调用 blogInfoMapper.updateById 方法更新该博客的删除标记。
      • 返回操作成功与否的布尔值。

关键决策点:

  • 逻辑删除: 博客文章的删除采用逻辑删除 (deleteFlag 标记),而不是物理删除,便于数据恢复和审计。
  • 数据转换: 同样使用 BeanTransUtilsBeanUtils.copyProperties 在 DTO 和 DO 之间进行转换。BlogInfoResponse 中对 createTime 进行了格式化处理,并额外提供了一个 getCurrentTime 方法。
  • 参数校验: 对API接口的输入参数使用 Spring Validation 进行校验。
  • 常量使用: 使用 Constants.BLOG_NORMALConstants.BLOG_DELETE 来表示博客的正常和删除状态,提高了代码的可读性和可维护性。
2.2.3 功能说明

博客文章模块实现了以下核心功能:

  • 文章列表展示: 获取所有未被删除的博客文章列表。
  • 文章详情查看: 根据文章 ID 获取特定文章的详细内容。
  • 发布新文章: 允许已登录用户创建并发布新的博客文章。
  • 编辑文章: 允许文章作者修改已发布的博客文章内容。
  • 删除文章: 允许文章作者逻辑删除自己的博客文章。

该模块是博客系统的主要内容管理部分,支撑了博客的核心业务。

2.3 模块三:评论模块 (如果存在)

经过对项目文件结构 (ls -R 输出) 和已分析代码的检查,当前项目中未发现明确的评论模块相关代码,例如 CommentController.javaCommentService.javaCommentInfo.javaCommentMapper.java 等。因此,可以认为本项目当前版本不包含评论功能模块。


3. 关键技术点深度剖析

本项目 spring-blog-demo-2 运用了多项现代 Java Web 开发中的关键技术,以下将对其中一些重要技术点进行深度剖析。

3.1 技术点一:Spring Boot 自动配置与起步依赖

Spring Boot 极大地简化了 Spring 应用的搭建和开发过程,其核心特性之一便是自动配置 (Auto-configuration) 和起步依赖 (Starter Dependencies)。

3.1.1 相关代码示例

pom.xml (部分依赖):

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.5</version><relativePath/> <!-- lookup parent from repository -->
</parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!-- Other dependencies -->
</dependencies>

Main Application Class (SpringBlogDemo2Application.java):

package org.example.springblogdemo2;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("org.example.springblogdemo2.mapper") // MyBatis-Plus mapper scan
public class SpringBlogDemo2Application {public static void main(String[] args) {SpringApplication.run(SpringBlogDemo2Application.class, args);}
}
3.1.2 应用方式和原因
  • spring-boot-starter-parent 项目继承自 spring-boot-starter-parent,它提供了一系列预定义的依赖管理和插件配置。这使得开发者无需手动指定常用库的版本号,避免了版本冲突问题,并统一了构建配置。
  • spring-boot-starter-web 引入此起步依赖会自动配置构建 Web 应用所需的核心组件,包括 Spring MVC、嵌入式的 Tomcat (默认)、Jackson (JSON处理) 等。开发者无需再手动配置 DispatcherServlet、视图解析器等繁琐的 XML 或 Java 配置。
  • mybatis-plus-spring-boot3-starter 这是 MyBatis-Plus 针对 Spring Boot 3 提供的起步依赖。它会自动配置 MyBatis-Plus 所需的 SqlSessionFactory、SqlSessionTemplate,并与 Spring Boot 的数据源配置集成。开发者只需在 application.propertiesapplication.yml 中配置数据源信息即可。
  • spring-boot-starter-validation 提供了对 JSR-303/JSR-380 (Bean Validation) 的支持,允许使用注解 (如 @NotNull, @NotBlank, @Length) 对请求参数或 JavaBean 进行数据校验。Spring Boot 会自动配置相关的校验器。
  • @SpringBootApplication 注解: 这是一个复合注解,它包含了 @SpringBootConfiguration (标记该类为配置类)、@EnableAutoConfiguration (启用 Spring Boot 的自动配置机制) 和 @ComponentScan (自动扫描当前包及其子包下的 Spring 组件)。
  • @MapperScan 注解: 虽然不是 Spring Boot 直接提供的,但与 MyBatis-Plus starter 结合使用,用于指定 MyBatis Mapper 接口所在的包路径,以便 Spring 容器能够扫描并创建代理实现。

原因:

采用 Spring Boot 的自动配置和起步依赖,主要目的是:

  1. 快速开发: 大幅减少了项目的初始配置工作,让开发者能更快地专注于业务逻辑实现。
  2. 简化管理: 统一管理依赖版本,减少版本冲突的风险。
  3. 约定优于配置: Spring Boot 遵循“约定优于配置”的原则,提供了许多默认配置,同时也允许开发者在需要时进行自定义覆盖。
3.1.3 主要思路和注意事项

主要思路:

Spring Boot 的自动配置机制基于 classpath 下的 jar 包、特定类的存在以及已定义的 Bean。当应用启动时,@EnableAutoConfiguration 会触发一系列 AutoConfiguration 类的加载。这些类通常带有 @ConditionalOnClass@ConditionalOnMissingBean 等条件注解,根据当前环境判断是否需要应用某个配置。例如,当 spring-boot-starter-web 在 classpath 中时,ServletWebServerFactoryAutoConfiguration 会自动配置一个嵌入式的 Web 服务器。

注意事项:

  1. 理解自动配置原理: 虽然自动配置很方便,但了解其大致原理有助于在出现问题时进行排查。可以通过启动应用时添加 --debug (或在 application.properties 中设置 debug=true) 查看自动配置报告,了解哪些配置被应用了,哪些没有。
  2. 排除特定自动配置: 如果某个自动配置不符合需求,可以通过 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) 或在配置文件中使用 spring.autoconfigure.exclude 属性来排除它。
  3. 自定义配置: 当默认配置不满足需求时,可以通过创建自定义的 @Configuration 类并声明相应的 Bean 来覆盖自动配置。Spring Boot 会优先使用用户自定义的 Bean。
  4. 依赖版本兼容性: 虽然 spring-boot-starter-parent 管理了大部分依赖版本,但在引入非 Spring Boot 管理的依赖时,仍需注意版本兼容性问题。

3.2 技术点二:Spring MVC 请求处理与响应

Spring MVC 是 Spring框架中用于构建 Web 应用程序的核心模块,它提供了基于 MVC (Model-View-Controller) 设计模式的强大功能来处理 HTTP 请求并生成响应。在本后端为主的项目中,响应通常是 JSON 数据。

3.2.1 相关代码示例

Controller (UserController.java - 部分):

package org.example.springblogdemo2.controller;// ... imports ...
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RequestMapping("/user") // 1. 类级别请求映射
@RestController // 2. 声明为 RESTful 控制器
public class UserController {@Resource(name = "userServiceImpl")private UserService userService;@RequestMapping("/login") // 3. 方法级别请求映射public UserLoginResponse login(@RequestBody @Validated UserLoginRequest userLoginRequest) { // 4. @RequestBody 和 @Validatedlog.info("用户登录,用户名:" + userLoginRequest.getUserName());return userService.checkPassword(userLoginRequest); // 5. 返回对象,自动序列化为 JSON}@RequestMapping("/getUserInfo")public UserInfoResponse getUserInfo(@NotNull(message = "userId 不能为 null") Integer userId) { // 6. 请求参数绑定log.info("获取用户信息,userId: {}", userId);return userService.getUserInfo(userId);}// ... other methods ...
}

Controller (BlogController.java - 部分):

package org.example.springblogdemo2.controller;// ... imports ...
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@Slf4j
@RequestMapping("/blog")
@RestController
public class BlogController {@Resource(name = "blogServiceImpl")private BlogService blogService;@RequestMapping("/getList")public List<BlogInfoResponse> getList() {log.info("获取博客列表~");List<BlogInfoResponse> blogInfos = blogService.getList();return blogInfos;}// ... other methods ...
}

统一响应处理 (ResponseAdvice.java - 如果存在并启用):

// Located in: org.example.springblogdemo2.common.advice.ResponseAdvice.java
package org.example.springblogdemo2.common.advice;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.example.springblogdemo2.common.exception.BlogException;
import org.example.springblogdemo2.pojo.response.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@RestControllerAdvice(basePackages = "org.example.springblogdemo2.controller") // 指定对哪些包下的 Controller 生效
public class ResponseAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof Result) {return body;}if (body instanceof String) {ObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.writeValueAsString(Result.success(body));} catch (JsonProcessingException e) {throw new BlogException("返回 String 类型结果错误");}}return Result.success(body);}
}

通用结果封装类 (Result.java):

// Located in: org.example.springblogdemo2.pojo.response.Result.java
package org.example.springblogdemo2.pojo.response;import lombok.Data;
import org.example.springblogdemo2.enums.ResultCodeEnum;@Data
public class Result<T> {private Integer code; // 状态码private String message; // 状态信息private T data; // 返回数据public static <T> Result<T> success(T data) {Result<T> result = new Result<>();result.setCode(ResultCodeEnum.SUCCESS.getCode());result.setMessage(ResultCodeEnum.SUCCESS.getMessage());result.setData(data);return result;}public static <T> Result<T> fail(Integer code, String message) {Result<T> result = new Result<>();result.setCode(code);result.setMessage(message);return result;}
}
3.2.2 应用方式和原因
  1. @RequestMapping (类级别和方法级别): 定义API路径。
  2. @RestController 声明为RESTful控制器,返回值直接写入响应体。
  3. @RequestBody 将请求体JSON反序列化为Java对象。
  4. @Validated 触发对请求对象的校验。
  5. 返回对象自动序列化: Java对象自动转为JSON响应。
  6. 请求参数绑定: 自动绑定URL参数到方法参数。
  7. @RestControllerAdviceResponseBodyAdvice (ResponseAdvice.java) 实现统一响应格式封装。
3.2.3 主要思路和注意事项
  • 请求分发: DispatcherServlet核心作用。
  • 参数解析: HandlerMethodArgumentResolver处理。
  • 数据转换: HttpMessageConverter负责序列化/反序列化。
  • 响应封装: ResponseBodyAdvice统一处理。
  • 注意事项: Content-Type,异常处理,路径变量与查询参数选择,ResponseBodyAdvice顺序,String类型特殊处理。

3.3 技术点三:MyBatis-Plus 数据持久化操作

MyBatis-Plus作为MyBatis的增强工具,简化了数据库操作。

3.3.1 相关代码示例

pom.xml (MyBatis-Plus Starter):

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version>
</dependency>

Application Configuration (示例):

spring.datasource.url=jdbc:mysql://localhost:3306/your_database_name
# ... other datasource properties ...
mybatis-plus.global-config.db-config.logic-delete-field=deleteFlag

Mapper Interface (UserInfoMapper.java):

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

Data Object (UserInfo.java - 部分):

@Data
public class UserInfo {@TableId(type = IdType.AUTO)private Integer id;@TableLogicprivate Integer deleteFlag;
}

Service Implementation (UserServiceImpl.java - 使用 QueryWrapper):

QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserInfo::getUserName, userLoginRequest.getUserName());
UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
3.3.2 应用方式和原因
  1. mybatis-plus-spring-boot3-starter 简化集成。
  2. @Mapper 注解: 标记Mapper接口。
  3. BaseMapper<T> 接口: 提供通用CRUD方法。
  4. 实体类注解 (@TableId, @TableLogic): 配置主键和逻辑删除。
  5. QueryWrapper / LambdaQueryWrapper 构建复杂查询条件。
3.3.3 主要思路和注意事项
  • 主要思路: 约定优于配置,通用CRUD,条件构造器,逻辑删除,分页插件。
  • 注意事项: Mapper扫描,实体与表映射,逻辑删除配置,QueryWrapper使用,事务管理,自定义SQL,性能,版本兼容性。

3.4 技术点四:用户认证与授权 (JWT & LoginInterceptor)

采用JWT进行无状态认证,通过Spring MVC拦截器实现访问控制。

3.4.1 相关代码示例

JWT 工具类 (JwtUtils.java):

public class JwtUtils {private static final Long EXPIRATION = 24 * 60 * 60 * 1000L;private static final String SECRET = "mysecretkey"; // 应从配置读取public static String genToken(Map<String, Object> claims) { /* ... */ }public static Claims parseToken(String token) { /* ... */ }
}

登录拦截器 (LoginInterceptor.java):

@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("token");if (!StringUtils.hasLength(token)) { /* ... handle missing token ... */ return false; }Claims claims = JwtUtils.parseToken(token);if (claims == null) { /* ... handle invalid token ... */ return false; }return true;}
}

Web 配置类 (WebConfig.java):

@Configuration
public class WebConfig implements WebMvcConfigurer {@Resource private LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/user/login", "/user/getAuthorInfo", "/blog/getList", "/blog/getBlogDetail", "/**/*.html", "/**/*.css", "/**/*.js");}
}

密码处理工具类 (SecurityUtils.java - 示例):

public class SecurityUtils {// MD5 + Salt (示例,不推荐)public static String encrypt(String password) { /* ... */ }public static boolean verify(String rawPassword, String encryptedPassword) { /* ... */ }
}
3.4.2 应用方式和原因
  1. JWT 生成 (JwtUtils.genToken): 登录成功后生成Token。
  2. JWT 解析与验证 (JwtUtils.parseTokenLoginInterceptor): 拦截器验证Token。
  3. LoginInterceptor 拦截请求: preHandle进行权限检查。
  4. 拦截路径配置 (WebConfig.addInterceptors): 精确控制受保护资源。
  5. 密码存储与校验 (SecurityUtils): 哈希存储密码。
3.4.3 主要思路和注意事项
  • 主要思路: 登录->发Token;后续请求带Token->拦截器验证。
  • 注意事项: JWT安全性 (密钥、HTTPS、Payload、有效期),拦截器配置,密码哈希 (使用强算法如BCrypt),Token存储,Token泄露与吊销,错误处理。

3.5 技术点五:全局异常处理与统一响应封装 (补充)

通过@RestControllerAdvice和自定义异常实现统一错误响应。

3.5.1 相关代码示例

自定义业务异常类 (BlogException.java):

public class BlogException extends RuntimeException { /* ... */ }

全局异常处理器 (ExceptionAdvice.java):

@Slf4j
@RestControllerAdvice(basePackages = "org.example.springblogdemo2.controller")
public class ExceptionAdvice {@ExceptionHandler(BlogException.class)public Result<Object> handleBlogException(BlogException e) { /* ... */ }@ExceptionHandler(MethodArgumentNotValidException.class)public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { /* ... */ }@ExceptionHandler(RuntimeException.class)public Result<Object> handleRuntimeException(RuntimeException e) { /* ... */ }
}

参数校验注解 (在 DTO 中使用):

public class UserLoginRequest {@NotNull(message = "用户名不能为 null")private String userName;
}
3.5.2 应用方式和原因
  1. @RestControllerAdvice: 标记全局异常处理器。
  2. @ExceptionHandler: 处理特定类型异常。
  3. 自定义异常 (BlogException): 区分业务错误。
  4. 参数校验 (@Validated 和 JSR-303 注解): 前置数据校验。
  5. 统一响应 (Result.java): 保证响应结构一致。
  6. @ResponseStatus: 指定HTTP响应状态码。
3.5.3 主要思路和注意事项
  • 主要思路: 集中处理,分类处理,结构化响应,日志记录。
  • 注意事项: 异常类型顺序,日志级别,错误信息用户友好性,HTTP状态码正确使用,ResponseAdviceExceptionAdvice协同,避免吞掉异常。

3.6 技术点六:前端技术与前后端交互

项目包含静态资源,通过Ajax与后端API交互。

3.6.1 相关代码示例

static 目录结构 (推测): /static/css/, /static/js/jquery.min.js, /static/editor.md/
前端 JavaScript (示例片段 - Ajax调用):

// 用户登录
$.ajax({url: "/user/login", type: "POST", contentType: "application/json",data: JSON.stringify({ userName: username, password: password }),success: function(response) { localStorage.setItem("authToken", response.data.token); /* ... */ }
});
// 使用 Editor.md
var editor = editormd("editormd-div", { path: "../editor.md/lib/" });
3.6.2 应用方式和原因
  1. 静态资源服务: Spring Boot默认从static目录提供。
  2. jQuery: 简化DOM操作和Ajax。
  3. Ajax: 异步调用后端API。
  4. Token 认证: 前端存储Token并在请求头发送。
  5. Editor.md (推测): Markdown编辑器。
  6. 前后端分离 (部分): 后端API,前端渲染。
3.6.3 主要思路和注意事项
  • 主要思路: 前端页面构建,Ajax数据请求,数据渲染,用户交互,简单状态管理。
  • 注意事项: API设计一致性,前端错误处理,安全性 (XSS, CSRF),前端路由 (本项目可能简单页面跳转),依赖管理,浏览器兼容性,性能优化。

4. 数据库设计

本项目的数据库设计主要围绕用户 (user_info) 和博客文章 (blog_info) 两个核心实体展开。以下是对数据库表结构、表间关系及关键字段的描述。这些信息主要从项目的 POJO (Plain Old Java Object) 数据对象类 (UserInfo.java, BlogInfo.java) 中推断得出,这些类通常直接映射到数据库表。

4.1 数据库表结构的设计

4.1.1 用户表 (user_info)

此表存储用户的基本信息,用于用户登录认证和信息展示。

对应实体类: org.example.springblogdemo2.pojo.dataobject.UserInfo.java

字段名数据类型 (推测)Java 类型注解/说明
idINT PRIMARY KEY AUTO_INCREMENTInteger@TableId(type = IdType.AUTO) - 用户唯一标识,主键,自增
user_nameVARCHAR(255)String用户名,用于登录
passwordVARCHAR(255)String加密后的用户密码
github_urlVARCHAR(255)String(可选) 用户的 GitHub 主页链接
delete_flagINT / TINYINTInteger@TableLogic - 逻辑删除标记 (0:正常, 1:删除)
create_timeDATETIMELocalDate记录创建时间
update_timeDATETIMELocalDate记录更新时间

SQL DDL (推测):

CREATE TABLE `user_info` (`id` INT NOT NULL AUTO_INCREMENT COMMENT '用户ID',`user_name` VARCHAR(255) NOT NULL COMMENT '用户名',`password` VARCHAR(255) NOT NULL COMMENT '加密密码',`github_url` VARCHAR(255) NULL COMMENT 'GitHub链接',`delete_flag` INT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记 (0:正常, 1:删除)',`create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE INDEX `idx_username` (`user_name` ASC) VISIBLE
) COMMENT = '用户信息表';
4.1.2 博客文章表 (blog_info)

此表存储博客文章的详细内容、作者信息及状态。

对应实体类: org.example.springblogdemo2.pojo.dataobject.BlogInfo.java

字段名数据类型 (推测)Java 类型注解/说明
idINT PRIMARY KEY AUTO_INCREMENTInteger@TableId(type = IdType.AUTO) - 文章唯一标识,主键,自增
titleVARCHAR(255)String文章标题
contentTEXTString文章内容 (Markdown 或 HTML)
user_idINTInteger作者的用户 ID,外键关联 user_info 表的 id
delete_flagINT / TINYINTInteger@TableLogic - 逻辑删除标记 (0:正常, 1:删除)
create_timeDATETIMEDate文章创建时间
update_timeDATETIMELocalDateTime文章最后更新时间

SQL DDL (推测):

CREATE TABLE `blog_info` (`id` INT NOT NULL AUTO_INCREMENT COMMENT '博客ID',`title` VARCHAR(255) NOT NULL COMMENT '博客标题',`content` TEXT NOT NULL COMMENT '博客内容',`user_id` INT NOT NULL COMMENT '作者ID (外键关联 user_info.id)',`delete_flag` INT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记 (0:正常, 1:删除)',`create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),INDEX `idx_user_id` (`user_id` ASC) VISIBLE,CONSTRAINT `fk_blog_user`FOREIGN KEY (`user_id`)REFERENCES `user_info` (`id`)ON DELETE RESTRICT  -- 或 ON DELETE CASCADE,取决于业务需求ON UPDATE CASCADE
) COMMENT = '博客文章信息表';

4.2 表与表之间的关系

本项目中主要存在以下表间关系:

  • user_infoblog_info 是一对多关系:
    • 一个用户 (user_info) 可以发表多篇博客文章 (blog_info)。
    • 一篇博客文章 (blog_info) 只属于一个用户 (user_info)。
    • 这种关系通过 blog_info 表中的 user_id 字段实现,该字段作为外键指向 user_info 表的 id 主键。

4.3 关键字段的含义

  • id (在两个表中):

    • 含义:记录的唯一标识符,主键。
    • 说明:通常设置为自增,由数据库自动生成,确保每条记录的唯一性。
  • user_name (user_info 表):

    • 含义:用户的登录名。
    • 说明:具有唯一性约束,用于用户身份识别。
  • password (user_info 表):

    • 含义:用户密码。
    • 说明:存储的是经过哈希加密后的密码字符串,不存储明文密码,以保证安全性。
  • delete_flag (在两个表中):

    • 含义:逻辑删除标记。
    • 说明:用于实现软删除。值为 0 (或配置的未删除值) 表示记录有效,值为 1 (或配置的已删除值) 表示记录已被逻辑删除,在常规查询中通常会被过滤掉。MyBatis-Plus 的 @TableLogic 注解可以自动处理逻辑删除的查询和更新操作。
  • user_id (blog_info 表):

    • 含义:文章作者的用户 ID。
    • 说明:外键,关联到 user_info 表的 id 字段,表明该文章的作者是谁。
  • title (blog_info 表):

    • 含义:博客文章的标题。
  • content (blog_info 表):

    • 含义:博客文章的主要内容。
    • 说明:通常存储 Markdown 格式的文本或经过 Markdown 解析后的 HTML 文本。
  • create_timeupdate_time (在两个表中):

    • 含义:记录的创建时间和最后更新时间。
    • 说明:用于审计和追踪记录的生命周期。create_time 通常在记录插入时设置,update_time 在记录每次更新时自动更新 (可以通过数据库触发器或 ORM 框架的特性实现,如 MyBatis-Plus 的自动填充功能)。

该数据库设计简洁明了,满足了基本的博客系统功能需求。如果未来需要扩展功能,例如评论、标签、分类等,则需要相应地增加新的表并建立关联。


5. 部署与运行

本节将简述 spring-blog-demo-2 项目的部署流程、运行环境要求和基本配置。这些信息主要基于 Spring Boot 项目的通用实践以及项目本身的结构和配置文件 (pom.xml, application.properties 等) 推断得出。

5.1 项目的部署流程

作为一个典型的 Spring Boot 项目,spring-blog-demo-2 可以通过多种方式部署,最常见的是将其打包成一个可执行的 JAR 文件。

  1. 环境准备:

    • 确保目标服务器已安装 Java 运行环境 (JRE 或 JDK),版本需与项目 pom.xml 中指定的 java.version (本项目为 Java 17) 兼容或更高。
    • 确保目标服务器已安装并运行 MySQL 数据库服务,并且网络可访问。
    • 根据 application.properties (或 application.yml) 中的数据库连接信息,在 MySQL 中创建对应的数据库和表结构 (如上一节所述的 user_infoblog_info 表)。
  2. 项目打包:

    • 在项目根目录下 (包含 pom.xml 文件的目录),执行 Maven 打包命令:
      mvn clean package
      
    • 此命令会执行项目的清理、编译、测试 (可跳过测试:mvn clean package -DskipTests) 并最终在 target/ 目录下生成一个可执行的 JAR 文件,文件名通常为 spring-blog-demo-2-0.0.1-SNAPSHOT.jar (根据 pom.xml 中的 artifactIdversion)。
    • Spring Boot Maven 插件 (spring-boot-maven-plugin) 会将所有依赖项和嵌入式 Web 服务器 (如 Tomcat) 打包到这个 JAR 文件中,使其成为一个“胖 JAR”(fat JAR)。
  3. 配置文件准备:

    • Spring Boot 项目的配置文件是 src/main/resources/application.properties (或 application.yml)。
    • 在部署时,可以通过多种方式覆盖或指定外部配置文件:
      • 与 JAR 同目录的 application.properties 将修改后的 application.properties 文件放置在 JAR 文件所在的同一目录下。外部配置文件会覆盖 JAR 包内部的同名配置。
      • 与 JAR 同目录的 config/ 子目录下的 application.properties 将配置文件放在 JAR 文件同级目录的 config/ 子目录下。
      • 通过命令行参数指定: 在启动命令中使用 --spring.config.location 参数指定外部配置文件的路径,例如:
        java -jar spring-blog-demo-2-0.0.1-SNAPSHOT.jar --spring.config.location=file:/path/to/your/custom-application.properties
        
    • 关键配置项包括数据库连接信息 (spring.datasource.url, spring.datasource.username, spring.datasource.password),服务器端口 (server.port,默认为 8080) 等。
  4. 项目运行:

    • 将打包好的 JAR 文件和准备好的外部配置文件 (如果使用) 上传到目标服务器。
    • 在服务器上,通过命令行启动项目:
      java -jar spring-blog-demo-2-0.0.1-SNAPSHOT.jar
      
    • 如果需要指定特定的 Spring Profile (如 devprod,在 pom.xml 中有定义,但 application.properties 中未见明显使用profile特定配置),可以使用:
      java -jar spring-blog-demo-2-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
      
    • 项目启动后,可以通过 http://<服务器IP>:<端口号> (例如 http://localhost:8080 如果在本地运行且端口为8080) 访问。
  5. 后台运行与进程管理 (可选但推荐):

    • 为了使应用在后台持续运行,可以使用 nohup 命令和 &
      nohup java -jar spring-blog-demo-2-0.0.1-SNAPSHOT.jar > app.log 2>&1 &
      
      日志会输出到 app.log 文件。
    • 更健壮的方式是使用进程管理工具,如 Systemd (Linux)、Supervisor,或者将应用容器化 (使用 Docker)。

5.2 项目运行的环境要求和配置

5.2.1 环境要求
  • Java: JDK 17 或更高版本 (根据 pom.xml 中的 <java.version>17</java.version>)。
  • Maven: 用于项目构建和打包 (例如 Maven 3.6.x 或更高版本)。
  • 数据库: MySQL 数据库 (版本如 5.7.x, 8.0.x)。需要确保数据库服务正在运行,并且项目配置的数据库用户具有对目标数据库的读写权限。
  • 操作系统: 任何支持 Java 运行的操作系统 (如 Linux, Windows, macOS)。
5.2.2 核心配置 (application.propertiesapplication.yml)

以下是项目中需要关注的核心配置项 (通常在 src/main/resources/application.properties 文件中定义):

  • 服务器配置:

    • server.port: 应用监听的端口号 (例如 server.port=8080)。如果未指定,默认为 8080。
  • 数据库连接配置:

    • spring.datasource.url: 数据库连接 URL (例如 jdbc:mysql://localhost:3306/spring_blog_demo2?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai)。
    • spring.datasource.username: 数据库用户名。
    • spring.datasource.password: 数据库密码。
    • spring.datasource.driver-class-name: 数据库驱动类名 (例如 com.mysql.cj.jdbc.Driver for MySQL 8+)。
  • MyBatis-Plus 配置 (可选,部分可以在代码中配置):

    • mybatis-plus.mapper-locations: Mapper XML 文件路径 (如果使用 XML 映射,本项目似乎主要使用注解和 BaseMapper)。
    • mybatis-plus.type-aliases-package: 实体类别名包路径。
    • mybatis-plus.global-config.db-config.logic-delete-field: 全局逻辑删除字段名 (如 delete_flag)。
    • mybatis-plus.global-config.db-config.logic-delete-value: 逻辑删除值 (如 1)。
    • mybatis-plus.global-config.db-config.logic-not-delete-value: 逻辑未删除值 (如 0)。
  • JWT 配置 (如果密钥或有效期从配置读取):

    • jwt.secret: JWT 签名密钥 (如 JwtUtils.java 中的 SECRET,强烈建议从配置读取)。
    • jwt.expiration: JWT 有效期 (如 JwtUtils.java 中的 EXPIRATION)。
  • 日志配置 (可选,Spring Boot 有默认日志配置):

    • logging.level.<package>: 设置特定包的日志级别 (例如 logging.level.org.example.springblogdemo2=DEBUG)。
    • logging.file.name / logging.file.path: 配置日志输出到文件。

注意: 实际的 application.properties 文件内容未在项目压缩包的顶层直接提供,通常位于 src/main/resources/ 目录下。上述配置项是基于 Spring Boot 和项目所用技术的典型配置。部署时,应确保这些配置项根据目标环境进行了正确设置。

该项目由于使用了 spring-boot-starter-web,内置了 Tomcat 作为 Web 服务器,因此无需单独部署 WAR 包到外部 Tomcat 服务器,直接运行 JAR 文件即可启动整个应用。


项目链接

http://www.dtcms.com/a/190153.html

相关文章:

  • k8s监控方案实践补充(一):部署Metrics Server实现kubectl top和HPA支持
  • iOS WebView和WKWebView怎么调试?
  • 行业趋势与技术创新:驾驭工业元宇宙与绿色智能制造
  • Large-Scale Language Models: In-Depth Principles and Pioneering Innovations
  • 【Linux网络】————详解TCP三次握手四次挥手
  • 【android bluetooth 协议分析 12】【A2DP详解 1】【车机侧蓝牙音乐免切源介绍】
  • AI时代的弯道超车之第八章:具体分享几个AI实际操作方法和案例
  • Kotlin Multiplatform与Flutter、Compose共存:构建高效跨平台应用的完整指南
  • [Spring]-组件的生命周期
  • 碎片笔记|AI生成图像溯源方法源码复现经验(持续更新中……)
  • 设计模式-中介者模式
  • 研读论文《Attention Is All You Need》(4)
  • 【Oracle专栏】清理告警日志、监听日志
  • 如何创建自动工作流程拆分Google Drive中的PDF文件
  • 【kafka】kafka概念,使用技巧go示例
  • 【!!!!终极 Java 中间件实战课:从 0 到 1 构建亿级流量电商系统全链路解决方案!!!!保姆级教程---超细】
  • 试除法判断素数优化【C语言】
  • 解决docker alpine缺少字体的问题 Could not initialize class sun.awt.X11FontManager
  • 使用 Docker Desktop 安装 Neo4j 知识图谱
  • 面试--HTML
  • scikit-learn在无监督学习算法的应用
  • 网络协议分析 实验五 UDP-IPv6-DNS
  • Leetcode (力扣)做题记录 hot100(62,64,287,108)
  • Java 虚拟线程(Virtual Threads):原理、性能提升与实践
  • Vue 图片预览功能(含缩略图)
  • 5.14本日总结
  • 常见 RPC 协议类别对比
  • WEB安全--Java安全--CC1利用链
  • 如何迁移 WSL 卸载 Ubuntu WSL
  • 5.18-AI分析师