文章目录
- 1.准备工作
-
- 1.1数据准备
- [1.2 创建项目](#1.2 创建项目)
- [1.3 准备前端界面和配置文件](#1.3 准备前端界面和配置文件)
- 2.项目公共模块
- [3. 业务代码](#3. 业务代码)
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 格式的数据.
实现服务器代码
-
- 定义接口返回实体
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;
}
通过@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+'">查看全文>></a>';
finalHtml += '</div>'
}
$(".container .right").html(finalHtml);
}
}
}
})
