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

发布于:2025-05-16 ⋅ 阅读:(14) ⋅ 点赞:(0)

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 {
    @Autowired
    private UserInfoMapper userInfoMapper;

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

    @Override
    public 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);
    }

    @Override
    public 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
    }

    @Override
    public 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 value
  private 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 BlogInfo
    boolean 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 {
    @Autowired
    private BlogInfoMapper blogInfoMapper;

    @Override
    public 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());
    }

    @Override
    public BlogInfoResponse getBlogDetail(Integer blogId) {
        return BeanTransUtils.trans(getBlogInfo(blogId));
    }

    @Override
    public 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);
    }

    @Override
    public 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("内部错误,请联系管理员");
        }
    }

    @Override
    public Boolean updateBlog(UpdateRequest updateRequest) {
        BlogInfo blogInfo = BeanTransUtils.trans(updateRequest); // Converts UpdateRequest to BlogInfo
        try {
            Integer result = blogInfoMapper.updateById(blogInfo);
            return result == 1;
        } catch (Exception exception) {
            log.error("编辑博客失败,e: ", exception);
            throw new BlogException("内部错误,请联系管理员");
        }
    }

    @Override
    public Boolean deleteBlog(Integer blogId) {
        BlogInfo blogInfo = new BlogInfo();
        blogInfo.setId(blogId);
        blogInfo.setDeleteFlag(Constants.BLOG_DELETE); // Logical delete
        try {
            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 ID
    private Integer deleteFlag; // 0 for normal, 1 for deleted
    private 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 string
    public String getCreateTime() {
        return DateUtils.dateFormat(createTime);
    }
    
    // This method seems to be for display purposes, might not be directly from DB field
    public 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 和 @Validated
        log.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> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public 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;
  @TableLogic
  private 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 {
    @Override
    public 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;
    @Override
    public 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 类型 注解/说明
id INT PRIMARY KEY AUTO_INCREMENT Integer @TableId(type = IdType.AUTO) - 用户唯一标识,主键,自增
user_name VARCHAR(255) String 用户名,用于登录
password VARCHAR(255) String 加密后的用户密码
github_url VARCHAR(255) String (可选) 用户的 GitHub 主页链接
delete_flag INT / TINYINT Integer @TableLogic - 逻辑删除标记 (0:正常, 1:删除)
create_time DATETIME LocalDate 记录创建时间
update_time DATETIME LocalDate 记录更新时间

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 类型 注解/说明
id INT PRIMARY KEY AUTO_INCREMENT Integer @TableId(type = IdType.AUTO) - 文章唯一标识,主键,自增
title VARCHAR(255) String 文章标题
content TEXT String 文章内容 (Markdown 或 HTML)
user_id INT Integer 作者的用户 ID,外键关联 user_info 表的 id
delete_flag INT / TINYINT Integer @TableLogic - 逻辑删除标记 (0:正常, 1:删除)
create_time DATETIME Date 文章创建时间
update_time DATETIME LocalDateTime 文章最后更新时间

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 文件即可启动整个应用。


项目链接


网站公告

今日签到

点亮在社区的每一天
去签到