Hello,你好呀,我是灰小猿,一個超會寫bug的程式猿!
利用國慶期間做了一個基于springboot+vue的前后端分離的個人博客網站,今天在這里將開發程序和大家分享一下,手把手教你搭建一個自己專屬的個人博客,
完整原始碼放置在Gitee上了,【原始碼鏈接】
小伙伴們記得?star?喲!
小伙伴們一鍵三連?關注!灰小猿帶你上高速啦🎉🎉🎉!
先看一下博客網站的演示視頻:
?專案目錄?
一、個人博客網站專案整體思路
二、Java后端介面開發
(1)資料庫設計
?(2)整合MybatisPlus
(3)統一結果封裝
(4)整合shiro+jwt實作安全驗證
(5)全域例外處理
(6)物體校驗
(7)跨域問題
(8)登錄介面開發
(9)博客介面開發
三、Vue前端頁面開發
(1)安裝Element-UI
(2)安裝axios
(3)配置頁面路由
(4)登錄頁面
(5)博客串列
(6)博客編輯
(7)博客詳情頁
(8)權限路由攔截
四、寫在最后&專案總結
一、個人博客網站專案整體思路
整個專案的設計是前后端分離的,后端使用的是SpringBoot+MybatisPlus設計,前端使用Vue+ElementUI搭建頁面,安全驗證等操作由shiro安全框架完成,在進行前后端資料互動的時候采用路由傳輸,同時在前后端解決了跨域問題,博客實作登錄功能,在未登錄的情況下只能訪問博客主頁,在登錄的狀態下可以實作博客的發布與編輯功能,
整個博客主頁的博客采用時間線的方式布局,先發布的文章會在最前面展示;博客編輯功能同時支持Markdown編輯器編輯,具體的功能實作小伙伴們繼續往下看!
二、Java后端介面開發
(1)資料庫設計
在資料庫設計上主要就是兩個表,一個用戶資訊表和一個博客資訊表,
博客資訊表中的資料ID會和用戶ID相對應,詳細的表結構如下:

