当前位置:网站首页>開源一套極簡的前後端分離專案腳手架
開源一套極簡的前後端分離專案腳手架
2020-11-06 21:07:00 【itread01】
前言
Fast Scaffold是一套極簡的前後端分離專案腳手架,包含一個portal前端、一個admin後端,可用於快速的搭建前後端分離專案進行二次開發
技術棧
portal前端:vue + element-ui + avue,使用typescript語法編碼
admin後端:springboot + mybatis-plus + mysql,採用jwt進行身份認證
專案結構
portal前端
前端專案,使用的是我們:Vue專案入門例項,在此基礎上做了一下跳轉
引入avue
avue,基於element-ui開發的一個很多騷操作的前端框架,我們也在test測試模組中的Admin頁面中進行了簡單測試
官網:https://avuejs.com/
router配置
router路由配置,新增test模組選單路由,beforeEach中判斷無令牌,跳轉登入頁面
router.beforeEach(async(to, from, next) => {
console.log("跳轉開始,目標:"+to.path);
document.title = `${to.meta.title}`;
//無令牌,跳轉登入頁面
if (to.name !== 'Login' && !TokenUtil.getToken()){
console.log("無令牌,跳轉登入頁面");
next({ name: 'Login' });
}
//跳轉頁面
next();
});
store配置
store配置,新增user屬性,getters提供getUser方法,以及mutations、actions的setUser方法
import Vue from 'vue'
import Vuex from 'vuex'
import User from "@/vo/user";
import CommonUtil from "@/utils/commonUtil";
import {Object} from "@/utils/commonUtil"
import AxiosUtil from "@/utils/axiosUtil";
import TokenUtil from "@/utils/tokenUtil";
import SessionStorageUtil from "@/utils/sessionStorageUtil";
Vue.use(Vuex);
/*
約定,元件不允許直接變更屬於 store 例項的 state,而應執行 action 來分發 (dispatch) 事件通知 store 去改變
*/
export default new Vuex.Store({
state: {
user:User,
},
getters:{
getUser: state => {
return state.user;
}
},
mutations: {
SET_USER: (state, user) => {
state.user = user;
}
},
actions: {
async setUser({commit}){
let thid = this;
console.log("呼叫getUserByToken介面獲取登入使用者!");
AxiosUtil.post(CommonUtil.getAdminUrl()+"/getUserByToken",{token:TokenUtil.getToken()},function (result) {
let data = result.data as Object;
commit('SET_USER', new User(data.id,data.username));
//設定到sessionStorage
SessionStorageUtil.setItem("loginUser",thid.getters.getUser);
});
}
},
modules: {
}
})
工具類封裝
axiosUtil.ts
設定全域性withCredentials,timeout
設定request攔截,在請求頭中設定token令牌
設定response攔截,設定了統一響應異常訊息提示以及令牌無效時跳轉登入頁面
封裝了post、get等靜態方法,方便呼叫
commonUtil.ts
封裝了一下常用、通用方法,比如獲取後端服務地址、獲取登入使用者等
sessionStorageUtil.ts
封裝sessionStorage會話級快取,方便設定快取
tokenUtil.ts
封裝token令牌工具類,方便設定token令牌到cookie
admin後端
後端專案,使用的是我們的:SpringBoot系列——MyBatis-Plus整合封裝,在此基礎上進行了調整
只保留tb_user表模組,其他表以及程式碼模組都不需要,密碼改成MD5加密儲存
配置檔案
server:
port: 10086
spring:
application:
name: admin
datasource: #資料庫相關
url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mvc:
format:
date: yyyy-MM-dd HH:mm:ss
jackson:
date-format: yyyy-MM-dd HH:mm:ss #jackson對響應回去的日期引數進行格式化
time-zone: GMT+8
portal:
url: http://172.16.35.52:10010 #前端地址(用於跨域配置)
token:
secret: huanzi-qch #token加密私鑰(很重要,注意保密)
expire:
time: 86400000 #token有效時長,單位毫秒 24*60*60*1000
cors安全跨域
建立MyConfiguration,開啟cors安全跨域,詳情可看回我們之前的部落格:SpringBoot系列——CORS(跨源資源共享)
@Configuration
public class MyConfiguration {
@Value("${portal.url}")
private String portalUrl;
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(portalUrl)
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true).maxAge(3600);
}
};
}
}
jwt身份認證
maven引入jwt依賴
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.5.0</version>
</dependency>
JwtUtil工具類,封裝生成token,校驗token,以及根據token獲取登入使用者
/**
* JWT工具類
*/
@Component
public class JwtUtil {
/**
* 過期時間,毫秒
*/
private static long TOKEN_EXPIRE_TIME;
@Value("${token.expire.time}")
public void setExpireTime(long expireTime) {
JwtUtil.TOKEN_EXPIRE_TIME = expireTime;
}
/**
* token私鑰
*/
private static String TOKEN_SECRET;
@Value("${token.secret}")
public void setSecret(String secret) {
JwtUtil.TOKEN_SECRET = secret;
}
/**
* 生成簽名
*/
public static String sign(String userId){
//過期時間
Date date = new Date(System.currentTimeMillis() + TOKEN_EXPIRE_TIME);
//私鑰及加密演算法
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
//設定頭資訊
HashMap<String, Object> header = new HashMap<>(2);
header.put("typ", "JWT");
header.put("alg", "HS256");
//附帶userID生成簽名
return JWT.create().withHeader(header).withClaim("userId",userId).withExpiresAt(date).sign(algorithm);
}
/**
* 驗證簽名
*/
public static boolean verity(String token){
//令牌為空
if(StringUtils.isEmpty(token)){
return false;
}
try {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
//是否能解密
DecodedJWT jwt = verifier.verify(token);
//校驗過期時間
if(new Date().after(jwt.getExpiresAt())){
return false;
}
return true;
} catch (IllegalArgumentException | JWTVerificationException e) {
ErrorUtil.errorInfoToString(e);
}
return false;
}
/**
* 根據token獲取使用者id
*/
public static String getUserIdByToken(String token){
try {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("userId").asString();
} catch (IllegalArgumentException | JWTVerificationException e) {
ErrorUtil.errorInfoToString(e);
}
return null;
}
}
登入攔截器
LoginFilter登入攔截器,不攔截登入請求、跨域預檢請求,其他請求全部攔截校驗是否有令牌
PS:我們已經配置了全域性安全跨域,但在攔截器中,PrintWriter.print回去的response,要手動新增一下響應頭標記允許對方跨域
//標記當前請求對方允許跨域訪問
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Access-Control-Allow-Headers","content-type, token");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Allow-Origin",portalUrl);
/**
* 登入攔截器
*/
@Component
public class LoginFilter implements Filter {
@Value("${server.servlet.context-path:}")
private String contextPath;
@Value("${portal.url}")
private String portalUrl;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String method = request.getMethod();
//不攔截登入請求、跨域預檢請求,其他請求全部攔截校驗是否有令牌
if (!"/login".equals(request.getRequestURI().replaceFirst(contextPath,"")) && !"options".equals(method.toLowerCase())) {
String token = request.getHeader("token");
//驗證簽名
if(!JwtUtil.verity(token)){
String dataString = "{\"status\":401,\"message\":\"無效token令牌,訪問失敗,請重新登入系統!\"}";
//清除cookie
Cookie cookie = new Cookie("PORTAL_TOKEN", null);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
//轉json字串並轉成Object物件,設定到Result中並賦值給返回值,記得表明當前頁面可以跨域訪問
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Access-Control-Allow-Headers","content-type, token");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Allow-Origin",portalUrl);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = response.getWriter();
out.print(dataString);
out.flush();
out.close();
return;
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
簡單控制器
IndexController控制器,提供三個post方法:login登入,logout登出,getUserByToken通過token令牌獲取登入使用者
@RestController
@RequestMapping("/")
@Slf4j
public class IndexController {
@Autowired
private TbUserService tbUserService;
/**
* 登入
*/
@PostMapping("login")
public Result<String> login(@RequestBody TbUserVo entityVo){
//只關注使用者名稱、密碼
if(StringUtils.isEmpty(entityVo.getUsername()) || StringUtils.isEmpty(entityVo.getPassword())){
return Result.build(400,"賬號或密碼不能為空......","");
}
TbUserVo tbUserVo = new TbUserVo();
tbUserVo.setUsername(entityVo.getUsername());
//密碼MD5加密後密文儲存,匹配時先MD5加密後匹配
tbUserVo.setPassword(MD5Util.getMD5(entityVo.getPassword()));
Result<List<TbUserVo>> listResult = tbUserService.list(tbUserVo);
if(Result.OK.equals(listResult.getStatus()) && listResult.getData().size() > 0){
TbUserVo userVo = listResult.getData().get(0);
//token
String token = JwtUtil.sign(userVo.getId()+"");
return Result.build(Result.OK,"登入成功!",token);
}
return Result.build(400,"賬號或密碼錯誤...","");
}
/**
* 登出
*/
@PostMapping("logout")
public Result<String> logout(HttpServletResponse response){
//清除cookie
Cookie cookie = new Cookie("PORTAL_TOKEN", null);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
return Result.build(Result.OK,"此路是我開,此樹是我栽,要從此路過,留下token令牌!","");
}
/**
* 通過token令牌獲取登入使用者
*/
@PostMapping("getUserByToken")
public Result<TbUserVo> getUserByToken(@RequestBody TbUserVo entityVo){
String userId = JwtUtil.getUserIdByToken(entityVo.getToken());
Result<TbUserVo> result = tbUserService.get(userId);
result.getData().setPassword(null);
return userId == null ? Result.build(500,"操作失敗!",new TbUserVo()) : result;
}
}
效果演示
登入
這是一個極簡登入頁面、登入功能,沒用令牌,路由會攔截跳到登入頁面
登入成功後儲存token令牌到cookie中,並獲取登入使用者資訊,儲存到Store中
為了解決重新整理頁面Store資料丟失,同時要儲存一份資料到sessionStorage快取,在讀取Store無資料時,先讀取快取,如果存在,再設定回Store中
登出成功後置空Store、sessionStorage
首頁
極簡的專案首頁,路徑/,一般作為專案主頁,現在頁面就是一個簡單的歡迎頁面,包括了幾個router-link路由以及登出按鈕
test測試
集成了vue資料繫結等簡單測試
info測試
獲取當前活躍配置環境分支,讀取配置檔案資訊等簡單測試
admin測試
element-ui配合上avue,可以快速搭建admin後臺管理頁面以及功能
打包部署
portal前端
已經配置好了package.json檔案
"scripts": {
"dev": "vue-cli-service serve --mode dev",
"test": "vue-cli-service test --mode test",
"build": "vue-cli-service build --mode prod"
},
同時,vue.config.js中配置了生成路徑
publicPath: './',
outputDir: 'dist',
assetsDir: 'static',
執行build命令,就會在package.json的同級目錄下面,建立dist資料夾,生成的檔案就在裡面
把生成的檔案放到Tomcat容器或者其他容器中,執行容器,前端portal專案完成部署
admin後端
pom檔案已經設定了打包配置
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<finalName>${project.artifactId}</finalName>
<outputDirectory>package</outputDirectory>
</configuration>
</plugin>
maven直接執行package命令,就會在與pom檔案同級目錄下面建立package資料夾,生成的jar包就在裡面
使用java命令:java -jar admin.jar,執行jar包,後端admin專案完成部署
後記
一套極簡的前後端分離專案腳手架就暫時記錄到這,後續再進行補充
程式碼開源
注:admin後端資料庫檔案在admin後端專案的resources/sql目錄下面
程式碼已經開源、託管到我的GitHub、碼雲:
GitHub:https://github.com/huanzi-qch/fast-scaffold
碼雲:https://gitee.com/huanzi-qch/fast-scaffold
版权声明
本文为[itread01]所创,转载请带上原文链接,感谢
https://www.itread01.com/content/1604667665.html
边栏推荐
- TensorFlow中的Tensor是什么?
- Linked blocking Queue Analysis of blocking queue
- Python download module to accelerate the implementation of recording
- The difference between gbdt and XGB, and the mathematical derivation of gradient descent method and Newton method
- What problems can clean architecture solve? - jbogard
- Introduction to quantitative investment and Trading (Python introduction to financial analysis)
- Analysis of etcd core mechanism
- 一篇文章教会你使用Python网络爬虫下载酷狗音乐
- With the advent of tensorflow 2.0, can pytoch still shake the status of big brother?
- Python基础数据类型——tuple浅析
猜你喜欢

Unity性能优化整理
![[C / C + + 1] clion configuration and running C language](/img/5b/ba96ff4447b150f50560e5d47cb8d1.jpg)
[C / C + + 1] clion configuration and running C language

大道至简 html + js 实现最朴实的小游戏俄罗斯方块

一路踩坑,被迫聊聊 C# 代码调试技巧和远程调试

Interface pressure test: installation, use and instruction of siege pressure test

With the advent of tensorflow 2.0, can pytoch still shake the status of big brother?

零基础打造一款属于自己的网页搜索引擎

零基础打造一款属于自己的网页搜索引擎
![[Xinge education] poor learning host computer series -- building step 7 Simulation Environment](/img/f8/4bb6f887d56a7a18eb55cbec579204.jpg)
[Xinge education] poor learning host computer series -- building step 7 Simulation Environment

一篇文章教会你使用HTML5 SVG 标签
随机推荐
How to use parameters in ES6
Arrangement of basic knowledge points
Python基础变量类型——List浅析
比特币一度突破14000美元,即将面临美国大选考验
For a while, a dynamic thread pool was created, and the source code was put into GitHub
Installing ns-3 on ubuntu18.04
The data of pandas was scrambled and the training machine and testing machine set were selected
Brief introduction of TF flags
Cglib 如何实现多重代理?
Lane change detection
[C] (original) step by step teach you to customize the control element - 04, ProgressBar (progress bar)
After reading this article, I understand a lot of webpack scaffolding
带你学习ES5中新增的方法
I've been rejected by the product manager. Why don't you know
前端都应懂的入门基础-github基础
理解格式化原理
[C / C + + 1] clion configuration and running C language
6.2 handleradapter adapter processor (in-depth analysis of SSM and project practice)
百万年薪,国内工作6年的前辈想和你分享这四点
NLP model Bert: from introduction to mastery (1)