【实战】博客系统:项目公共模块 + 博客列表的实现

文章目录

1.准备工作

1.1数据准备

sql 复制代码
-- 建表SQL
create database if not exists java_blog_spring charset utf8mb4;

use java_blog_spring;
-- 用户表
DROP TABLE IF EXISTS java_blog_spring.user_info;
CREATE TABLE java_blog_spring.user_info(
        `id` INT NOT NULL AUTO_INCREMENT,
        `user_name` VARCHAR ( 128 ) NOT NULL,
        `password` VARCHAR ( 128 ) NOT NULL,
        `github_url` VARCHAR ( 128 ) NULL,
        `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now(),
        PRIMARY KEY ( id ),
UNIQUE INDEX user_name_UNIQUE ( user_name ASC )) ENGINE = INNODB DEFAULT CHARACTER 
SET = utf8mb4 COMMENT = '用户表';

-- 博客表
drop table if exists java_blog_spring.blog_info;
CREATE TABLE java_blog_spring.blog_info (
  `id` INT NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(200) NULL,
  `content` TEXT NULL,
  `user_id` INT(11) NULL,
  `delete_flag` TINYINT(4) NULL DEFAULT 0,
  `create_time` DATETIME DEFAULT now(),
  `update_time` DATETIME DEFAULT now() ON UPDATE now(),
  PRIMARY KEY (id))
ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '博客表';

-- 新增用户信息
insert into java_blog_spring.user_info (user_name, password,github_url)values("zhangsan","123456","https://gitee.com/bubble-fish666/class-java45");
insert into java_blog_spring.user_info (user_name, password,github_url)values("lisi","123456","https://gitee.com/bubble-fish666/class-java45");

insert into java_blog_spring.blog_info (title,content,user_id) values("第一篇博客","111我是博客正文我是博客正文我是博客正文",1);
insert into java_blog_spring.blog_info (title,content,user_id) values("第二篇博客","222我是博客正文我是博客正文我是博客正文",2);

1.2 创建项目

创建SpringBoot项目,添加Spring MVC 和 MyBatis 对应依赖

java 复制代码
<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
			<version>3.5.5</version>
		</dependency>

1.3 准备前端界面和配置文件

java 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
  file:
    name: spring-blog.log

2.项目公共模块

项目分为控制层(Controller),服务层(Service),持久层(Mapper).各层之间的调用关系如下:

我们先根据需求完成公共层代码的编写

1. 统一返回结果实体类

复制代码
a. code:业务状态码
    - 200:业务处理成功
    - -1:业务处理失败
    - 后续有其他异常信息,可以再补充.
b. errMsg:业务处理失败时,返回的错误信息
c. data:业务返回数据

定义业务状态枚举,可以根据自己需求再增加

java 复制代码
@AllArgsConstructor
public enum ResultCodeEnum {
    SUCCESS(200),
    FAIL(-1);
    //实际开发中,需要分的更细
    @Setter @Getter
    private int code;
}
java 复制代码
@Data
public class Result {

    private ResultCodeEnum code;//业务状态码,非http状态码
    private String errMsg;
    private Object data;

    public static Result success(Object data){
        Result result = new Result();
        result.setCode(ResultCodeEnum.SUCCESS);
        result.setData(data);
        return result;
    }

    public static Result fail(String errMsg){
        Result result = new Result();
        result.setCode(ResultCodeEnum.FAIL);
        result.setErrMsg(errMsg);
        return result;
    }
    public static Result fail(String errMsg,Object data){
        Result result = new Result();
        result.setCode(ResultCodeEnum.FAIL);
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }

}

2.统一返回结果

java 复制代码
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType,Class converterType){
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof  String){
            return objectMapper.writeValueAsString(Result.success(body));
        }
        if(body instanceof Result){
            return body;
        }
        return Result.success(body);
    }

}


3.定义项目异常

java 复制代码
@Data
public class BlogException extends RuntimeException{
    private int code;
    private String errMsg;

    public BlogException(int code,String errMsg){
        this.code = code;
        this.errMsg = errMsg;
    }
}

4.统一异常处理

java 复制代码
@Slf4j
@ResponseBody
@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler
    public Result exceptionHandler(Exception exception){
        log.error("发生异常,e:",exception);
        return Result.fail(exception.getMessage());
    }
}

3. 业务代码

3.1 持久层

我们博客系统使用MyBatisPlus来完成持久层代码的开发,创建mapper实现BaseMapper即可

  • 实体类
java 复制代码
@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;
}
java 复制代码
@Data
public class BlogInfo {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    private Integer deleteFlag;
    private Integer createTime;
    private Integer updateTime;
}
  • Mapper
java 复制代码
@Mapper
public interface BlogInfoMapper extends BaseMapper<BlogInfo> {
}
java 复制代码
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

3.2 实现博客列表功能

约定前后端交互接口

复制代码
[请求]
/blog/getList GET
[响应]
{
  "code": 200,
  "errMsg": null,
  "data": [{
    "id": 1,
    "title": "第一篇博客",
    "content": "111我是博客正文我是博客正文我是博客正文",
    "userId": 1,
    "updateTime": "2024-08-22 11:27:03"
  },
  ......
  ]
}

客户端给服务器发送一个 /blog/getlist 这样的 HTTP 请求,服务器给客户端返回了一个 JSON 格式的数据.

实现服务器代码

    1. 定义接口返回实体
java 复制代码
@Data
public class BlogInfoResponse {
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDate createTime;
}

Java官方文档

通过@JsonFormat设置页面返回的日期格式

格式参考 java.text.SimpleDateFormat 官方文档

  • 实现Controller
java 复制代码
@Slf4j
@RequestMapping("/blog")
@RestController
public class BlogController {
    @Resource(name = "blogServiceImpl")
    private BlogService blogService;

//    @Autowired
//    private BlogServiceImpl blogService;

    @RequestMapping("/getList")
    public List<BlogInfoResponse> getList(){
        log.info("获取博客列表..");
        List<BlogInfoResponse> blogInfos = blogService.getList();
        return blogInfos;
    }
}
  • 两种注入方式对比(注释了 @Autowired,用了 @Resource):
注解 来源 注入依据 注意事项
@Resource JDK 原生注解 先按 name 匹配,再按类型 这里 name = "blogServiceImpl" 表示注入 Spring 容器中名称为 blogServiceImpl 的 Bean(即 BlogServiceImpl 实现类的实例,默认首字母小写)。
@Autowired Spring 注解 先按类型匹配,再按名称 若注释的代码启用(@Autowired private BlogServiceImpl blogService),是按类型注入 BlogServiceImpl 实例(不推荐直接注入实现类,更推荐注入接口 BlogService,符合面向接口编程)。
  • 核心原则:Controller 应依赖 Service 接口(BlogService)而非实现类(BlogServiceImpl),这样后续替换 BlogService 的实现类时,Controller 无需修改代码(解耦)。你当前的 @Resource 注入接口是正确的。

  • 实现Service

基于SOA理念,Service采用接口对外提供服务,实现类用Impl的后缀与接口区别.

SOA(Service-Oriented Architecture,面向服务的架构)是一种高层级的架构设计理念,可通过在网络上使用基于通用通信语言的服务接口,让软件组件可重复使用

java 复制代码
public interface BlogService {
    List<BlogInfoResponse> getList();
}
java 复制代码
@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,0);
        List<BlogInfo> blogInfos = blogInfoMapper.selectList(queryWrapper);
        List<BlogInfoResponse> blogInfoResponses = blogInfos.stream().map(blogInfo -> {
            BlogInfoResponse response = new BlogInfoResponse();
            BeanUtils.copyProperties(blogInfo,response);
            return response;
        }).collect(Collectors.toList());
        return blogInfoResponses;
    }
}
java 复制代码
@Override
public List<BlogInfoResponse> getList() {
    // 步骤 1:创建 MyBatis-Plus 的查询条件构造器,用于拼接查询条件
    QueryWrapper<BlogInfo> queryWrapper = new QueryWrapper<>();
    
    // 步骤 2:设置查询条件:删除标记(deleteFlag)等于 0(即查询未删除的博客)
    queryWrapper.lambda().eq(BlogInfo::getDeleteFlag, 0);
    
    // 步骤 3:调用 Mapper 层方法,执行查询,获取数据库中的原始博客数据(BlogInfo 是数据库表对应的实体类)
    List<BlogInfo> blogInfos = blogInfoMapper.selectList(queryWrapper);
    
    // 步骤 4:将数据库实体类(BlogInfo)转换为响应DTO(BlogInfoResponse),再返回给 Controller
    List<BlogInfoResponse> blogInfoResponses = blogInfos.stream().map(blogInfo -> {
        BlogInfoResponse response = new BlogInfoResponse();
        // 复制 BlogInfo 的属性到 BlogInfoResponse(如 id、title、content 等同名属性)
        BeanUtils.copyProperties(blogInfo, response);
        return response;
    }).collect(Collectors.toList());
    
    // 步骤 5:返回转换后的响应数据
    return blogInfoResponses;
}

postman 测试

实现客户端代码

修改 blog_list.html,删除之前写死的博客内容(即 <div class="blog"> ),并新增 js 代码处理ajax请求.

  • 使用 ajax给服务器发送 HTTP 请求.
  • 服务器返回的响应是一个 JSON 格式的数据,根据这个响应数据使用 DOM API 构造页面内容.
  • 跳转到博客详情页的 url 形如 blog_detail.html?blogId=1 这样就可以让博客详情页知道当前是要访问哪篇博客.
html 复制代码
$.ajax({
            type: "get",
            url: "/blog/getList",
            success: function(result){
                //针对后端结果,进行简单校验
                if(result.code == "SUCCESS" && result.data !=null && result.data.length>0){
                    var finalHtml = "";
                    for(var blogInfo of result.data){
                        for(var blogInfo of result.data){
                                finalHtml += '<div class="blog">';
                                finalHtml += '<div class="title">'+blogInfo.title+'</div>';
                                finalHtml += '<div class="date">'+blogInfo.createTime+'</div>';
                                finalHtml += '<div class="desc">'+blogInfo.content+'</div>';
                                finalHtml += '<a class="detail" href="blog_detail.html?blogId='+blogInfo.id+'">查看全文&gt;&gt;</a>';
                                finalHtml += '</div>'
                                }
                                $(".container .right").html(finalHtml);
       }
 }
   }
  })
相关推荐
伯明翰java2 小时前
Redis学习笔记-List列表(2)
redis·笔记·学习
阿里云大数据AI技术2 小时前
【跨国数仓迁移最佳实践 12】阿里云 MaxCompute 实现 BigQuery 10 万条 SQL 智能转写迁移
大数据·sql
光泽雨3 小时前
python学习基础
开发语言·数据库·python
让学习成为一种生活方式3 小时前
Pfam 数据库详解--生信工具60
数据库
q***49863 小时前
数据库操作与数据管理——Rust 与 SQLite 的集成
数据库·rust·sqlite
AA陈超3 小时前
使用UnrealEngine引擎,实现鼠标点击移动
c++·笔记·学习·ue5·虚幻引擎
川西胖墩墩4 小时前
流程图在算法设计中的实战应用
数据库·论文阅读·人工智能·职场和发展·流程图
IMPYLH5 小时前
Lua 的 assert 函数
开发语言·笔记·junit·单元测试·lua
离离茶5 小时前
【笔记1-8】Qt bug记录:QListWidget窗口的浏览模式切换为ListMode后,滚轮滚动速度变慢
笔记·qt·bug
San30.5 小时前
基于 DeepSeek 的智能工具调用:让大模型与真实世界连接
人工智能·python·交互