相關技術
Spring + Spring Boot + Spring MVC + MyBatis
Html + Css + JS
實現功能
- 用戶注冊 - 密碼加鹽加密 (md5 加密)
- 前后端用戶信息存儲 - 令牌技術
- 用戶登錄 - (使用 攔截器 做登錄校驗)
- 博客的增刪改查
- 后端數據返回前端, 采用 SpringBoot 做統一功能處理和統一異常處理
數據庫設計
- 用戶表
- 博客表
前端頁面
博客登錄頁 (blog_login.html)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><me_ta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客登陸頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/login.css"></head><body><div class="nav"><img src="pic/logo2.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a></div><div class="container-login"><div class="login-dialog"><h3>登陸</h3><div class="row"><span>用戶名</span><input type="text" name="username" id="username"></div><div class="row"><span>密碼</span><input type="password" name="password" id="password"></div><div class="row"><button id="submit" onclick="login()">提交</button></div></div></div><script src="js/jquery.min.js"></script><script>function login() {// 發送 ajax 請求, 獲取 token$.ajax({type: "post",url: "/user/login",data: {"userName": $("#username").val(),"password": $("#password").val()},success: function(result) {if(result.code == 200 && result.data != null) {// 存儲 token 到本地localStorage.setItem("user_token", result.data);location.href = "blog_list.html";}else{alert("用戶名或密碼錯誤");}}});}</script>
</body></html>
用戶登錄成功之后, 會將用戶信息, 生成令牌, 存儲到 request 中, 前后端都能從中獲取當前登錄用戶的信息
博客列表頁 (blog_list.html)
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客列表頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/list.css"></head>
<body><div class="nav"><img src="pic/logo2.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a><a class="nav-span" href="#" onclick="logout()">注銷</a></div><div class="container"><div class="left"><div class="card"><img src="pic/doge.jpg" alt=""><h3></h3><a href="#"></a><div class="row"><span>文章</span><span>分類</span></div><div class="row"><span>2</span><span>1</span></div></div></div><div class="right"></div></div><script src="js/jquery.min.js"></script><script src="js/common.js"></script><script>//顯示用戶信息var userUrl = "/user/getUserInfo";getUserInfo(userUrl);// 獲取所有的博客信息$.ajax({type: "get",url: "/blog/getList",success: function(result) {console.log("result:" + result);if(result.code == 200 && result.data != null) {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);}},error: function(error) {console.log("error:" + error);location.href = "blog_login.html";if(error != null && error.state == 401) {location.href = "blog_login.html";}}});</script>
</body>
</html>
當前頁面會自動調用一個 ajax 請求, 用以獲取數據庫中 所有未刪除博客 的信息進行展示 (博客正文會裁取前100字進行顯示)
博客詳情頁 (blog_detail.html)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客詳情頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/detail.css"></head><body><div class="nav"><img src="pic/logo2.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a><a class="nav-span" href="#" onclick="logout()">注銷</a></div><div class="container"><div class="left"><div class="card"><img src="pic/doge.jpg" alt=""><h3></h3><a href="#"></a><div class="row"><span>文章</span><span>分類</span></div><div class="row"><span>2</span><span>1</span></div></div></div><div class="right"><div class="content"><div class="title"></div><div class="date"></div><div class="detail" id="detail" style="background-color: transparent;"></div><!-- <div class="operating"><button onclick="window.location.href='blog_update.html'">編輯</button><button onclick="deleteBlog()">刪除</button></div> --></div></div></div><!-- 引入 editor.md 的依賴 --><link rel="stylesheet" href="blog-editormd/css/editormd.css" /><script src="js/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><script src="js/common.js"></script><script>// 獲取博客詳情$.ajax({type: "get",url: "/blog/getBlogDetail"+location.search,success: function(result) {console.log(result);if(result.code == 200 && result.data != null) {console.log("abc" + result);var blog = result.data;$(".right .content .title").text(blog.title);$(".right .content .date").text(blog.createTime);// $(".right .content .detail").text(blog.content);editormd.markdownToHTML("detail", {markdown: blog.content,});// 是否顯示 編輯/刪除 按鈕if(blog.isLoginUser == true) {var html = "";html += '<div class="operating">';html += '<button onclick="window.location.href=\'blog_update.html'+location.search+'\'">編輯</button>';html += '<button onclick="deleteBlog()">刪除</button>';html += '</div>';$(".content").append(html);}}},error: function(error) {if(error != null && error.status == 401) {location.href = "blog_list.html";}}});//顯示博客作者信息var userUrl = "/user/getAuthorInfo" + location.search;getUserInfo(userUrl);function deleteBlog() {$.ajax({type: "post",url: "/blog/delete" + location.search,success: function(result) {if(result.code == 200 && result.data != null && result.data == true) {location.href = "blog_list.html";}}});}</script>
</body></html>
對于每篇博客, 會顯示博客信息 (標題, 最后一次的修改時間, 博客正文), 和博客作者的信息 (用戶名) (TODO: 作者頭像, 作者的總文章數量, 博客的分類所屬)
頁面會自動校驗登錄用戶是否為當前博客的作者, 如果是, 那么可以對當前博客進行編輯和刪除, 如果不是, 這兩個按鈕不會顯示
博客編輯頁 (blog_edit.html)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客編輯頁</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/edit.css"><link rel="stylesheet" href="blog-editormd/css/editormd.css" /></head><body><div class="nav"><img src="pic/logo2.jpg" alt=""><span class="blog-title">我的博客系統</span><div class="space"></div><a class="nav-span" href="blog_list.html">主頁</a><a class="nav-span" href="blog_edit.html">寫博客</a><a class="nav-span" href="#" onclick="logout()">注銷</a></div><div class="content-edit"><div class="push"><input type="text" name="" id="title"><input type="button" value="發布文章" id="submit" onclick="submit()"></div><!-- markdown 插件 html代碼 --><div id="editor"><textarea style="display:none;" id="content" name="content">##在這里寫下一篇博客</textarea></div></div><script src="js/jquery.min.js"></script><script src="blog-editormd/editormd.min.js"></script><script src="js/common.js"></script><script type="text/javascript">$(function () {var editor = editormd("editor", {width: "100%",height: "550px",path: "blog-editormd/lib/"});});function submit() {$.ajax({type: "post",url: "/blog/add",data: {title: $("#title").val(),content: $("#content").val()},success: function(result) {if(result.code == 200 && result.data != null && result.data == true) {location.href = "blog_list.html";}else {alert("博客發布失敗!");}}});}</script>
</body></html>
博客編輯頁使用了 gittee 上的一個開源 markdown 組件
對于未有博客的 “寫博客” , 調用的是 “插入操作”
對于已有博客的 “編輯博客” , 調用的是 “更新操作”
“刪除博客” 操作是邏輯刪除, 即修改數據庫的某一字段, 所以調用的也是 “更新操作”
前端頁面共同的 js (common.js)
$(document).ajaxSend(function(e, xhr, opt) {// 獲取本地存儲中的 tokenvar user_token = localStorage.getItem("user_token");// 將 token 設置到每個 ajax 請求的 header 中xhr.setRequestHeader("user_token_header", user_token);
});// 獲取用戶信息
function getUserInfo(url) {$.ajax({type: "post",url: url,success: function(result) {if(result.code == 200 && result.data != null) {$(".left .card h3").text(result.data.userName);$(".left .card a").attr("href", result.data.githubUrl);}}});
}// 用戶退出
function logout() {localStorage.removeItem("user_token");location.href = "blog_login.html";
}
主要就是獲取當前登錄用戶的信息, 以及退出登錄的邏輯
后端代碼
項目的基本框架
實體類
BlogInfo
@Data
public class BlogInfo {private Integer id;private String title;private String content;private Integer userId;private Integer deleteFlag;private Date createTime;private Date updateTime;private Boolean isLoginUser = false;// 返回 String 類型的數據 (BlogInfo 里面存儲的是 Date 類型數據)public String getCreateTime() {return DateUtils.formateDate(createTime);}public String getUpdateTime() {return DateUtils.formateDate(updateTime);}
}
對應數據的 blog 表
UserInfo
@Data
public class UserInfo {private Integer id;private String userName;private String password;private String githubUrl;private Integer deleteFlag;private Date createTime;private Date updateTime;
}
對應數據的 user 表
Result
@Data
public class Result {private int code; //200成功 -1失敗 -2未登錄private String errMsg;private Object data;public static Result success(Object data) {Result result = new Result();result.setCode(Constant.SUCCESS_CODE);result.setErrMsg("");result.setData(data);return result;}public static Result fail(String errMsg) {Result result = new Result();result.setCode(Constant.FAIL_CODE);result.setErrMsg(errMsg);result.setData(null);return result;}public static Result fail(String errMsg, Object data) {Result result = new Result();result.setCode(Constant.FAIL_CODE);result.setErrMsg(errMsg);result.setData(data);return result;}public static Result unlogin() {Result result = new Result();result.setCode(Constant.FAIL_CODE);result.setErrMsg("用戶未登錄");result.setData(null);return result;}public static Result unlogin(String errMsg) {Result result = new Result();result.setCode(Constant.UNLOGIN_CODE);result.setErrMsg("用戶未登錄");result.setData(null);return result;}
}
用于統一數據格式返回 (不知道可以看一下我的另一篇博客 Spring Boot統一功能處理(攔截器, 統一數據返回格式, 統一異常處理) )
Constant 類 (常量值存儲)
public class Constant {public final static Integer SUCCESS_CODE = 200;public final static Integer FAIL_CODE = -1;public final static Integer UNLOGIN_CODE = -2;public final static String USER_TOKEN_HEADER = "user_token_header";public final static String USER_CLAIM_ID = "id";public final static String USER_CLAIM_NAME = "name";
}
Mapper 類
通過 MyBatis 操作數據庫
BlogMapper
@Mapper
public interface BlogMapper {// 查詢博客列表@Select("select * from blog where delete_flag = 0 order by create_time desc")List<BlogInfo> selectAllBlog();// 根據博客 ID, 查詢博客信息@Select("select * from blog where delete_flag = 0 and id = #{blogId}")BlogInfo selectById(@Param("blogId") Integer blogId);// 根據博客 ID, 修改/刪除 博客信息Integer updateBlog(BlogInfo blogInfo);// 插入博客@Insert("insert into blog(title, content, user_id) values(#{blogInfo.title}, #{blogInfo.content}, #{blogInfo.userId})")Integer insertBlog(@Param("blogInfo") BlogInfo blogInfo);
}
數據庫操作 blog 表
UserMapper
@Mapper
public interface UserMapper {// 根據用戶名, 查詢用戶信息@Select("select * from user where user_name = #{userName} and delete_flag = 0")UserInfo selectByName(@Param("userName") String userName);// 根據用戶 ID, 查詢用戶信息@Select("select * from user where id = #{userId} and delete_flag = 0")UserInfo selectById(@Param("userId") Integer userId);}
數據庫操作 user 表
用戶登錄頁
登錄功能
登錄頁面點擊登錄按鈕后, 觸發 controller 層的 login 接口
@Autowiredprivate UserService userService;// 登錄接口@RequestMapping("/login")public Result login(String userName, String password) {// 1.對參數進行校驗// 2.對密碼進行校驗// 3.如果校驗成功, 生成 tokenif(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
// throw new UnsupportedOperationException("用戶名或密碼不能為空");return Result.fail("用戶名或密碼不能為空");}// 獲取用戶信息UserInfo userInfo = userService.queryUserByName(userName);if(userInfo == null || userInfo.getId() <= 0) {return Result.fail("用戶不存在");}// 密碼校驗if(!SecurityUtils.verify(password, userInfo.getPassword())) {return Result.fail("密碼錯誤");}// 用戶信息正確, 生成 tokenMap<String, Object> claim = new HashMap<>();claim.put(Constant.USER_CLAIM_ID, userInfo.getId());claim.put(Constant.USER_CLAIM_NAME, userInfo.getUserName());return Result.success(JWTUtils.getToken(claim));}
login 接口先對前端數據進行判空校驗, 然后根據用戶名 查詢數據庫中是否有對應的信息, 將獲取信息與輸入信息進行比對, 返回登錄判定信息 (登錄成功生成 token 令牌)
獲取用戶信息:
密碼校驗:
生成 token 令牌:
用戶注銷
用戶注銷是個前端功能
在 common.js 里面
function logout() {localStorage.removeItem("user_token");location.href = "blog_login.html";
}
博客列表頁
博客列表頁獲取所有未刪除博客的信息進行展示
調用接口 getList
@Autowired
private BlogService blogService;@RequestMapping("/getList")
public List<BlogInfo> queryBlogList() {return blogService.queryBlogList();
}
博客列表頁左側登錄用戶信息欄, 獲取當前登錄用戶的信息進行展示
調用接口 getUserInfo
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;// 獲取當前登錄用戶的信息@RequestMapping("/getUserInfo")public UserInfo getUserInfo(HttpServletRequest request) {// 1. 獲取 token, 從 token 中獲取 IDString user_token = request.getHeader(Constant.USER_TOKEN_HEADER);Integer userId = JWTUtils.getUserIdFromToken(user_token);// 2. 根據 ID, 獲取用戶信息if(userId == null || userId <= 0) {return null;}UserInfo userInfo =userService.queryUserByID(userId);userInfo.setPassword("");return userInfo;}
}
博客詳情頁
博客詳情頁右側獲取博客詳情信息
調用接口 getBlogDetail
@Slf4j
@RestController
@RequestMapping("/blog")
public class BlogController {@Autowiredprivate BlogService blogService;// 根據博客id獲取博客信息@RequestMapping("/getBlogDetail")public BlogInfo getBlogDetail(Integer blogId, HttpServletRequest request) {BlogInfo blogInfo = blogService.getBlogDetail(blogId);// 獲取登錄用戶信息String user_token = request.getHeader(Constant.USER_TOKEN_HEADER);Integer userId = JWTUtils.getUserIdFromToken(user_token);// 判斷登錄用戶是否為作者if(userId != null && userId == blogInfo.getUserId()) {blogInfo.setIsLoginUser(true);}else {blogInfo.setIsLoginUser(false);}return blogInfo;}
}
博客詳情頁左側獲取博客作者信息
調用接口 getAuthorInfo
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;// 根據博客 ID, 獲取作者信息@RequestMapping("/getAuthorInfo")public UserInfo getAuthorInfo(Integer blogId) {// 校驗博客 ID 是否正確if(blogId == null || blogId <= 0) {return null;}UserInfo userInfo = userService.queryAuthorInfoByBlogId(blogId);userInfo.setPassword("");return userInfo;}
}
博客詳情頁中, 編輯和刪除功能
調用接口 update & delete
@Slf4j
@RestController
@RequestMapping("/blog")
public class BlogController {@Autowiredprivate BlogService blogService;// 編輯博客@RequestMapping("/update")public Boolean update(Integer blogId, String title, String content) {log.error("blogId:{}, title:{}, content:{}", blogId, title, content);if(blogId == null || !StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {log.error("update, 參數非法");return false;}BlogInfo blogInfo = new BlogInfo();blogInfo.setId(blogId);blogInfo.setTitle(title);blogInfo.setContent(content);log.error("blogInfo:{}", blogInfo);Integer result = blogService.updateBlog(blogInfo);if(result < 1) return false;return true;}// 刪除博客(邏輯刪除)@RequestMapping("/delete")public Boolean delete(Integer blogId) {BlogInfo blogInfo = new BlogInfo();blogInfo.setId(blogId);blogInfo.setDeleteFlag(1);log.error("blogInfo:{}", blogInfo);Integer result = blogService.updateBlog(blogInfo);if(result < 1) return false;return true;}
}
博客編輯頁
博客撰寫后存入數據庫
調用接口 add
@Slf4j
@RestController
@RequestMapping("/blog")
public class BlogController {@Autowiredprivate BlogService blogService;// 添加博客@RequestMapping("/add")public Boolean publishBlog(String title, String content, HttpServletRequest request) {// 1.參數校驗if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {return false;}// 2.獲取當前用戶String user_token = request.getHeader(Constant.USER_TOKEN_HEADER);Integer userId = JWTUtils.getUserIdFromToken(user_token);if(userId == null || userId <= 0) {return false;}// 3.博客發布BlogInfo blogInfo = new BlogInfo();blogInfo.setUserId( userId);blogInfo.setContent(content);blogInfo.setTitle(title);Integer result = blogService.publishBlog(blogInfo);return result<=0 ? false:true;}
}