目錄
一、項目簡介
?二、項目整體架構(gòu)
數(shù)據(jù)庫模塊
后端模塊
前端模塊
?三、項目具體展示
?四、項目的具體實現(xiàn)
1、一些準備工作
??數(shù)據(jù)庫、數(shù)據(jù)表的創(chuàng)建
??設(shè)置數(shù)據(jù)庫和MyBatis的配置
??將前端項目引入到當前項目中
2、登錄注冊模塊
??實體類的創(chuàng)建
??前端后端交互
??后端流程
?? 登錄功能的實現(xiàn)和注冊大同小異
3、統(tǒng)一功能的處理
??統(tǒng)一異常處理
??統(tǒng)一數(shù)據(jù)格式返回
??統(tǒng)一用戶的登錄驗證(用戶登錄攔截器)
??過程中遇到的bug
4、博客列表頁面的實現(xiàn)(更新中...)
5、博客詳情頁面的實現(xiàn)
6、博客的修改和刪除功能
7、博客列表分頁功能的實現(xiàn)
8、隨機加鹽的實現(xiàn)
一、項目簡介
項目名稱:個人博客系統(tǒng)
主要操作的對象是:文章和用戶,用戶可在該系統(tǒng)上發(fā)表自己的博客,查看自己或別人已經(jīng)發(fā)表的文章
需要用到兩張表:userinfo(用戶表)、articleinfo(文章表)
需要實現(xiàn)的功能:
登錄+注冊
博客的分頁列表功能
新增發(fā)表博客
修改、刪除自己的博客
項目技術(shù)棧
SSM(SpringBoot + SpringMVC + MyBatis)
MySQL
jQuery
?項目亮點
手動對用戶密碼實現(xiàn)隨機加鹽
統(tǒng)一異常處理、攔截器
用戶登錄持久化(session內(nèi)存)
分頁功能
?二、項目整體架構(gòu)
數(shù)據(jù)庫模塊
兩張表:用戶表 + 文章表
后端模塊
控制層(controller包)——》控制器
服務(wù)層(service包)——》服務(wù)類
持久層--數(shù)據(jù)訪問層(java目錄下mapper類 + resources目錄下的mapper.xml)——》mapper
實體層(model包)——》實體類
工具層(config包+util包)——》統(tǒng)一異常處理、統(tǒng)一返回、隨機加鹽
?
前端模塊
前端設(shè)計到7個頁面
login.html登錄頁
reg.html注冊頁
blog_list.html總的博客列表頁——》用到了blog_l
myblog_list.html個人博客列表頁
blog_content.html博客詳情頁
blog_edit.html博客編輯頁
blog_update.html博客修改頁
?
?三、項目具體展示
項目公網(wǎng)地址:登陸頁面
?登錄頁面、注冊頁面
?
?總的博客列表頁面
?博客詳情頁
個人博客主頁
?
?博客修改頁面
博客編輯頁面
?
?
?項目源碼:java_spring: SpringMVC、SpringBoot、MyBatis學習 - Gitee.com
?
?四、項目的具體實現(xiàn)
1、一些準備工作
首先我們新建一個springboot項目,項目具體的創(chuàng)建流程我這里就不在贅述。
詳見:SpringBoot項目的創(chuàng)建
?
接下來我們就要數(shù)據(jù)庫引入該項目。
??數(shù)據(jù)庫、數(shù)據(jù)表的創(chuàng)建
?用戶表的創(chuàng)建
?文章表的創(chuàng)建
??設(shè)置數(shù)據(jù)庫和MyBatis的配置
??配置數(shù)據(jù)庫的連接信息
?這里的很多內(nèi)容是固定的
# 數(shù)據(jù)庫連接配置
Spring:
datasource:
url: jdbc:mysql://localhost:3306/你要連接的數(shù)據(jù)庫名?characterEncoding=utf8&useSSL=false
username: 用戶名
password: 自己的密碼
driver-class-name: com.mysql.cj.jdbc.Driver #只要你數(shù)據(jù)庫用的是mysql這個是固定的
???配置MyBatis XML存放位置和命名規(guī)則
?此時我們已經(jīng)在通過maven將MySQL Driver和MyBatis Framework這兩個包導入了進來,此時啟動項目,項目依然能夠正常運行,說明我們數(shù)據(jù)庫連接是正常的。
??將前端項目引入到當前項目中
下面的前端的靜態(tài)資源復制到我們resource/static目錄下面:
springboot項目-個人博客系統(tǒng)靜態(tài)頁面https://download.csdn.net/download/weixin_61061381/87459276
2、登錄注冊模塊
??實體類的創(chuàng)建
登錄和注冊不就是對用戶表進行操作嗎?這個我們首先創(chuàng)建用戶表對應(yīng)的實體類
首先根據(jù)我們數(shù)據(jù)庫的表,在我們的程序中創(chuàng)建想對應(yīng)的實體類——我們的MyBatis是ORM框架,我們的程序?qū)ο笈c關(guān)系數(shù)據(jù)庫數(shù)據(jù)之間有響應(yīng)的映射關(guān)系
- 一個數(shù)據(jù)庫中的數(shù)據(jù)表對應(yīng)我們程序中的一個實體類
- 數(shù)據(jù)表中的一行數(shù)據(jù)對應(yīng)該實體類的一個實例化對象
- 該數(shù)據(jù)表中的各個屬性對應(yīng)該實體類的成員變量(屬性)
?對了,還記我們springboot的掃描路徑嗎?
只有在啟動類的同級目錄下,springboot才會對該目錄下的類進行掃描,配合類注解存到spring容器中。
接下來我們就嘗試給我們的用戶表插入一條數(shù)據(jù)。
?如上圖所示:我們從前端的login.html中獲取到數(shù)據(jù)后,經(jīng)過控制層——》服務(wù)層——》數(shù)據(jù)持久層,最后才作用到我們的數(shù)據(jù)庫上。
廢話不多說,讓我們跟著流程來走一遍吧!
?controller層
上圖描述的是,我們通過url直接調(diào)用了我們控制層的sayHi()方法,并且把我們sayHi()方法返回的的結(jié)果展現(xiàn)在了瀏覽器上。
??但是現(xiàn)在我們有前端了,我們前端提交數(shù)據(jù)(調(diào)用我們的后端controller層的登錄方法),然后把結(jié)果返回給前端不就行了嗎(用戶是登錄失敗了,還是成功了,交由我們的前端來處理)
??前端后端交互
首先用戶在前端頁面輸入了用戶信息
然后前端緊接著就把用戶輸入的信息傳遞給后端(提交到后端指定的接口上,比如登錄提交的就是/user/reg?
后端接收到用戶信息,存到數(shù)據(jù)庫中,并返回注冊的結(jié)果(成功了?還是失敗了?)?
?前端接受到后端返回的結(jié)果后做進一步的處理
??后端流程
還是先來看這張圖
下面是根據(jù)上圖流程構(gòu)建的目錄
?controller層代碼
package com.example.demo.controller;
import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* 而Spring Boot框架項目接口返回 JSON格式的數(shù)據(jù)比較簡單:
* 在 Controller 類中使用@RestController注解即可返回 JSON格式的數(shù)據(jù)。
*/
@RestController
@RequestMapping("/user")
public class UserController {
// 屬性注入service服務(wù)層的userService類
@Autowired
public UserService userService;
@RequestMapping("/reg")
public HashMap<String, Object> reg(String username, String password1, String password2) {
HashMap<String, Object> result = new HashMap<>();
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password1) || !StringUtils.hasLength(password2)) {
result.put("status", -1);
result.put("msg", "參數(shù)輸入錯誤");
result.put("data", "");
return result;
}
else {
if (!password1.equals(password2)) {
result.put("status", -1);
result.put("msg", "前后密碼不一致");
result.put("data", "");
return result;
}
else {
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
userInfo.setPassword(password1);
int ret = userService.reg(userInfo);
if (ret != 1) {
result.put("status", -1);
result.put("msg", "數(shù)據(jù)庫添加出錯");
result.put("data", "");
return result;
}
else {
result.put("status", 200);
result.put("msg", "注冊成功");
result.put("data", ret);
return result;
}
}
}
}
}
在controller層中調(diào)用了service服務(wù)層的reg方法
?service服務(wù)層又調(diào)用了持久層中的mapper接口
?mapper接口的實現(xiàn):UserMapper.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.demo.mapper.UserMapper">
<insert id="reg">
insert into userinfo(username, password) values(#{userinfo.username}, #{userinfo.password});
</insert>
</mapper>
?到此,關(guān)于注冊整個前后端的流程就走完了,下面我們來驗證一下
?
但是當我們重復注冊張三這個用戶名
?這個異常我們前端是不知道的,我們不知道發(fā)生了什么,是注冊成功了?還是失敗了?
在后端我們應(yīng)該統(tǒng)一對異常進行處理,并把具體的異常情況告訴前端
這就是我們下面要說的統(tǒng)一處理功能的實現(xiàn)
關(guān)于前后端參數(shù)的傳遞,詳見:SpringMVC學習筆記(獲取參數(shù),傳遞參數(shù)——關(guān)于前后端傳參交互的總結(jié)、from表單、Ajax數(shù)據(jù)提交)_是小魚兒哈的博客-CSDN博客
?關(guān)于MyBatis實現(xiàn)數(shù)據(jù)庫的增刪改查,詳見:第一個MyBatis程序_是小魚兒哈的博客-CSDN博客
關(guān)于spring更簡單的存取用戶對象,詳見:
?spring更簡單的對象存取
?? 登錄功能的實現(xiàn)和注冊大同小異
后端流程
?前后端交互的流程
下面我們通過瀏覽器驗證一下我們的登錄功能
一點補充?
通過controller層的代碼,可以看到我們返回給前端的好像是hashmap,這樣肯定是不行的。
這個時候就用到了我們的@RestController注解了
*@RestController是@Controller和@ResponseBody兩者的結(jié)合,使用這個注解后該controller的所有方法都會返回json格式的數(shù)據(jù) * 因為@ResponseBody的作用就是把返回的對象轉(zhuǎn)換為json格式,并把json數(shù)據(jù)寫入response的body中,前臺收到response時就可以獲取其body中的json數(shù)據(jù)了。 * 如果在整個controller類上方添加@RestController,其作用就相當于把該controller下的所有方法都加上@ResponseBody,使每個方法直接返回response對象
3、統(tǒng)一功能的處理
上面我們說了,當程序出現(xiàn)了異常獲取其他情況,我們不統(tǒng)一處理(把結(jié)果告訴前端)我們其實是不知道發(fā)生了什么的。
??統(tǒng)一異常處理
所以我們需要單獨在工具層中(我們的common包下面,建一個統(tǒng)一異常處理的類)
??統(tǒng)一數(shù)據(jù)格式返回
一般在web項目中,我們前后端都是通過json這種數(shù)據(jù)格式來交換數(shù)據(jù)格式——》我們后端需要給前端返回json格式的數(shù)據(jù)。
總之,我們前后端用戶交互的數(shù)據(jù)個數(shù)一般是統(tǒng)一的,不會出現(xiàn)你一個接口用一種數(shù)據(jù)格式,而那個接口又換了,這樣就會有很多問題,不利于開發(fā)。
統(tǒng)一數(shù)據(jù)的優(yōu)點
- 方便前端程序員更好的接受和解析后端數(shù)據(jù)接口返回的數(shù)據(jù)。
- 降低前端程序員和后端程序員的溝通成本,按照某個格式實現(xiàn)就可以了,因為所有接口都是這樣返回的。
- 有利于項目統(tǒng)一數(shù)據(jù)的維護和修改。
- 有利于后端技術(shù)部門的統(tǒng)一規(guī)范的標準制定,不會出現(xiàn)稀奇古怪的返回內(nèi)容。
統(tǒng)一數(shù)據(jù)格式的返回有兩種實現(xiàn)方式,返回一個公共對象或者重寫。
這里我們采用第二種重寫(不過他的靈活性有待提升)?
具體的我們可以使用@ControllerAdvice+ResponseBodyAdvice的方式實現(xiàn),具體實現(xiàn)代碼如下:
但是正如我們上面所說的,通過重寫來進行統(tǒng)一數(shù)據(jù)格式的返回,他的靈活性的確有待提高。你看我們上面只是處理了成功的情況,但要是失敗的情況呢?——》并且即使失敗,也是分好幾種情況呢?。?!
?我們不如再創(chuàng)建一個工具類,用來自定義返回hashmap數(shù)據(jù)(再通過@ResponseBody轉(zhuǎn)成json格式)
?那么對應(yīng)的我們的統(tǒng)一數(shù)據(jù)格式返回類就發(fā)生了變化
package com.example.demo.common;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
/**
* 統(tǒng)一數(shù)據(jù)格式返回(靈活性有待提高)
* 通過統(tǒng)一數(shù)據(jù)格式的返回,不管我們控制層的方法返回了什么類型的數(shù)據(jù)
* 通過重寫末尾都能把他轉(zhuǎn)成hashmap格式的數(shù)據(jù),然后又通過@ResponseBody注解,將java對象轉(zhuǎn)成了json對象。
*/
@ControllerAdvice
@ResponseBody
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true; // 這個值為true的時候,才會對返回的數(shù)據(jù)進行重寫
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 在有了我們自定義數(shù)據(jù)返回后,我們的這個統(tǒng)一數(shù)據(jù)格式返回類就像是一個托地的。
// 因為controller層可以直接調(diào)用AjaxResult,來返回hashmap(通過@ResponseBody轉(zhuǎn)成json)
// 但如果controller沒有調(diào)用AjaxResult,直接返回了
if (body instanceof HashMap) {
return body; // 此時已經(jīng)是hashmap格式了
}
if (body instanceof Integer){ // 當controller層中的方法直接返回int類型時候
int num = (int) body;
if (num <= 0) {
// 應(yīng)對int類型錯誤返回(查詢文章列表,新增和刪除博客可能會用到)——》也可能用不到,如果新增或查詢失敗,我直接就在controller就返回了(通過調(diào)用AjaxResult)
// 新增、刪除或查詢失?。ǚ堑迷赾ontroller返回int值,再通過這里返回json對象的話,不靈活(出錯信息顯示的不具體
// 所以說這里我們只是以防萬一,我們還是選擇再controller層直接返回json對象,這樣更信息具體,更有怎針對性)
return AjaxResult.fail("抱歉,本次操作失敗,請稍后再試!"); // 這里無法區(qū)分是新增失敗還是刪除失敗
// 這里我們本來返回的是一個hashmap格式的對象,但加了@ResponseBody,把我們的java對象轉(zhuǎn)成的了json格式
}
}
if (body == null) { // (比如查詢操作,直接返回查詢到的UserInfo對象,然后直接返回該對象)
// 這里我們本來返回的是一個hashmap格式的對象,但加了@ResponseBody,把我們的java對象轉(zhuǎn)成的了json格式
return AjaxResult.fail("抱歉,查詢失??!"); // 這時對查詢當前用戶的特判
}
// 這里我們本來返回的是一個hashmap格式的對象,但加了@ResponseBody,把我們的java對象轉(zhuǎn)成的了json格式
return AjaxResult.success("操作成功", body);
// 前端是通過result中的status值來判斷操作是否成功的,這個類用來處理操作成功的情況(為操作成功的情況兜底)
// 但這可能存在問題,如果操作失敗,并且在controller層沒有調(diào)用AjaxResult中的fail方法(而是直接返回,通過這個類來返回統(tǒng)一的數(shù)據(jù)格式,就會出現(xiàn)問題——》在這個類我們都是按成功的處理的)
// 解決方案,在該類中提前判斷body(判斷操作失敗的情況)--->我們約定如果操作失敗就返回負數(shù)(在controller層調(diào)用AjaxResult的情況)
}
}
那么與之對應(yīng)的我們在controller層的類也就發(fā)生了改變
package com.example.demo.controller;
import com.example.demo.common.AjaxResult;
import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.jws.soap.SOAPBinding;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
/**
* 而Spring Boot框架項目接口返回 JSON格式的數(shù)據(jù)比較簡單:
* 在 Controller 類中使用@RestController注解即可返回 JSON格式的數(shù)據(jù)。
* @RestController是@Controller和@ResponseBody兩者的結(jié)合,使用這個注解后該controller的所有方法都會返回json格式的數(shù)據(jù),
* 因為@ResponseBody的作用就是把返回的對象轉(zhuǎn)換為json格式,并把json數(shù)據(jù)寫入response的body中,前臺收到response時就可以獲取其body中的json數(shù)據(jù)了。
* 如果在整個controller類上方添加@RestController,其作用就相當于把該controller下的所有方法都加上@ResponseBody,使每個方法直接返回response對象。
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
public UserService userService;
@RequestMapping("/reg")
public Object reg(String username, String password1, String password2) {
HashMap<String, Object> result = new HashMap<>();
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password1) || !StringUtils.hasLength(password2)) {
return AjaxResult.fail("你輸入的參數(shù)有誤,請重新輸入!");
}
else {
if (!password1.equals(password2)) {
return AjaxResult.fail("前后密碼不一致,請重新輸入!");
}
else {
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
userInfo.setPassword(password1);
int ret = userService.reg(userInfo);
if (ret != 1) {
return AjaxResult.fail("數(shù)據(jù)庫添加用戶失敗,請稍后再試!");
}
else {
return AjaxResult.success("恭喜,注冊成功!", ret);
}
}
}
}
@RequestMapping("/login")
public Object login(String username, String password) {
HashMap<String, Object> result = new HashMap<>();
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
return AjaxResult.fail("你輸入的參數(shù)有誤,請重新輸入!");
}
else {
// 需要在數(shù)據(jù)庫中查詢當前登錄的用戶是否存在
UserInfo userInfo = userService.selectByUsername(username);
if (userInfo == null || !password.equals(userInfo.getPassword())) {
return AjaxResult.fail("你當前的用戶名或密碼錯誤,請重新輸入!");
}
else {
return AjaxResult.success("恭喜,登錄成功!", "");
}
}
}
}
??統(tǒng)一用戶的登錄驗證(用戶登錄攔截器)
spring攔截器
對于以上問題Spring中提供了具體的實現(xiàn)攔截器:HandlerInterceptor,攔截器的實現(xiàn)分為以下兩個步驟:
1、創(chuàng)建自定義攔截器,實現(xiàn) HandlerInterceptor 接口的perHandle(執(zhí)行具體方法之前的預處理)方法。
2、將自定義攔截器加入 WebMvcConfiger的 addInterceptors方法中。
?
創(chuàng)建用戶登錄攔截器
?將該自定義攔截器放到系統(tǒng)的配置文件中
(將自定義攔截器加入 WebMvcConfiger的 addInterceptors方法中)
?用瀏覽器測試一下是否真的攔截了
可以看到因為我們沒有放行l(wèi)ogin.html,登錄頁面直接顯示不出來了。
我們改下攔截規(guī)則?
?
然后你發(fā)現(xiàn),咦,怎么還是不行。
當然不行呀,你雖然運行了login.html通行,但是login.html還用到了css/js/image圖片呢!這些東西你還沒放行呢?。?!
?
??過程中遇到的bug
一開始,當我把自定義的用戶登錄攔截器放到了系統(tǒng)配置文件,我歡歡喜喜的啟動項目,結(jié)果——
?加上類注解后問題就解決了。文章來源:http://www.zghlxwxcb.cn/news/detail-778095.html
關(guān)于統(tǒng)一功能的處理,詳見:SpringBoot統(tǒng)一功能處理_是小魚兒哈的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-778095.html
4、博客列表頁面的實現(xiàn)(更新中...)
5、博客詳情頁面的實現(xiàn)
6、博客的修改和刪除功能
7、博客列表分頁功能的實現(xiàn)
8、隨機加鹽的實現(xiàn)
到了這里,關(guān)于SpringBoot實戰(zhàn)——個人博客項目的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!