1.博客系统简要分析
一共有6个网页,分别是博客列表页面,博客详情页面,发布博客页面,博客登陆页面,博客更新页面,修改个人信息页面(暂未实现),我们要实现的功能有,实现博客列表的展示页面,博客详情页面的展示功能,用户登录功能,显示用户信息功能,编辑博客功能,发布博客功能,删除博客功能,退出登录功能
我们现在就开始写吧
2.创建springBoot项目
1.创建项目,勾选需要的依赖
2.删除无用的目录及文件
4.创建框架
3.配置文件(选择yml)
application.xml
spring:
profiles:
active: dev
logging:
file:
path: logs/
level:
root: info
application-dev.xml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #自动驼峰转换
mapper-locations: classpath:mapper/***Mapper.xml
application-prod.xml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis: #上线就不用打印mybatis执行日志
configuration:
map-underscore-to-camel-case: true #自动驼峰转换
3.数据库准备
这个项目的数据库表比较简单
只有两个表
1.设计表结构
2.建表sql
-- 建表SQL
create database if not exists `java_blog_spring1` charset utf8mb4;
-- 用户表
drop table if exists `java_blog_spring`.`user`;
CREATE TABLE `java_blog_spring`.`user` (
`id` INT NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(128) NOT NULL,
`password` VARCHAR(128) NOT NULL,
`photo` VARCHAR(128) NOT NULL,
`github_url` VARCHAR(128) NULL,
`delete_flag` TINYINT(4) NULL DEFAULT 0,
`create_time` TIMESTAMP NULL DEFAULT current_timestamp(),
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`;
CREATE TABLE `java_blog_spring`.`blog` (
`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` TIMESTAMP NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`))
ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '博客表';
-- 新增用户信息
insert into `java_blog_spring`.`user` (`user_name`, `password`,`photo`,`github_url`)values
("zhangsan","123456","pic/doge.jpg","https://gitee.com/bubble-fish666/class-java45");
insert into `java_blog_spring`.`user` (`user_name`, `password`,`photo`,`github_url`)values
("lisi","123456","pic/doge.jpg","https://gitee.com/bubble-fish666/class-java45");
insert into `java_blog_spring`.`blog` (`title`,`content`,`user_id`) values
("第一篇博客","111我是博客正文我是博客正文我是博客正文",1);
insert into `java_blog_spring`.`blog` (`title`,`content`,`user_id`) values
("第一篇博客","222我是博客正文我是博客正⽂我是博客正文",2);
use java_blog_spring;
-- 查询两个表的数据
select * from user;
select * from blog;
4.接口设计
- 获取所有的博客列表
5.数据库相关查询操作
- 根据用户id查询用户信息
- 根据用户名称查询用户
- 查询所有未删除的博客(按照时间降序排列)
- 根据博客id查询博客详情
- 插入一条博客
- 根据博客id更新博客
- 删除博客
- 根据用户id查询博客数量
6.写代码准备工作
1.model包下,创建Java实体类
我们在配置文件中配置了数据库表字段到类属性的自动驼峰转换,所以可以不用进行重命名
自动驼峰映射
configuration:
map-underscore-to-camel-case: true
1.User类
@Data
public class User {
// java中属性使用小驼峰命名
// 我们配置了自动驼峰转换
private Integer id;
private String userName;
private String passWord;
private String photo;
private String githubUrl;
private Byte deleteFlag;
private Date createTime;
}
2.Blog类
@Data
@Data
public class Blog {
private Integer id;
private String title;
private String content;
private Integer userId;
private Integer deleteFlag;
private Date createTime;
}
2.mapper层
- 简单的sql语句我们使用注解实现
- 复杂的sql语句使用xml配置文件实现
1.userMapper接口
@Mapper
public interface UserMapper {
/**
* 根据用户id查询用户信息
* @param id
* @return
*/
@Select("select user_name, password, photo, github_url from user where delete_flag = 0 and id = #{id}")
User selectById(Integer id);
/**
* 根据用户名称查询用户
* @param userName
* @return
*/
@Select(("select user_name, password, photo, github_url from user where delete_flag = 0 and user_name = #{userName}"))
User selectByName(String userName);
}
2.BlogMapper接口
@Mapper
public interface BlogMapper {
/**
* 查询所有未删除的博客.按照时间降序排列
* @return
*/
@Select("select id, title, content, user_id, create_time from blog where delete_flag = 0 order by create_time;")
List<Blog> selectAllBlog();
/**
* 根据博客id查询博客详情
* @param blogId
* @return
*/
@Select("select id, title, content, user_id, create_time from blog where delete_flag = 0 and id = #{blogId}")
Blog selectByBlogId(Integer blogId);
/**
* 插入一条博客
* @param blog
* @return
*/
@Insert("insert into blog (title, content, user_id) values (#{title, #{content}, #{userId})")
Integer insertBlog(Blog blog);
/**
* 根据博客id更新博客
* 删除博客就是把delete_id改为1
* @return
*/
Integer updateBlog(Blog blog);
/**
* 根据用户id查询博客数量
* @param userId
* @return
*/
@Select("select count(id) from blog where delete_flag = 0 and user_id = #{userId}")
Integer selectBlogCount(Integer userId);
}
因为更新博客内容的sql语句比较复杂,我们就不采用注解的方式,使用配置文件的方式来写
3.BlogMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springblog.mapper.BlogMapper">
<update id="updateBlog">
update blog
<set>
<if test="title!=null">title=#{title},</if>
<if test="content!=null">content=#{content},</if>
<if test="deleteFlag!=null">delete_flag=#{deleteFlag},</if>
</set>
<where>
id = #{id}
</where>
</update>
</mapper>
3.Service包下
service层被称作业务层,它用来处理逻辑上的业务,而不去考虑具体的实现,这样controller层就不会直接去调用mapper层,可以将代码解耦,便于扩展
1.UserService类
@Service
public class UserService {
@Autowired
// 将UserMapper对象注入进来
private UserMapper userMapper;
/**
* 根据用户id查询用户信息
* @param id
* @return
*/
public User selectById(Integer id) {
return userMapper.selectById(id);
}
/**
* 根据用户名称查询用户
* @param userName
* @return
*/
public User selectByName(String userName) {
return userMapper.selectByName(userName);
}
}
2.BlogService类
@Service
public class BlogService {
@Autowired
private BlogMapper blogMapper;
/**
* 查询所有未删除的博客.按照时间降序排列
* @return
*/
public List<Blog> selectAllBlog() {
return blogMapper.selectAllBlog();
}
/**
* 根据博客id查询博客详情
* @param blogId
* @return
*/
public Blog selectByBlogId(Integer blogId) {
return blogMapper.selectByBlogId(blogId);
}
/**
* 插入一条博客
* @param blog
* @return
*/
public Integer insertBlog(Blog blog) {
return blogMapper.insertBlog(blog);
}
/**
* 根据博客id更新博客
* 删除博客就是把delete_id改为1
* @return
*/
public Integer updateBlog(Blog blog) {
return blogMapper.updateBlog(blog);
}
/**
* 根据用户id查询博客数量
* @param userId
* @return
*/
public Integer selectBlogCount(Integer userId) {
return blogMapper.selectBlogCount(userId);
}
}
4.测试
使用BlogMapper做演示,UserMapper同理
在mapper接口点击Fn+Alt+Insert(按钮因电脑而异,不行可以试下Alt+Insert)
然后在弹出框中点击Test
然后勾选需要测试的方法
此时就可以看到test包下出现了对应的类
然后我们就可以在这里写测试方法
1.BlogMapperTest测试类
@SpringBootTest
class BlogMapperTest {
@Autowired
private BlogService blogService;
@Test
void selectAllBlog() {
List<Blog> blogs = blogService.selectAllBlog();
System.out.println(blogs.toString());
}
@Test
void selectByBlogId() {
System.out.println(blogService.selectByBlogId(2).toString());
}
@Test
void insertBlog() {
Blog blog = new Blog();
blog.setTitle("测试");
blog.setContent("测试正文");
blog.setUserId(1);
System.out.println(blogService.insertBlog(blog));
}
@Test
void updateBlog() {
Blog blog = new Blog();
blog.setTitle("测试更新");
blog.setId(1);
System.out.println(blogService.updateBlog(blog));
}
@Test
void deleteBlog() {
Blog blog = new Blog();
blog.setDeleteFlag(1);
blog.setId(1);
System.out.println(blogService.updateBlog(blog));
}
@Test
void selectBlogCount() {
System.out.println(blogService.selectBlogCount(2));
}
}
2.UserMapperTest测试类
@SpringBootTest
class UserMapperTest {
@Autowired
private UserService userService;
@Test
void selectById() {
System.out.println(userService.selectById(1).toString());
}
@Test
void selectByName() {
System.out.println(userService.selectByName("zhangsan").toString());
}
}
3.测试结果
5.添加前端界面
把之前写好的博客系统静态页面拷贝到static⽬录下
6.添加公共模块
⼯具层(common) => 统⼀返回类, 统⼀异常处理类
1.添加统一返回类Result
@Data
public class Result {
private Integer code;
private String msg;
private Object data;
/**
* 业务执行成功返回的数据
* @return
*/
public static Result success(String msg, Object data) {
Result result = new Result();
result.setCode(200);
result.setMsg(msg);
result.setData(data);
return result;
}
/**
* 业务执行成功返回的数据
* @return
*/
public static Result success(Object data) {
Result result = new Result();
result.setCode(200);
result.setMsg("执行成功");
result.setData(data);
return result;
}
/**
* 业务执行失败返回的数据
* @return
*/
public static Result fail(Integer code, String msg, Object data) {
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
/**
* 业务执行失败返回的数据
* @return
*/
public static Result fail(Integer code, String msg) {
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
2.添加统一异常处理类
使用code = -1表示出现异常
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Result error (Exception e) {
return Result.fail(-1, e.getMessage());
}
}
3.添加统一返回格式
在数据返回之前调用此方法,将返回数据格式统一
如果是String类型会报错,所以我们要处理一下,异常使用@SneakyThrows注解
如果返回的数据格式,已经是Result类型,就不需要处理,直接返回即可
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
/**
* 内容是否需要重写
* 返回true表示需要重写
* @param returnType
* @param converterType
* @return
*/
@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) {
//如果返回的数据格式,已经是Result类型,就不需要处理,直接返回即可
if (body instanceof Result) {
return body;
}
// 如果是String类型会报错,所以我们要处理一下
if (body instanceof String) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValueAsString(body);
}
return Result.success(body);
}
}
7.实现博客列表显示
1.约定前后端交互的接口
【请求】
- blog/getList
【响应】
- [200]
- [200]返回数据成功,显示博客列表
- [-1]目前没有博客
- [401]没有权限访问
- [error]访问出现错误,打印异常信息
浏览器给服务器发送一个blog/getList这样的Http请求,服务器返回给浏览器一个json格式的数据
2.实现服务器代码
@RestController
@RequestMapping("/blog")
public class BlogController {
@Autowired
private BlogService blogService;
@RequestMapping("/getList")
public List<Blog> getList() {
// 获取博客列表
List<Blog> blogs = blogService.selectAllBlog();
if (blogs == null) {
return null;
}
return blogs;
}
}
使用postman测试成功,服务器正确返回数据
3.实现客户端代码
修改 blog_list.html, 删除之前写死的博客内容(即 <div class=“blog”> ), 并新增js 代码处理 ajax 请求.
- 使用ajax给服务器发送数据
- 服务器返回一个json数据格式的响应
- 前端根据这个响应使用DOM API构造页面内容
- 响应中的时间为时间戳,需要修改
- 列表中拿到的content应该是已经裁剪过的摘要
- 跳转到博客详情页的url应该在后面加上参数blogId=1,这样可以让博客详情页知道当前访问的是哪篇博客
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
$.ajax({
type: "get",
url: "/blog/getList",
success: function (result) {
if (result.code == 200 && result.data != null && result.data.length > 0) {
//循环拼接数据到document
var finalHtml = "";
for (var blog of result.data) {
finalHtml += '<div class="blog">';
finalHtml += '<div class="title">' + blog.title + '</div>';
finalHtml += '<div class="date">' + blog.createTime + '</div>';
finalHtml += '<div class="desc">' + blog.content + '</div>'
finalHtml += '<a class="detail" href="blog_detail.html?blogId = '+blog.id+'">查看全⽂>></a>'
finalHtml += '</div>';
}
$(".right").html(finalHtml);
} else if (result.code == -1) {
alert(result.mag);
}
},
error: function () {
console.log("后端返回失败");
}
});
</script>
4.处理日期显示问题
SimpleDateFormat 格式化
创建一个DateUtil工具类
public class DateUtil {
public static String format(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
return simpleDateFormat.format(date);
}
}
重新获取博客创建时间
@Data
public class Blog {
private Integer id;
private String title;
private String content;
private Integer userId;
private Integer deleteFlag;
private Date createTime;
public String getCreateTime() {
return DateUtil.format(createTime);
}
}
5.处理裁剪摘要问题
博客列表页面应该显示的是正文的摘要,并非全部显示出来,在博客的详情页面才需要全部显示出来
修改Blog Service中的方法
/**
* 查询所有未删除的博客.按照时间降序排列
* @return
*/
public List<Blog> selectAllBlog() {
List<Blog> blogs = blogMapper.selectAllBlog();
// 遍历如果博客的正文长度超过100,就裁剪
for (Blog blog : blogs) {
if (blog.getContent().length() > 100) {
blog.setContent(blog.getContent().substring(0,100)+"...");
}
}
return blogs;
}
4.博客列表界面显示成功
8.实现博客详情
点击查看全文能进入当前博客详情页面,根据博客id动态的获取博客详情
1.约定前后端交互接口
【请求】
- blog/getBlogDetails
【响应】
- [200]
- [200]返回数据成功,显示博客详情
- [-1]该博客不存在
- [401]没有权限访问
- [error]访问出现错误,打印异常信息
浏览器给服务器发送一个blog.getDetails的Http请求,服务器返回给浏览器一个json格式的数据
2.实现服务器代码
@RequestMapping("/blog/getBlogDetails")
public Result getDetails(Integer blogId) {
// 判合法
if (blogId == null || blogId <= 0) {
return Result.fail(-1,"博客不存在");
}
Blog blog = blogService.selectByBlogId(blogId);
if (blog == null) {
return Result.fail(-1,"博客不存在");
}
return Result.success(blog);
}
使用postman测试成功,服务器正确返回数据
3.实现客户端代码
- 使用ajax,根据当前页面的URL中的blogId参数(使⽤ location.search 即可得到形如 ?blogId=1 的数据),给服务器发送请求
- 服务器返回一个json数据格式的响应
- 前端根据这个响应使用通过 editor.md 转换成 html, 并显示
1. 引⼊ editor.md
<!-- 引⼊ editor.md 的依赖 -->
<link rel="stylesheet" href="blog-editormd/css/editormd.css" />
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js">
</script>
<script src="blog-editormd/lib/marked.min.js"></script>
<script src="blog-editormd/lib/prettify.min.js"></script>
<script src="blog-editormd/editormd.js"></script>
2.新增 js 代码, 从服务器获取博客详情数据
<script>
$.ajax({
type: "get",
url: "/blog/getBlogDetails" + location.search,
success: function (result) {
console.log(result);
if (result.code == 200 && result.data != null) {
$(".title").text(result.data.title);
$(".date").text(result.data.createTime);
editormd.markdownToHTML("content", {
markdown: result.data.content,
});
} else {
alert(result.msg);
}
},
error: function () {
console.log('访问出错');
}
});
</script>
4.博客列表界面显示成功
9.实现登陆
- 登录页面提供一个form表单,通过form的方式把用户名密码提交给服务器
- 服务器端验证⽤户名密码是否正确
- 如果密码正确, 则在服务器端创建 Session , 并把 sessionId 通过 Cookie 返回给浏览器
-
前后端分离的项⽬中, 虽然主要使⽤ ajax 进⾏前后端交互, 但是也不是完全不能⽤ form
1.约定前后端交互接口
【请求】
- user/login
【响应】
- [200]
- [200] 登陆成功
- [-1] 用户名或密码不能为空
- [-2] 用户名或密码错误
- [error]访问出现错误,打印异常信息
2.实现服务器代码
创建 UserController
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public Result login(String username, String password) {
// 判空
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
return Result.fail(-1,"用户名或密码不能为空");
}
// 判断用户名密码是否匹配
User user = userService.selectByName(username);
if (user == null || !user.getPassWord().equals(password)) {
return Result.fail(-2,"用户名或密码错误");
}
return Result.success("登陆成功");
}
}
使用postman测试登录成功
3.实现客户端代码
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
function login() {
$.ajax({
type: "post",
url: "/user/login",
data: {
"username": $("#username").val(),
"password": $("#password").val()
},
success: function (result) {
if (result.code == 200 && result.data == 1) {
location.assign("blog_list.html");
} else if(result.code == -1){
alert("⽤户名或密码不能为空");
return;
} else if(result.code == -2){
alert("⽤户名或密码错误");
return;
}
},
error : function (error) {
console.log(error.msg);
}
})
}
</script>
3.实现客户端代码
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
function login() {
$.ajax({
type: "post",
url: "/user/login",
data: {
"username": $("#username").val(),
"password": $("#password").val()
},
success: function (result) {
if(result.code == -1){
alert(result.msg);
} else if(result.code == -2){
alert(result.msg);
}
else if (result.code == 200 && result.data != null) {
location.assign("blog_list.html");
}
},
error : function (error) {
console.log(error.msg);
}
})
}
</script>
4.登陆功能实现成功
剩下的功能在下篇博客实现~