(2)整合MybatisPlus
平常我們使用的都是mybatis來做資料庫操作,MybatisPlus是在Mybatis的基礎上興起的,我個人的理解是它在Mybatis和逆向工程的結合,可以直接讀取我們的資料庫,并且自動的生成*Mapper.xml、Dao、Service中的代碼,提高我們的開發效率,
整合MybatisPlus的步驟如下:
第一步,匯入所需jar包
在這里我們需要匯入MybatisPlus所依賴的jar包,同時因為MybatisPlus需要涉及到代碼的自動生成,所以還需要引入freemarker的頁面模版引擎,
<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<!--freemarker模版引擎依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
<scope>runtime</scope>
</dependency>
<!--mp代碼生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
第二步、寫入組態檔
因為我們需要連接資料庫嘛,所以當然需要用到資料庫連接驅動,同時還需要在組態檔中進行配置,指定好我們的資料庫驅動、用戶名、密碼、資料庫名稱這些,
同時還需要指定好MybatisPlus掃描的xml檔案,
#配置資料庫資訊
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: ADMIN
#指定mybatisPlus掃描的xml檔案
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
第三步、開啟mapper介面掃描,添加分頁插件
在這里需要實作一個分頁插件PaginationInterceptor,使用該分頁插件的目的很簡單,就是讓我們在每次查詢到的結果以分頁的形式展示出來,該插件是寫在MybatisPlusConfig類下的,
同時還有一點需要注意的是,在添加該組態檔的時候我們需要在類上增加@MapperScan("")注解,在其中傳入我們想要將介面寫入到的包名,該介面的目的就是執行想要變成實作類的介面所在的包,如@MapperScan("com.gyg.mapper")
/**
* mybatisPlus配置
*/
@Configuration
@EnableTransactionManagement
@MapperScan("com.gyg.mapper") //指定變成實作類的介面所在的包
public class MybatisPlusConfig {
/**
* 實作一個分頁插件PaginationInterceptor
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
第四步、生成相關代碼
想要通過mybatisplus生成代碼,官方是給了我們一個工具類的,通過該工具類,我們可以寫入自己的引數,然后就可以自動的生成相關的代碼了,
工具類名叫:CodeGenerator ,使用時我們需要將其和springboot的啟動類放置在同級目錄下,啟動運行之后,輸入我們想要生成對應代碼的表名即可,
工具類的代碼比較長,我放置在了gitee上,【原始碼鏈接】
運行這個代碼生成器我們就可以自動的生成相關資料表的mapper、dao、service等內容了!
現在資料庫相關的代碼已經是基本完成了,
(3)統一結果封裝
由于我們的資料都是需要通過json串的形式回傳給我們的前端頁面的,所以我們就需要對回傳的結果進行一個統一的封裝,在這里我們可以自定義一個封裝類Result,方便我們將資料以統一的格式回傳出去,
該封裝類中一般需要回傳的資訊有三個:
- 狀態碼code(如200表示操作正確,400表示例外)
- 結果訊息msg
- 結果資料data
同時在封裝類中定義全域方法,用于在不同的狀態下回傳不同的資料,封裝類的代碼如下:
import lombok.Data;
import java.io.Serializable;
/**
* 封裝一個回傳統一格式資料的結果集
*/
@Data
public class Result implements Serializable {
private int code; //200正常、非200例外
private String msg; //提示資訊
private Object data; //回傳資料
public static Result success(Object data) {
return success(200,"操作成功",data);
}
/**
* 訊息回傳方法
*
* @param code
* @param msg
* @param data
* @return
*/
public static Result success(int code, String msg, Object data) {
Result r = new Result();
r.setCode(code);
r.setMsg(msg);
r.setData(data);
return r;
}
public static Result fail(String msg) {
return fail(400,msg,null);
}
public static Result fail(String msg, Object data) {
return fail(400,msg,data);
}
public static Result fail(int code, String msg, Object data) {
Result r = new Result();
r.setCode(code);
r.setMsg(msg);
r.setData(data);
return r;
}
}
(4)整合shiro+jwt實作安全驗證
在進行安全驗證的時候我采用的是shiro+jwt結合的方式,大概驗證思路是這樣的:
前端將登陸資訊傳送過來之后,通過shiro的Realm進行安全驗證,如果驗證不通過,那么直接將錯誤資訊回傳到前端,如果登錄資訊驗證通過,就將用戶資訊存盤到服務器端,然后通過jwtUtils工具類根據用戶的ID生成一個token,并且將該token放入回傳請求的請求頭中,攜帶給瀏覽器,瀏覽器在接收到服務器的回傳的請求的時候,就會決議并獲取到該token,并將該token存盤到本地;
這樣在瀏覽器每次向服務器發送請求的時候都會從本地攜帶上該token,服務器也會對每次瀏覽器發送的請求進行驗證,驗證瀏覽器回傳的token和服務器端保存的token是否相同,如果相同就放行進行處理;如果不相同就將錯誤資訊回傳到瀏覽器,
附上一個請求程序的圖示:

安全驗證所用到的類有:
- ShiroConfig:用于配置shiro的驗證資訊
- AccountRealm:用于對瀏覽器回傳的登錄資訊進行驗證
- JwtToken:封裝和獲取token中的資料
- AccountProfile:登錄之后回傳的用戶資訊的一個載體
- JwtFilter:jwt過濾器,用于過濾瀏覽器的請求
其中的代碼比較多,我就放置在的Gitee上,小伙伴們可以在其中獲取【原始碼鏈接】
(5)全域例外處理
無論我們平常在進行什么樣的專案開發,進行全域例外處理都是一個非常好的習慣,進行全域例外處理,它可以將我們的錯誤資訊用最簡單的方式表示出來,并不會出現大量的報錯資訊,方便我們查閱,在這里我宣告了幾個在專案中經常會遇到的報錯資訊,
/**
* 例外處理工具類
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 運行時例外
* @param e
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST) //判斷回傳訊息是否正常
@ExceptionHandler(value = RuntimeException.class)
public Result handler(RuntimeException e){
log.error("運行時例外---------->>>" + e);
return Result.fail(e.getMessage());
}
/**
* shiro運行例外
* @param e
* @return
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED) //判斷回傳訊息是否正常,沒有權限例外
@ExceptionHandler(value = ShiroException.class)
public Result handler(ShiroException e){
log.error("shiro例外---------->>>" + e);
return Result.fail(401,e.getMessage(),null);
}
/**
* 物體校驗例外
* @param e
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST) //判斷回傳訊息是否正常,沒有權限例外
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result handler(MethodArgumentNotValidException e){
log.error("物體檢驗例外例外---------->>>" + e);
BindingResult bindingResult = e.getBindingResult();
ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
return Result.fail(objectError.getDefaultMessage());
}
/**
* 處理斷言例外
* @param e
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST) //判斷回傳訊息是否正常,沒有權限例外
@ExceptionHandler(value = IllegalArgumentException.class)
public Result handler(IllegalArgumentException e){
log.error("斷言例外例外---------->>>" + e);
return Result.fail(e.getMessage());
}
}
(6)物體校驗
在表單資料提交的時候,我們通常會對資料進行校驗,比如不能為空,或長度不能小于指定值等,在前端我們可以通過js插件來完成,但是如果在后端的話,我們可以通過使用Hibernate validatior的方式來進行校驗,
在springboot中已經自動集成了Hibernate validatior的校驗,我們只需要在代碼中直接使用就可以了,
所以我們只需要在物體的屬性上添加相應的校驗規則就可以了,比如在user實體類中:
/**
*
* @author 關注公眾號:碼猿編程日記
* @since 2021-09-21
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("m_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@NotBlank(message = "用戶名不能為空")
private String username;
private String avatar;
@NotBlank(message = "郵箱不能為空")
@Email(message = "郵箱格式不正確")
private String email;
private String password;
private Integer status;
private LocalDateTime created;
private LocalDateTime lastLogin;
}
(7)跨域問題
由于我們做的是前后端分離的專案,所以在請求發送上一定會出現同源策略的相關問題,這就需要我們解決跨域問題了,關于在前后端互動中解決跨域問題,我專門寫了一篇博客,小伙伴們可以去看那一篇《SpringBoot與Vue互動解決跨域問題》
在springboot的后端解決跨域問題的策略比較簡單,只需要添加一個類CorsConfig,并且讓它實作WebMvcConfigurer介面, 其中代碼如下,一般在開發的時候直接將代碼復制過去就可以了,
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 解決跨域問題
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
(8)登錄介面開發
登錄介面的開發思路很簡單,就是接收前端發送過來的登錄資訊,進行驗證是否通過,同時還有一個退出登錄的介面,傳入用戶的資訊,確定是在登錄狀態時可以實作退出登錄操作,
代碼如下;
@RestController
public class AccountController {
@Autowired
UserService userService;
@Autowired
JwtUtils jwtUtils;
@PostMapping("/login")
public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response) {
System.out.println("用戶名和密碼:" + loginDto.getUsername() + " " + loginDto.getPassword());
// 獲取到當前用戶
Subject subject = SecurityUtils.getSubject();
// 封裝用戶名和密碼
UsernamePasswordToken token = new UsernamePasswordToken(loginDto.getUsername(), loginDto.getPassword());
System.out.println("封裝用戶名和密碼成功!!!");
try {
// 使用shiro進行用戶驗證
subject.login(token);
// 如果驗證通過再根據用戶名查找到該用戶
User user = userService.getOne(new QueryWrapper<User>().eq("username", loginDto.getUsername()));
Assert.notNull(user, "用戶不存在!");
if (!user.getPassword().equals(loginDto.getPassword())) {
return Result.fail("密碼錯誤!");
}
// 根據用戶id生成一個jwt
String jwt = jwtUtils.generateToken(user.getId());
// 將jwt寫入
response.setHeader("authorization", jwt);
response.setHeader("Access-Control-Expose-Headers", "authorization");
// 如果正確就回傳用戶資訊
return Result.success(MapUtil.builder()
.put("id", user.getId())
.put("username", user.getUsername())
.put("avatar", user.getAvatar())
.put("email", user.getEmail())
.map()
);
} catch (UnknownAccountException e) {
return Result.fail("用戶不存在2");
} catch (IncorrectCredentialsException e) {
return Result.fail("密碼不正確2");
}
}
/**
* 退出登錄
*
* @return
*/
@RequiresAuthentication
@GetMapping("/logout")
public Result logout() {
Subject subject = SecurityUtils.getSubject();
// AccountProfile profile = (AccountProfile) subject.getPrincipal();
// System.out.println(profile.getId());
// 會請求到logout
subject.logout();
return Result.success("退出成功");
}
@RequiresAuthentication
@GetMapping("/testlogin")
public Result testlogin() {
User user = userService.getById(1L);
return Result.success(user);
}
}
(9)博客介面開發
博客介面中主要實作的功能有:回傳主頁資訊,回傳指定博客資訊,編輯和發布博客、洗掉博客的功能,其中編輯和洗掉博客只有在登錄狀態下才能請求成功,其他兩個請求無需進行登錄,
代碼如下:
/**
* @author 關注公眾號:碼猿編程日記
* @since 2021-09-21
*/
@RestController
//@RequestMapping("/blog")
public class BlogController {
@Autowired
BlogService blogService;
/**
* 分頁博客頁
*
* @param currentPage
* @return
*/
@GetMapping("/blogs")
public Result list(@RequestParam(defaultValue = "1") Integer currentPage) {
Page page = new Page(currentPage, 5);
AccountProfile accountProfile = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
System.out.println(accountProfile);
IPage<Blog> pageDate = blogService.page(page, new QueryWrapper<Blog>().orderByDesc("created"));
return Result.success(pageDate);
}
/**
* 查找指定的博客
*
* @param id
* @return
*/
@GetMapping("/blog/{id}")
public Result detail(@PathVariable(name = "id") long id) {
Blog blog = blogService.getById(id);
// 用斷言來來判斷文章是否找不到
Assert.notNull(blog, "該博客已經被洗掉!");
// 回傳該博客資料
return Result.success(blog);
}
/**
* @param blog
* @return
*/
// 只有登錄之后才能編輯
@RequiresAuthentication
@PostMapping("/blog/edit")
public Result edit(@Validated @RequestBody Blog blog) {
System.out.println("編輯測驗11111111111111111");
System.out.println(blog.toString());
System.out.println("當前用戶ID:" + ShiroUtil.getProfile().getId());
System.out.println(blog.toString());
// System.out.println("當前用戶id:" + ShiroUtil.getSubjectID());
Blog temp = null;
// 如果博客id不為空,就是編輯
if (blog.getId() != null) {
temp = blogService.getById(blog.getId());
// 每一個用戶只能編輯自己的文章
Assert.isTrue(temp.getUserId().equals(ShiroUtil.getProfile().getId()), "你沒有權限編輯");
} else {
// 如果id為空,就是添加
temp = new Blog();
// 將這篇文章添加給當前用戶的id
temp.setUserId(ShiroUtil.getProfile().getId());
// 博客創建時間
temp.setCreated(LocalDateTime.now());
temp.setStatus(0);
}
// 將兩個物件進行復制,指定那些欄位不復制
//BeanUtil.copyProperties("轉換前的類","轉換后的類");
BeanUtil.copyProperties(blog, temp, "id", "userId", "created", "status");
//保存或者更新這一篇文章
blogService.saveOrUpdate(temp);
return Result.success("操作成功");
}
/**
* 根據博客ID洗掉博客
* @param id
* @return
*/
@RequiresAuthentication
@PostMapping("/blog/delete/{id}")
public Result deleteBlog(@PathVariable("id") long id){
System.out.println(id);
System.out.println("------------");
// int bid = Integer.parseInt(id);
boolean isRemove = blogService.removeById(id);
if (!isRemove){
return Result.fail("洗掉失敗!");
}
return Result.success("洗掉成功!");
}
}
以上就是我們后臺介面開發的全部程序,在開發完成之后需要進行相關的介面測驗,測驗完成無誤之后就可以進行前臺頁面的開發了,
三、Vue前端頁面開發
前端頁面的開發我們是基于Vue和Element-Ui的,同時涉及axios發送請求,markdown編輯器的引入、登錄驗證、跨域請求等問題,
博客主頁的頁面是這樣的:

接下來和大家分享一下前端頁面的開發流程,
(1)安裝Element-UI
Element-UI是進行前端開發的一個組件庫,官網地址,這里面提供了各種已經開發好的組件供我們使用,
Element - The world's most popular Vue UI framework
使用該組件庫我們首先是需要引入的,在vue的根目錄下,輸入如下命令:
# 切換到專案根目錄
cd vueblog-vue
# 安裝element-ui
npm install element-ui --save
之后打開專案的src目錄下的main.js檔案,引入Element-UI依賴,
import Element from 'element-ui'
import "element-ui/lib/theme-chalk/index.css"
Vue.use(Element)
到現在,組件庫中的組件我們就可以任意使用了,
(2)安裝axios
axios是一個基于promise的HTTP庫,在我們進行前后端專案開發的時候,使用該工具可以提高我們的開發效率,【axios官網】
Axios的安裝命令如下:
cnpm install axios --save
同樣需要在main.js中全域引入axios,
import axios from 'axios'
Vue.prototype.$axios = axios
之后我們就可以通過this.$axios.get()來發起我們的請求了!
(3)配置頁面路由
接下來是定義頁面路由,定義頁面路由的目的是我們在訪問相應路徑的時候,可以根據路由來確定到我們將要訪問的頁面,
在views文件夾中的頁面有:
- BlogDetail.vue(博客詳情頁)
- BlogEdit.vue(編輯博客)
- Blogs.vue(博客串列)
- Login.vue(登錄頁面)
頁面路由設定在router檔案下的index.js中,配置如下:
/**
* 路由注冊中心
*/
import Vue from 'vue'
import VueRouter from 'vue-router'
//注冊頁面
import Login from '../views/Login.vue'
import Blogs from '../views/Blogs.vue'
import BlogEdit from '../views/BlogEdit.vue'
import BlogDetail from '../views/BlogDetail.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'index',
redirect: {name: "Blogs"} //頁面重定向
},
{
path: '/blogs',
name: 'Blogs',
component: Blogs
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/blog/add',
name: 'BlogAdd',
component: BlogEdit,
//添加權限訪問,表示只有登錄之后才能進行該操作
meta: {
requireAuth: true
}
},
{
path: '/blog/:blogId/edit',
name: 'BlogEdit',
component: BlogEdit,
//添加權限訪問,表示只有登錄之后才能進行該操作
meta: {
requireAuth: true
}
},
{
path: '/blog/:blogId',
name: 'BlogDetail',
component: BlogDetail
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
在上述代碼中帶有meta:requireAuth: true說明是需要登錄之后才能訪問的受限資源,后面我們路由權限攔截時候會講到這個,
(4)登錄頁面
登錄頁面我們這里是由用戶名和密碼進行登錄的,組件我采用了element-ui中的組件,所以在登錄中直接就有了登錄校驗的功能,
?
登錄驗證
在這里點擊登錄按鈕之后,會有一個驗證登錄的程序,簡單說一下驗證的思路,
我們發起登錄請求之后,獲取到它回傳的請求,查看請求中是否存在我們需要的jwttoken,如果存在的,那么我們就將獲取到的token和用戶資訊共享給我們的瀏覽器,之后跳轉到主頁,如果不存在,就彈窗提示,并且不做任何操作,
代碼如下:
methods: {
/**提交表單**/
async submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
// alert('submit!');
//提交登錄資訊
//獲取到當前的this物件
const _this = this;
this.$axios.post("/login", this.ruleForm).then(res => {
console.log(res.data)
const jwt = res.headers["authorization"]
if (jwt === null){
this.$alert('用戶名或密碼錯誤!!', '提示', {
confirmButtonText: '確定',
callback: action => {
// _this.$router.push("/blogs")
}
});
}else {
const userInfo = res.data.data
console.log(jwt)
console.log(userInfo)
//把資料共享出去
_this.$store.commit("SET_TOKEN", jwt);
_this.$store.commit("SET_USERINFO", userInfo);
//獲取
console.log(_this.$store.getters.getUser)
//頁面跳轉
_this.$router.push("/blogs")
}
});
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
token狀態同步
在上述代碼中,我們用到了$store來同步token和用戶資訊,那么這個同步是如何完成的呢,其實是我們在store檔案下的index.js中進行了封裝和設定,
存盤token,我們用的是localStorage,存盤用戶資訊,我們用的是sessionStorage,畢竟用戶資訊我們不需要長久保存,保存了token資訊,我們隨時都可以初始化用戶資訊,
index.js中的代碼如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// token: "",
//用戶的資訊可以直接從瀏覽器中取出來
token: localStorage.getItem("token"),
//反序列化操作
userInfo: JSON.parse(sessionStorage.getItem("userInfo"))
},
mutations: {
/**類似set操作*/
//給token賦值
SET_TOKEN: (state, token) => {
state.token = token;
//將資訊存盤到瀏覽器中,以至于瀏覽器關閉時資訊還在
localStorage.setItem("token", token);
},
//給userinfo賦值
SET_USERINFO: (state, userInfo) => {
state.userInfo = userInfo;
//session會在每次瀏覽器關閉時清空,在重新登錄后再生成
//由于sessionStorage不能存盤物件,所以要將其存盤成字串的形式
sessionStorage.setItem("userInfo", JSON.stringify(userInfo));
},
//移除用戶資訊
REMOVE_INFO: (state) => {
//移除用戶資訊時將用戶所有的資訊都置為空
state.token = "";
state.userInfo = {};
localStorage.setItem("token", "");
sessionStorage.setItem("userInfo", JSON.stringify(""));
}
},
getters: {
/**類似get請求*/
//獲取用戶資訊
getUser: state => {
return state.userInfo;
}
},
actions: {},
modules: {}
})
定義全域axios攔截器
由于我們的登錄有時候會出現密碼輸出錯誤的情況,雖然不需要做任何操作,但是有時候我們還是需要進行彈窗提示,這樣對于這種錯誤資訊的彈窗,我們就可以對其進行統一的封裝和設定,所以我對axios設定一個攔截器,包括前置攔截和后置攔截,如果說我們回傳資料的code或者status不正常就會彈窗提示相應的資訊,
操作是在在src目錄下創建一個檔案axios.js(與main.js同級),定義axios的攔截:
import axios from "axios"
import Element from "element-ui"
import router from "../router"
import store from "../store";
//設定統一請求路徑
axios.defaults.baseURL = "/api"
//前置攔截
axios.interceptors.request.use(config => {
return config
})
/**
* 對請求的回傳資料進行過濾
*/
axios.interceptors.response.use(response => {
let res = response.data;
console.log("=================")
console.log(res)
console.log("=================")
//如果狀態碼是200,直接放行
if (res.code === 200) {
return response
} else {
//如果是用戶名錯誤會直接斷言處理,不會到達這一步!
//彈窗提示!
Element.Message.error('用戶名或密碼錯誤!', {duration: 3 * 1000})
//回傳錯誤資訊
return Promise.reject(response.data.msg)
}
},
//如果是非密碼錯誤,會到達這一步
error => {
console.log(error)
//如果回傳的資料里面是空
if (error.response.data){
error.message = error.response.data.msg;
}
//如果狀態碼是401,
if (error.response.status === 401){
store.commit("REMOVE_INFO")
router.push("/login")
}
//彈出錯誤資訊
Element.Message.error(error.message, {duration: 3 * 1000})
return Promise.reject(error)
})
之后別忘了在main,js檔案中匯入axios,js檔案,
import './axios.js' // 請求攔截
簡單說一下這幾個攔截的作用:
前置攔截:在請求之前的攔截,可以在其中統一為所有需要權限的請求裝配上header的token資訊,這樣就不要在使用的時候再配置,
后綴攔截:在請求回傳之后的攔截,可以在請求之后對回傳的資料進行處理和驗證,
(5)博客串列
在我們登錄完成之后就會進入了博客的主頁面,在該頁面主要是展示了當前錄入到系統中的博客資訊,界面如下:
?
整個博客的顯示是按照時間線的方式展開的,最后發布的博客會在第一個出現,同時你會發現在博客主頁的頭部會展示我們的一些基本資訊,包括個人資訊以及編輯和退出的功能,這個頭部資訊會一直顯示在我們的頁面中,所以為了能夠實作代碼復用,減少代碼的使用量,我們將頭部資訊全部都抽取了出來,放置在了Header.vue頁面中,
<template>
<div class="m_content">
<h3>歡迎來到{{user.username}}的博客</h3>
<div class="block">
<el-avatar :size="50" :src="user.avatar"></el-avatar>
<div>{{user.username}}</div>
</div>
<div class="maction">
<span><el-link type="primary" href="/blogs">主頁</el-link></span>
<el-divider direction="vertical"></el-divider>
<span><el-link type="success" href="/blog/add">發表博客</el-link></span>
<span v-show="!haslogin">
<el-divider direction="vertical"></el-divider>
<span><el-link type="warning" href="/login">登錄</el-link></span>
</span>
<span v-show="haslogin">
<el-divider v-show="haslogin" direction="vertical"></el-divider>
<span ><el-link type="danger" @click="logout">退出</el-link></span>
</span>
</div>
</div>
</template>
<script>
export default {
name: "Header",
data() {
return {
user: {
username: "請先登錄",
avatar: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
},
haslogin: false
}
},
//寫入方法
methods: {
//退出登錄
logout() {
const _this = this;
//發送退出登錄請求
_this.$axios.get("/logout", {
//由于只有在登錄的時候才能進行退出,所以將token資訊攜帶回去
headers: {
"authorization": localStorage.getItem("token")
}
}).then(res => {
//清空資料資訊
_this.$store.commit("REMOVE_INFO")
//回傳到登錄界面
_this.$router.push("/login")
})
},
},
//執行一個初始化操作
created() {
//如果用戶名存在,就回顯出來
if (this.$store.getters.getUser.username) {
this.user.username = this.$store.getters.getUser.username
this.user.avatar = this.$store.getters.getUser.avatar
this.haslogin = true
}
}
}
</script>
<style scoped>
.m_content {
max-width: 960px;
margin: 0 auto;
text-align: center;
}
.maction {
margin: 10px 0px;
}
</style>
如果在其他頁面中需要該頭部資訊時,只需要將Header頁面參考到該頁面中,之后在內容中寫入即可,如下:
import Header from "@/components/Header";
data() {
components: {Header}
}
然后模板中呼叫組件
<Header></Header>
在該博客主頁中包括博客分頁,以及博客排列,因為我們使用了分頁組件,所以在回傳的資訊會直接帶有分頁資訊,我們直接拿來用就可以了,
<template>
<div>
<Header></Header>
<div class="block">
<el-timeline>
<el-timeline-item :timestamp="blog.created" placement="top" v-for="blog in blogs">
<el-card>
<router-link :to="{name:'BlogDetail',params:{blogId:blog.id}}">
<h4>{{blog.title}}</h4>
</router-link>
<p>{{blog.description}}</p>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
<el-pagination class="mpage"
background
layout="prev, pager, next"
:current-page="currentPage"
:page-size="pageSize"
:total="total"
@current-change=page
>
</el-pagination>
</div>
</template>
<script>
//匯入公共的Header
import Header from "../components/Header";
export default {
name: "Blogs",
//將Header注冊進去
components: {Header},
//回傳的資料
data() {
return {
blogs: {},
currentPage: 1, //當前頁
total: 0, //總共多少頁
pageSize: 5 //每一頁的資料個數
}
},
mounted() {
},
methods: {
//請求指定頁的方法
page(currentPage) {
const _this = this;
_this.$axios.get("/blogs?currentPage=" + currentPage,{
headers: {
"authorization": localStorage.getItem("token")
}
}).then(res => {
console.log(res)
//從獲取到的資料中進行賦值
_this.blogs = res.data.data.records
_this.currentPage = res.data.data.current
_this.total = res.data.data.total
_this.pageSize = res.data.data.size
})
}
},
created() {
this.page(1)
}
}
</script>
<style scoped>
.mpage {
margin: 0 auto;
text-align: center;
}
</style>
data()中直接定義博客串列blogs、以及一些分頁資訊,methods()中定義分頁的呼叫介面page(currentPage),引數是需要調整的頁碼currentPage,得到結果之后直接賦值即可,然后初始化時候,直接在mounted()方法中呼叫第一頁this.page(1),
(6)博客編輯
博客編輯頁面中我們可以對已經發布的博客進行編輯,也可以發布新的博文,但是該項功能是只有在登錄的狀態下才能使用的,在博客編輯頁面中,我們引入了markdown編輯器,該編輯器有關于vue的支持,我們直接匯入相關依賴拿來用就可以了,
Markdown編輯器引入
第一步、進入插件
Markdown編輯器中比較好用的插件是mavon-editor,首先我們需要安裝相關插件,
cnpm install mavon-editor --save
第二步、全域注冊
引入之后如果想要使用,當然是需要在main.js檔案中全域注冊的,
// 全域注冊
import Vue from 'vue'
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
// use
Vue.use(mavonEditor)
第三步、定義到頁面中
Markdown編輯器的使用,在注冊到全域頁面中之后,只需要我們在頁面中使用如下代碼引入即可,
<mavon-editor v-model="editForm.content"/>
以上就是vue引入markdown編輯器的步驟了,
另外附上博客編輯頁面的代碼:
<template>
<div>
<Header></Header>
<div class="m_content">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="標題" prop="title">
<el-input v-model="ruleForm.title"></el-input>
</el-form-item>
<el-form-item label="摘要" prop="description">
<el-input type="textarea" v-model="ruleForm.description"></el-input>
</el-form-item>
<el-form-item label="內容" prop="content">
<mavon-editor v-model="ruleForm.content"></mavon-editor>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">立即發布</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import Header from "../components/Header";
export default {
name: "BlogEdit",
components: {Header},
data() {
return {
ruleForm: {
id: "",
title: '',
description: '',
content: '',
},
rules: {
title: [
{required: true, message: '請輸入標題', trigger: 'blur'},
{min: 5, max: 100, message: '長度在 5 到 100 個字符', trigger: 'blur'}
],
description: [
{required: true, message: '請輸入摘要', trigger: 'blur'}
],
content: [
{required: true, message: '請輸入內容', trigger: 'blur'}
],
}
};
},
mounted() {
console.log(localStorage.getItem("token"))
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
const _this = this
console.log(this.ruleForm)
//發送編輯的請求
_this.$axios.post("/blog/edit", this.ruleForm, {
//添加請求頭部token
headers: {
"authorization": localStorage.getItem("token")
}
}).then(res => {
console.log(res)
this.$alert('編輯操作成功!', '提示', {
confirmButtonText: '確定',
callback: action => {
_this.$router.push("/blogs")
}
});
})
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
//頁面渲染前的后置方法
created() {
//將博客資訊回顯,作用在進行編輯的時候可以在編輯頁面出現用戶的資訊
//用戶發送“/blog/{id}/edit”型別的請求才會回顯
//用戶發送“/blog/add”添加博客的請求時,獲取不到值
const blogId = this.$route.params.blogId
console.log(blogId)
const _this = this;
if (blogId){
_this.$axios.get("/blog/" + blogId).then(res=>{
const blog = res.data.data;
_this.ruleForm.id = blog.id
_this.ruleForm.title = blog.title
_this.ruleForm.description = blog.description
_this.ruleForm.content = blog.content
})
}
}
}
</script>
<style scoped>
.m_content {
margin: 0 auto;
}
</style>
效果如下:

(7)博客詳情頁
在博客詳情頁中,我們需要回顯我們的博客資訊,但是我們在發布博客的時候,使用的是markdown編輯器,所以在回顯的時候,我們回顯的內容帶有markdown標簽的,那么應該如何回顯我們正式編輯的文本呢?
在這里需要使用一個插件markdown-it,它的作用是決議md檔案,之后再匯入github-markdown-it,使用md樣式,
# 用于決議md檔案
cnpm install markdown-it --save
# md樣式
cnpm install github-markdown-css
在需要寫入博客文本的地方的使用方法是:
<div class="content markdown-body" v-html="blog.content"></div>
其中還涉及到一些md相關的渲染,可以看代碼,
具體邏輯是這樣的;初始化create()方法中呼叫getBlog()方法,請求博客詳情介面,回傳的博客詳情content通過markdown-it工具進行渲染,
再匯入樣式:
import 'github-markdown.css'
然后在content的div中添加class為markdown-body即可
<template>
<div>
<Header></Header>
<div class="mblog">
<h2>{{blog.title}}</h2>
<div>
<el-link icon="el-icon-edit" v-if="ownBlog">
<!--攜帶博客ID跳轉到編輯頁面,對博客進行修改-->
<router-link :to="{name:'BlogEdit',params:{blogId: blog.id}}">
編輯
</router-link>
</el-link>
<el-divider direction="vertical"></el-divider>
<el-link type="danger" @click="messageHandel" v-if="ownBlog">
洗掉
</el-link>
</div>
<el-divider></el-divider>
<div class="markdown-body" v-html="blog.content"></div>
</div>
<el-dialog
:title="title"
:visible.sync="centerDialogVisible"
width="30%"
center>
<span slot="footer" class="dialog-footer">
<el-button @click="centerDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="deleteBlog">確 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
// 匯入所需依賴
import Header from "../components/Header";
//為了讓markdown轉換的文本更好看,使用這個檔案進行渲染
import "github-markdown-css/github-markdown.css"
import messageModel from "../components/messageModel";
export default {
name: "BlogDetail",
components: {Header, messageModel},
data() {
return {
centerDialogVisible: false,
title:'您確定要洗掉此條博客嗎?',
blog: {
id: "",
title: "",
content: ""
},
ownBlog: false
}
},
methods:{
messageHandel(){
this.centerDialogVisible = true
},
deleteBlog(){
const _this = this
const blogid = this.$route.params.blogId
_this.$axios.post("/blog/delete/" + blogid,{
headers:{
"authorization":localStorage.getItem("token")
}
}).then(res=>{
console.log(res)
const code = res.data.code
if (code === 200){
this.centerDialogVisible = false
_this.$router.push("/blogs")
}else {
this.$alert('洗掉失敗!', '提示', {
confirmButtonText: '確定',
});
}
})
}
},
created() {
//從路徑中獲取到博客id
const blogId = this.$route.params.blogId
console.log(blogId)
const _this = this
//按照博客id找到指定的博客,
_this.$axios.get("/blog/" + blogId).then(res => {
//獲取到回傳的博客資訊,并且將該資訊回顯給頁面
const blog = res.data.data;
_this.blog.id = blog.id
_this.blog.title = blog.title
_this.blog.description = blog.description
/**
* 將markdown的文本轉為正常文本
* */
//拿到markdown渲染資源物件
var markDownIT = require("markdown-it")
var md = new markDownIT() //獲取到markdown-it的物件
var result = md.render(blog.content) //將markdown文本渲染成html文本
_this.blog.content = result //將正常文本賦值給頁面顯示
//只有在當前的博客的用戶id和當前登錄的用戶的id相符合的時候,才會顯示編輯按鈕
_this.ownBlog = (blog.userId === _this.$store.getters.getUser.id)
})
}
}
</script>
<style scoped>
.mblog {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
width: 100%;
min-height: 760px;
margin: 10px 15px;
}
/deep/.el-dialog__body{
padding: 0 !important
}
</style>
得到的效果如下,其中的博文是我之前在CSDN發布過的,效果基本上一樣,

(8)權限路由攔截
因為我們最開始也提到了部分頁面是需要在登錄的狀態下才能訪問的,那么在前臺應該如何進行攔截呢?其實思路是很簡單的,主要就是給每一個頁面請求添加一個引數,標記其是否是需要在登錄狀態下才能訪問,同時過濾攔截每一個請求,如果該請求是需要登錄才能訪問的,那么就從瀏覽器中獲取token,如果能夠夠獲取到,就放行,否則就跳轉到登錄頁面,
關于這個知識點我單獨總結了一篇博客,歡迎小伙伴們學習!《兩步教你在Vue中設定登錄驗證攔截!》
四、寫在最后&專案總結
到這里整個專案就算是開發完成了,其中也參考了b站大佬的視頻講解,自己也學到到了很多東西,整個專案對學習前后臺分離的新手來說還是非常友好的,其中的shiro+jwt安全驗證、統一結果封裝、以及前臺頁面中路由的設定、存盤和獲取token、跨域解決、登錄驗證等都是非常值得學習的,
專案原始碼我放在gitee了,【原始碼鏈接】,小伙伴們別忘了?star?喲!
一鍵三連加關注!灰小猿帶你上高速啦!???
我是灰小猿,我們下期見!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/307363.html
標籤:其他
上一篇:H5+echarts模擬全國程式員可視化大資料【附完整原始碼】
下一篇:一文搞懂鎖知識

