入門基礎項目(SpringBoot+Vue)

文章目錄

  • 1. css布局相關
  • 2. JS
  • 3. Vue 腳手架搭建
  • 4. ElementUI
    • 4.1 引入ElementUI
    • 4.2 首頁
      • 4.2.1 整體框架
      • 4.2.2 Aside-logo
      • 4.2.3 Aside-菜單
      • 4.2.4 Header-左側
      • 4.2.5 Header-右側
      • 4.2.6 iconfont 自定義圖標
      • 4.2.7 完整代碼
    • 4.3 封裝前后端交互工具 axios
      • 4.3.1 安裝 axios
      • 4.3.2 /src/utils/目錄下建立一個request.js
      • 4.3.3 main.js 全局聲明
      • 4.3.4 跨域訪問
    • 4.4 登錄、注冊
      • 4.4.1 整體設計
      • 4.4.2 驗證碼組件
      • 4.4.3 登錄 Login.vue
      • 4.4.4 注冊 Register.vue
      • 4.4.5 router/index.js 配置路由
      • 4.4.6 SpringBoot 解決跨域問題 CorsConfig
      • 4.4.7 后端接口
    • 4.5 SpringBoot集成JWT token實現權限驗證
      • 4.5.1 pom.xml添加JWT依賴
      • 4.5.2 工具類 TokenUtils
      • 4.5.3 login() 方法增加 token 返回
      • 4.5.4 自定義注解 AuthAccess
      • 4.5.5 自定義攔截器 JwtInterceptor
      • 4.5.6 配置攔截器 InterceptorConfig
    • 4.6 單文件、多文件上傳和下載
      • 4.6.1 文件上傳、下載 Java 代碼
    • 4.6 個人信息修改、修改密碼、重置密碼
  • 5. 相關學習網站

參考資料:📖【青哥帶小白做畢設2024】所有教程資料匯總

1. css布局相關

參考資料:📖CSS-布局-flex

2. JS

變量賦值

  • b = a?.name:a 是 undefined 或者 null,b 不報錯
  • b = a ?? c:a 是 undefined 或者 null,則賦值 c 給 b
  • b = a || c:a 是 undefined 或者 null,則賦值 c 給 b

數組操作

  • 新增元素:push()
  • 刪除元素:splice()、pop()、shift()
  • 截取數組:slice()
  • 合并數組:concat()
  • 字符串變數組:split()
  • 數組變字符串:join(),默認使用 , 逗號分割
  • 獲取元素序號:indexOf()
  • filter():篩選元素
    • let newArr = users.filter(v => v.name !== ‘李四’ && v.name !== ‘王二’) // 刪除數組的指定元素
  • find():查找
  • map():轉換
    • [1,2,3].map(v => v *2)
  • forEach():遍歷
  • reduce():合并
    • 語法:arr.reduce(function(prev, cur, index, arr){...}, init);
    • prev:累計器累計回調的返回值,表示上一次調用回調時的返回值,或者初始值 init
    • cur:表示當前正在處理的數組元素
    • index:表示當前正在處理的數組元素的索引
    • arr:表示原數組
    • init:初始值
    • arr = [1,2,3]; let sum = arr.reduce((pre, cur) => pre + cur)

獲取數組中每個字符出現的個數

let names =['a', 'b', 'c', 'a', 'b']
let res = names.reduce((all, cur) => {if (cur in all) {all[cur]++;} else {all[cur] = 1;}return all
}, {})console.log("res=" , res) // {a: 2, b: 2, c: 1}

3. Vue 腳手架搭建

npm 配置淘寶鏡像:

npm config set registry http://registry.npm.taobao.org/

安裝vue/cli

npm install -g @vue/cli
vue --version

創建項目

vue create vue

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

配置文件 vue.config.js

配置啟動端口、title等,修改后需要重啟生效。

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,devServer: {port: 7000},chainWebpack: config => {config.plugin('html').tap(args => {args[0].title = "管理平臺";return args;})}
})

App.vue

<template><div id="app"><router-view/></div>
</template>

router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'Vue.use(VueRouter)const routes = [{path: '/',name: 'home',component: () => import('../views/HomeView.vue')}
]const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes
})export default router

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'import '@/assets/css/global.css'Vue.config.productionTip = falsenew Vue({router,render: h => h(App)
}).$mount('#app')

HomeView.vue

<template><div>主頁</div>
</template><script>
export default {name: "HomeView",
};
</script>

assets/css/global.css

* {box-sizing: border-box;
}body {color: #333;font-size: 14px;margin: 0;padding: 0;
}

在這里插入圖片描述

4. ElementUI

4.1 引入ElementUI

npm 安裝

npm i element-ui -S

在 main.js 里引入 ElementUI

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';Vue.use(ElementUI, { size: 'small' });

4.2 首頁

在這里插入圖片描述

4.2.1 整體框架

<el-container><el-aside>Aside</el-aside><el-container><el-header>Header</el-header><el-main>Main</el-main></el-container>
</el-container>

4.2.2 Aside-logo

<div style="height: 60px; line-height: 60px; font-size: 20px; display: flex; align-items: center; justify-content: center"><img src="@/assets/logo1.png" style="width: 30px;" alt=""><span class="logo-title" v-show="!isCollapse">Honey2024</span>
</div>

4.2.3 Aside-菜單

<el-menu:collapse="isCollapse":collapse-transition="false"routerbackground-color="#001529" text-color="rgba(255, 255, 255, 0.65)" active-text-color="#fff"style="border: none":default-active="$route.path"
><el-menu-item index="/"><i class="el-icon-house"></i> <span slot="title">系統首頁</span></el-menu-item><el-submenu index="2"><template slot="title"><i class="el-icon-menu"></i> <span>信息管理</span></template><el-menu-item index="/user">用戶信息</el-menu-item><el-menu-item index="/admin">管理員信息</el-menu-item></el-submenu>
</el-menu>

4.2.4 Header-左側

<i :class="collapseIcon" @click="handleCollapse" style="font-size: 26px"></i><el-breadcrumb separator="/" style="margin-left: 20px"><el-breadcrumb-item :to="{ path: '/' }">首頁</el-breadcrumb-item><el-breadcrumb-item :to="{ path: '/' }">課程管理</el-breadcrumb-item>
</el-breadcrumb>

4.2.5 Header-右側

<div style="flex: 1; display: flex; justify-content: flex-end; align-items: center"><!-- 全屏按鈕 --><i class="el-icon-full-screen" @click="handleFull" style="font-size: 25px"></i><!-- 下拉框 --><el-dropdown placement="bottom"><div style="display: flex; align-items: center; cursor: pointer"><img src="@/assets/logo1.png" alt="" style="width: 40px; height: 40px; margin: 0 5px"><span>管理員</span></div><el-dropdown-menu slot="dropdown"><el-dropdown-item>個人信息</el-dropdown-item><el-dropdown-item>修改密碼</el-dropdown-item><el-dropdown-item>退出登錄</el-dropdown-item></el-dropdown-menu></el-dropdown>
</div>

4.2.6 iconfont 自定義圖標

iconfont-阿里巴巴矢量圖標庫

搜索需要的圖標,添加到自己的項目
在這里插入圖片描述

設置圖標前綴el-icon-Font Familyelement-icons
在這里插入圖片描述

點擊 “下載至本地”
在這里插入圖片描述

賦值4個文件到項目
在這里插入圖片描述

在 main.js 里引入 iconfont

import '@/assets/css/iconfont/iconfont.css'

4.2.7 完整代碼

<template><div><el-container><!--  側邊欄  --><el-aside:width="asideWidth"style="min-height: 100vh; background-color: #001529"><!-- logo+項目名稱 --><div style="height: 60px; color: white; display: flex; align-items: center; justify-content: center"><img src="@/assets/logo1.png" alt="" style="width: 40px; height: 40px"><span class="logo-title" v-show="!isCollapse">honey2024</span></div><!-- 側邊菜單欄 --><el-menu:collapse="isCollapse":collapse-transition="false"routerbackground-color="#001529" text-color="rgba(255, 255, 255, 0.65)" active-text-color="#fff"style="border: none":default-active="$route.path"><el-menu-item index="/"><i class="el-icon-house"></i> <span slot="title">系統首頁</span></el-menu-item><el-submenu index="2"><template slot="title"><i class="el-icon-menu"></i> <span>信息管理</span></template><el-menu-item index="/user">用戶信息</el-menu-item><el-menu-item index="/admin">管理員信息</el-menu-item></el-submenu></el-menu></el-aside><el-container><!--  頭部區域  --><el-header><!-- 展開折疊按鈕 --><i :class="collapseIcon" style="font-size: 26px" @click="handleCollapse"></i><!-- 面包屑 --><el-breadcrumbseparator-class="el-icon-arrow-right"style="margin-left: 20px"><el-breadcrumb-item :to="{ path: '/' }">首頁</el-breadcrumb-item><el-breadcrumb-item :to="{ path: '/user' }">用戶管理</el-breadcrumb-item></el-breadcrumb><!-- 頭像下拉框 --><div style="flex: 1; display: flex; justify-content: flex-end; align-items: center"><!-- 全屏按鈕 --><i class="el-icon-full-screen" @click="handleFull" style="font-size: 25px"></i><!-- 下拉框 --><el-dropdown placement="bottom"><div style="display: flex; align-items: center; cursor: pointer"><img src="@/assets/logo1.png" alt="" style="width: 40px; height: 40px; margin: 0 5px"><span>管理員</span></div><el-dropdown-menu slot="dropdown"><el-dropdown-item>個人信息</el-dropdown-item><el-dropdown-item>修改密碼</el-dropdown-item><el-dropdown-item>退出登錄</el-dropdown-item></el-dropdown-menu></el-dropdown></div></el-header><!--  主體區域  --><el-main><div style="box-shadow: 0 0 10px rgba(0,0,0,.1); padding: 10px 20px; border-radius: 5px; margin-bottom: 10px">早安,騷年,祝你開心每一天!</div><el-card style="width: 500px"><div slot="header" class="clearfix"><span>2024項目管理平臺</span></div><div>2024項目管理平臺正式開始了<div style="margin-top: 20px"><div style="margin: 10px 0"><strong>主題色</strong></div><el-button type="primary">按鈕</el-button><el-button type="success">按鈕</el-button><el-button type="warning">按鈕</el-button><el-button type="danger">按鈕</el-button><el-button type="info">按鈕</el-button></div></div></el-card></el-main></el-container></el-container></div>
</template><script>
export default {name: "HomeView",data() {return {isCollapse: false, // 不收縮asideWidth: "200px",collapseIcon: "el-icon-s-fold"};},methods: {handleFull() {document.documentElement.requestFullscreen();},handleCollapse() {this.isCollapse = !this.isCollapse;this.asideWidth = this.isCollapse ? "64px" : "200px";this.collapseIcon = this.isCollapse ? "el-icon-s-unfold" : "el-icon-s-fold";}}
};
</script><style>
.el-menu--inline {background-color: #000c17 !important;
}
.el-menu--inline .el-menu-item {background-color: #000c17 !important;padding-left: 49px !important;
}
.el-menu-item:hover,
.el-submenu__title:hover {color: #fff !important;
}
.el-submenu__title:hover i {color: #fff !important;
}
.el-menu-item:hover i {color: #fff !important;
}
.el-menu-item.is-active {background-color: #1890ff !important;border-radius: 5px !important;width: calc(100% - 8px);margin-left: 4px;
}
.el-menu-item.is-active i,
.el-menu-item.is-active .el-tooltip {margin-left: -4px;
}
.el-menu-item {height: 40px !important;line-height: 40px !important;
}
.el-submenu__title {height: 40px !important;line-height: 40px !important;
}
.el-submenu .el-menu-item {min-width: 0 !important;
}
.el-menu--inline .el-menu-item.is-active {padding-left: 45px !important;
}
/*.el-submenu__icon-arrow {*/
/*  margin-top: -5px;*/
/*}*/.el-aside {transition: width 0.3s;box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
}
.logo-title {margin-left: 5px;font-size: 20px;transition: all 0.3s; /* 0.3s */
}
.el-header {box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);display: flex;align-items: center;
}
</style>

在這里插入圖片描述

4.3 封裝前后端交互工具 axios

4.3.1 安裝 axios

npm i axios -S

4.3.2 /src/utils/目錄下建立一個request.js

import axios from 'axios'// 創建一個新的 axios 對象
const request = axios.create({baseURL: 'http://localhost:9090',  // 請求后端地址timeout: 30000    // 超時時間
})// request 攔截器
// 可以自請求發送前對請求做一些處理
// 比如統一加token,對請求參數統一加密
request.interceptors.request.use(config => {config.headers['Content-Type'] = 'application/json;charset=utf-8';// let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null// config.headers['token'] = 'token'  // 設置請求頭return config;
}, error => {console.error('request error: ' + error); // for debugreturn Promise.reject(error);
});// response 攔截器
// 可以在接口響應后統一處理結果
request.interceptors.response.use(response => {let res = response.data;// 兼容服務端返回的字符串數據if (typeof res === 'string') {res = res ? JSON.parse(res) : res}return res;},error => {console.error('response error: ' + error) // for debugreturn Promise.reject(error)}
)export default request

4.3.3 main.js 全局聲明

import request from '@/utils/request'Vue.prototype.$request = request;

4.3.4 跨域訪問

前端調用:

this.$request.get("selectAll").then((res) => {this.tableData = res.data;
});this.$request.get("selectByPage", {params: { pageNum: 0, pageSize: 10, username: "gai", name: "蓋" }
}).then((res) => {this.tableData = res.data;
});

?? 報錯信息:
在這里插入圖片描述
在這里插入圖片描述

這就是常見的 跨域訪問 問題。關于 跨域訪問 參見 📖 關于跨域和端口問題 。本例報錯原因是前端地址是 http://localhost:7000/ ,訪問后端地址 http://localhost:9090/,端口不一致,導致 跨域訪問 報錯。

📌 解決方法:
UserController 上加個注解 @CrossOrigin

@CrossOrigin
@RestController
public class UserController {
}

在這里插入圖片描述

響應頭可見,Access-Control-Allow-Origin: *,后端默認接收所以地址的請求。

4.4 登錄、注冊

4.4.1 整體設計

在這里插入圖片描述

4.4.2 驗證碼組件

conponents/ValidCode.vue

<template><div class="ValidCode disabled-select" style="width: 100%; height: 100%" @click="refreshCode"><span v-for="(item, index) in codeList" :key="index" :style="getStyle(item)">{{item.code}}</span></div>
</template><script>
export default {name: 'ValidCode',data () {return {length: 4,codeList: []}},mounted () {this.createdCode()},methods: {refreshCode () {this.createdCode()},createdCode () {let len = this.length,codeList = [],chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789',charsLen = chars.length// 生成for (let i = 0; i < len; i++) {let rgb = [Math.round(Math.random() * 220), Math.round(Math.random() * 240), Math.round(Math.random() * 200)]codeList.push({code: chars.charAt(Math.floor(Math.random() * charsLen)),color: `rgb(${rgb})`,padding: `${[Math.floor(Math.random() * 10)]}px`,transform: `rotate(${Math.floor(Math.random() * 90) - Math.floor(Math.random() * 90)}deg)`})}// 指向this.codeList = codeList// 將當前數據派發出去this.$emit('update:value', codeList.map(item => item.code).join(''))},getStyle (data) {return `color: ${data.color}; font-size: ${data.fontSize}; padding: ${data.padding}; transform: ${data.transform}`}}
}
</script><style>
.ValidCode{display: flex;justify-content: center;align-items: center;cursor: pointer;
}
.ValidCode span {display: inline-block;font-size: 18px;
}
</style>

通過 this.$emit('update:value', codeList.map(item => item.code).join('')) 對外暴露方法 update:value,將生成驗證碼暴露出去。

4.4.3 登錄 Login.vue

<template><div style="height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #0f9876"><div style="display: flex; background-color: white; width: 50%; border-radius: 5px; overflow: hidden"><!-- 左側圖片區域 --><div style="flex: 1"><img src="@/assets/login.png" alt="" style="width: 100%"></div><!-- 右側表單區域 --><div style="flex: 1; display: flex; align-items: center; justify-content: center"><el-form :model="user" style="width: 80%" :rules="rules" ref="loginRef"><div style="font-size: 20px; font-weight: bold; text-align: center; margin-bottom: 20px">歡迎登錄后臺管理系統</div><el-form-item prop="username"><el-input prefix-icon="el-icon-user" size="medium" placeholder="請輸入賬號" v-model="user.username"></el-input></el-form-item><el-form-item prop="password"><el-input prefix-icon="el-icon-lock" size="medium" placeholder="請輸入密碼" v-model="user.password" show-password></el-input></el-form-item><el-form-item prop="code"><div style="display: flex"><el-input style="flex: 1" prefix-icon="el-icon-circle-check" size="medium" placeholder="請輸入驗證碼"  v-model="user.code"></el-input><div style="flex: 1; height: 36px"><!-- 驗證碼組件回調方法 --><valid-code @update:value="getCode" /></div></div></el-form-item><el-form-item><el-button type="primary" style="width: 100%" @click="login">登 錄</el-button></el-form-item><div style="display: flex"><div style="flex: 1">還沒有賬號?請 <span style="color: #0f9876; cursor: pointer" @click="$router.push('/register')">注冊</span></div><div style="flex: 1; text-align: right"><span style="color: #0f9876; cursor: pointer">忘記密碼</span></div></div></el-form></div></div></div>
</template><script>
import ValidCode from "@/components/ValidCode.vue";export default {name: "Login",// 引入驗證碼組件components: {ValidCode,},data() {// 自定義驗證碼校驗const validateCode = (rule, value, callback) => {if (value === "") {callback(new Error("請輸入驗證碼"));} else if (value.toLowerCase() !== this.componentCode) {callback(new Error("驗證碼錯誤"));} else {callback();}};return {componentCode: "", // 驗證碼組件傳遞過來的 codeuser: {username: "",password: "",code: "", // 表單里用戶輸入的驗證碼 code},rules: {username: [{ required: true, message: "請輸入賬號", trigger: "blur" }],password: [{ required: true, message: "請輸入密碼", trigger: "blur" }],code: [{ validator: validateCode, trigger: "blur" }],},};},methods: {getCode(val) {this.componentCode = val.toLowerCase();},login() {this.$refs["loginRef"].validate((valid) => {if (valid) {// 驗證通過this.$request.post("/login", this.user).then((res) => {if (res.code === "200") {// 登錄成功,跳轉到首頁this.$router.push("/");this.$message.success("登錄成功");localStorage.setItem("honey-user", JSON.stringify(res.data)); // 存儲用戶數據} else {this.$message.error(res.msg);}});}});}}
};
</script>

@update:value="getCode" 接收驗證碼組件傳遞參數。
用戶名、密碼、驗證碼驗證通過后,this.$router.push("/") 跳轉到首頁,localStorage.setItem("honey-user", JSON.stringify(res.data)); 存儲用戶數據到本地存儲,用于后續網頁訪問讀取 token

4.4.4 注冊 Register.vue

<template><div style="height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #669fef"><div style="display: flex; background-color: white; width: 50%; border-radius: 5px; overflow: hidden"><div style="flex: 1"><img src="@/assets/register.png" alt="" style="width: 100%"></div><div style="flex: 1; display: flex; align-items: center; justify-content: center"><el-form :model="user" style="width: 80%" :rules="rules" ref="registerRef"><div style="font-size: 20px; font-weight: bold; text-align: center; margin-bottom: 20px">歡迎注冊后臺管理系統</div><el-form-item prop="username"><el-input prefix-icon="el-icon-user" size="medium" placeholder="請輸入賬號" v-model="user.username"></el-input></el-form-item><el-form-item prop="password"><el-input prefix-icon="el-icon-lock" size="medium" show-password placeholder="請輸入密碼" v-model="user.password"></el-input></el-form-item><el-form-item prop="confirmPass"><el-input prefix-icon="el-icon-lock" size="medium" show-password placeholder="請確認密碼" v-model="user.confirmPass"></el-input></el-form-item><el-form-item><el-button type="info" style="width: 100%" @click="register">注 冊</el-button></el-form-item><div style="display: flex"><div style="flex: 1">已經有賬號了?請 <span style="color: #6e77f2; cursor: pointer" @click="$router.push('/login')">登錄</span></div></div></el-form></div></div></div>
</template><script>
export default {name: "Register",data() {// 驗證碼校驗const validatePassword = (rule, confirmPass, callback) => {if (confirmPass === '') {callback(new Error('請確認密碼'))} else if (confirmPass !== this.user.password) {callback(new Error('兩次輸入的密碼不一致'))} else {callback()}}return {user: {username: '',password: '',confirmPass: ''},rules: {username: [{ required: true, message: '請輸入賬號', trigger: 'blur' },],password: [{ required: true, message: '請輸入密碼', trigger: 'blur' },],confirmPass: [{ validator: validatePassword, trigger: 'blur' }],}}},methods: {register() {this.$refs['registerRef'].validate((valid) => {if (valid) {// 驗證通過this.$request.post('/register', this.user).then(res => {if (res.code === '200') {this.$router.push('/login')this.$message.success('注冊成功')} else {this.$message.error(res.msg)}})}})}}
}
</script>

驗證通過后,this.$router.push('/login') 跳轉到登錄頁。

4.4.5 router/index.js 配置路由

import Vue from 'vue'
import VueRouter from 'vue-router'Vue.use(VueRouter)const routes = [{path: '/',name: 'home',component: () => import('../views/HomeView.vue')},{path: '/register',name: 'register',component: () => import('../views/Register.vue')},{path: '/login',name: 'login',component: () => import('../views/Login.vue')}
]const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes
})export default router

loginregister 組件加入 router

4.4.6 SpringBoot 解決跨域問題 CorsConfig

訪問地址 http://localhost:7000/login
在這里插入圖片描述

輸入用戶名、密碼、驗證碼后,訪問后端地址 http://localhost:9090/login跨域訪問報錯:
在這里插入圖片描述

上一節給出解決方案Controller 類加個注解 @CrossOrigin,但每次新增 Controller 類都需要手工添加注解,比較麻煩。SpringBoot 提供過濾器 CorsFilter 統一處理 跨域訪問 問題。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;@Configuration
public class CorsConfig {// 當前跨域請求最大有效時長。這里默認1天private static final long MAX_AGE = 24 * 60 * 60;@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*"); // 1 設置訪問源地址corsConfiguration.addAllowedHeader("*"); // 2 設置訪問源請求頭corsConfiguration.addAllowedMethod("*"); // 3 設置訪問源請求方法corsConfiguration.setMaxAge(MAX_AGE);source.registerCorsConfiguration("/**", corsConfiguration); // 4 對接口配置跨域設置return new CorsFilter(source);}
}

4.4.7 后端接口

WebController

@RestController
public class WebController {@ResourceUserService userService;@PostMapping("/login")public Result login(@RequestBody User user) {System.out.println(user);if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())) {return Result.error("數據輸入不合法");}user = userService.login(user);return Result.success(user);}@PostMapping("/register")public Result register(@RequestBody User user) {if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())) {return Result.error("數據輸入不合法");}if (user.getUsername().length() > 10 || user.getPassword().length() > 20) {return Result.error("數據輸入不合法");}user = userService.register(user);return Result.success(user);}
}

UserServiceImpl

@Override
public User login(User user) {// 根據用戶名查詢數據庫的用戶信息User dbUser = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, user.getUsername()));if (dbUser == null) {// 拋出一個自定義的異常throw new ServiceException("用戶名或密碼錯誤");}if (!user.getPassword().equals(dbUser.getPassword())) {throw new ServiceException("用戶名或密碼錯誤");}// 生成tokenString token = TokenUtils.createToken(dbUser.getId(), dbUser.getPassword());dbUser.setToken(token);return dbUser;
}@Override
public User register(User user) {User dbUser = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, user.getUsername()));if (dbUser != null) {// 拋出一個自定義的異常throw new ServiceException("用戶名已存在");}user.setName(user.getUsername());userMapper.insert(user);return user;
}

自定義異常 ServiceException

@Getter
public class ServiceException extends RuntimeException {private final String code;public ServiceException(String msg) {super(msg);this.code = "500";}public ServiceException(String code, String msg) {super(msg);this.code = code;}}

GlobalException

@ControllerAdvice
public class GlobalException {@ExceptionHandler(ServiceException.class)@ResponseBodypublic Result serviceException(ServiceException e) {return Result.error(e.getCode(), e.getMessage());}}

4.5 SpringBoot集成JWT token實現權限驗證

4.5.1 pom.xml添加JWT依賴

<!-- JWT -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.3.0</version>
</dependency>

4.5.2 工具類 TokenUtils

@Component
public class TokenUtils {private static UserMapper staticUserMapper;@ResourceUserMapper userMapper;@PostConstructpublic void setUserService() {staticUserMapper = userMapper;}/*** 生成token** @return*/public static String createToken(String userId, String sign) {return JWT.create().withAudience(userId) // 將 user id 保存到 token 里面,作為載荷.withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小時后token過期.sign(Algorithm.HMAC256(sign)); // 以 password 作為 token 的密鑰}/*** 獲取當前登錄的用戶信息** @return user對象*/public static User getCurrentUser() {try {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader("token");if (StrUtil.isNotBlank(token)) {String userId = JWT.decode(token).getAudience().get(0);return staticUserMapper.selectById(Integer.valueOf(userId));}} catch (Exception e) {return null;}return null;}
}

4.5.3 login() 方法增加 token 返回

@Override
public User login(User user) {// 根據用戶名查詢數據庫的用戶信息User dbUser = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, user.getUsername()));if (dbUser == null) {// 拋出一個自定義的異常throw new ServiceException("用戶名或密碼錯誤");}if (!user.getPassword().equals(dbUser.getPassword())) {throw new ServiceException("用戶名或密碼錯誤");}// 生成tokenString token = TokenUtils.createToken(String.valueOf(dbUser.getId()), dbUser.getPassword());dbUser.setToken(token);return dbUser;
}

Login.vue 將返回 token 存儲本地

login() {
this.$refs["loginRef"].validate((valid) => {if (valid) {// 驗證通過this.$request.post("/login", this.user).then((res) => {if (res.code === "200") {// 登錄成功,跳轉到首頁this.$router.push("/");this.$message.success("登錄成功");localStorage.setItem("honey-user", JSON.stringify(res.data.token)); // 存儲 token 到本地} else {this.$message.error(res.msg);}});}
});

在這里插入圖片描述
登錄成功后,本地存儲數據:

在這里插入圖片描述

📌前端接口在每次請求后端數據的時候,都會在請求頭帶上這個 token 作為驗證信息。

📅 request.js:
請求攔截器:對請求頭增加 token

request.interceptors.request.use(config => {config.headers['Content-Type'] = 'application/json;charset=utf-8';// 設置請求頭,增加tokenlet token = JSON.parse(localStorage.getItem("honey-user") || '{}')config.headers['token'] = tokenreturn config;
}

響應攔截器:判斷權限不足,重定向登錄頁面

request.interceptors.response.use(response => {let res = response.data;// 兼容服務端返回的字符串數據if (typeof res === 'string') {res = res ? JSON.parse(res) : res}// 攔截權限不足的請求,重定向登錄頁面,防止直接輸入網址訪問if (res.code === '401') {router.push('/login')}return res;}
)

如果不登錄直接訪問 http://localhost:7000/ ,后臺接口返回錯誤碼 401,會被響應攔截器攔截,重定向到登錄頁面。登錄完成后,本地存儲 token,后續訪問后端請求從本地存儲獲取到 token,才能正常訪問。

4.5.4 自定義注解 AuthAccess

import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthAccess {
}

該注解用于標注權限放行的方法。

@AuthAccess
@PostMapping("/register")
public Result register(@RequestBody User user) {if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())) {return Result.error("數據輸入不合法");}if (user.getUsername().length() > 10 || user.getPassword().length() > 20) {return Result.error("數據輸入不合法");}user = userService.register(user);return Result.success(user);
}

WebController 的方法 register() 標注注解 @AuthAccess,結合下面攔截器 JwtInterceptor 對該注解的處理,register() 方法將被放行。

4.5.5 自定義攔截器 JwtInterceptor

public class JwtInterceptor implements HandlerInterceptor {@Resourceprivate UserMapper userMapper;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String token = request.getHeader("token");if (StringUtils.isBlank(token)) {token = request.getParameter("token");}// 對標注 AuthAccess 注解的方法進行放行if (handler instanceof HandlerMethod) {AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class);if (annotation != null) {return true;}}// 判斷前端上送 token,執行認證if (StringUtils.isBlank(token)) {throw new ServiceException("401", "請登錄");}// 獲取 token 中的 user idString userId;try {userId = JWT.decode(token).getAudience().get(0);} catch (JWTDecodeException j) {throw new ServiceException("401", "請登錄");}// 根據token中的userid查詢數據庫User user = userMapper.selectById(userId);if (user == null) {throw new ServiceException("401", "請登錄");}// 用戶密碼加簽驗證 tokenJWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();try {jwtVerifier.verify(token); // 驗證token} catch (JWTVerificationException e) {throw new ServiceException("401", "請登錄");}return true;}
}

📌 請求頭獲取 token 字段值,進行JWT 認證判斷是否為登錄成功生成的 token ,進而進行權限認證。

💦if (handler instanceof HandlerMethod) 的含義是什么?

1.springmvc 啟動時候,掃描所有 controller 類,解析所有映射方法,將每個映射方法封裝一個對象 HandlerMethod ,該類包含所有請求映射方法信息(映射路徑 / 方法名 / 參數 / 注解 / 返回值),上例中 AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class),就是獲取請求方法是否標注 AuthAccess 注解。
2.springmvc 針對這些請求映射方法信息封裝對象類,使用類似 map 的數據結構進行統一管理 Map<String, HandlerMethod> map
3.頁面發起請求時(/users/currentUser),進入攔截器之后,springmvc 自動解析請求路徑,得到 url(/users/currentUser),獲取url之后,進而獲取 /users/currentUser 路徑對應的映射方法 HandlerMethod 實例
4.調用攔截器 preHandle 方法并將請求對象、響應對象、映射方法對象 handler 一起傳入。
📖 登錄攔截器原理

1.在Spring MVC中,攔截器的preHandlepostHandleafterCompletion方法的第三個參數是一個 Object 類型的 handler 參數。這個 handler 參數實際上就是處理當前請求的處理器。
2.在Spring MVC中,處理器不一定是 HandlerMethod 類型的。例如,當請求的URL對應的是一個靜態資源時,處理器可能是ResourceHttpRequestHandler類型的。
3.因此,如果你的攔截器的代碼只適用于 HandlerMethod 類型的處理器,你需要在代碼中加入 if (handler instanceof HandlerMethod)這樣的判斷,以確保代碼不會在處理其他類型的處理器時出錯。
4.在Spring MVC中,HandlerMethod 是一個特殊的處理器類型,它用于處理由 @RequestMapping 注解(或其變體,如@GetMapping、@PostMapping等)標注的方法。
📖 Springmvc攔截器的時候要加判斷 handler instanceof HandlerMethod

4.5.6 配置攔截器 InterceptorConfig

@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {@Overrideprotected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**")         // 1. 設置攔截路徑.excludePathPatterns("/login"); // 2. 設置放行路徑super.addInterceptors(registry);}@Beanpublic JwtInterceptor jwtInterceptor() {return new JwtInterceptor();}
}

.addPathPatterns("/**"):對所用請求地址進行攔截
.excludePathPatterns("/login"):設置放行路徑,此處對 /login 進行放行,不進行攔截處理,即不校驗 token。如果想對整個路徑放行,可以設置 /login/**,即對 /login 下所有路徑放行。

  • .excludePathPatterns(url) 和 注解 @AuthAccess 結合使用,可以靈活設置放行方法。

4.6 單文件、多文件上傳和下載

4.6.1 文件上傳、下載 Java 代碼

@RestController
@RequestMapping("/file")
public class FileController {@Value("${ip:localhost}")String ip;@Value("${server.port}")String port;private static final String ROOT_PATH =  System.getProperty("user.dir") + File.separator + "files";@PostMapping("/upload")public Result upload(MultipartFile file) throws IOException {// 文件的原始名稱String originalFilename = file.getOriginalFilename();// 獲取文件名稱、后綴名String mainName = FileUtil.mainName(originalFilename);String extName = FileUtil.extName(originalFilename);// 如果當前文件的父級目錄不存在,就創建if (!FileUtil.exist(ROOT_PATH)) {FileUtil.mkdir(ROOT_PATH);}// 如果當前上傳的文件已經存在了,那么重命名一個文件if (FileUtil.exist(ROOT_PATH + File.separator + originalFilename)) {originalFilename = System.currentTimeMillis() + "_" + mainName + "." + extName;}File saveFile = new File(ROOT_PATH + File.separator + originalFilename);// 存儲文件到本地的磁盤里面去file.transferTo(saveFile);String url = "http://" + ip + ":" + port + "/file/download/" + originalFilename;// 返回文件的鏈接,這個鏈接就是文件的下載地址,這個下載地址就是我的后臺提供出來的return Result.success(url);}@AuthAccess@GetMapping("/download/{fileName}")public void download(@PathVariable String fileName, HttpServletResponse response) throws IOException {// 附件下載response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));// 預覽// response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));String filePath = ROOT_PATH  + File.separator + fileName;if (!FileUtil.exist(filePath)) {return;}byte[] bytes = FileUtil.readBytes(filePath);ServletOutputStream outputStream = response.getOutputStream();outputStream.write(bytes);outputStream.flush();outputStream.close();}
}

在這里插入圖片描述

在這里插入圖片描述

📅 響應頭 Content-Dispositionattachment;filename=,文件以附件形式下載;

在這里插入圖片描述

📅Content-Dispositioninline;filename=,圖片和 pdf 可以預覽,其他文件類型還是以附件形式下載。

在這里插入圖片描述

4.6 個人信息修改、修改密碼、重置密碼

📚 ElementUI官網
📖 Element-UI自學實踐

5. 相關學習網站

詳盡的搭建過程可以參考:
📚使用ElementPlus頁面布局搭建
📚[bilibili]VUE項目,VUE項目實戰,vue后臺管理系統,前端面試,前端面試項目

關于跨域和端口問題

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/72181.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72181.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/72181.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

unity學習61:UI布局layout

目錄 1 布局 layout 1.1 先準備測試UI,新增這樣一組 panel 和 image 1.2 新增 vertical layout 1.3 現在移動任意一個image 都會影響其他 1.3.1 對比 如果沒有這個&#xff0c;就會是覆蓋效果了 1.3.2 對比 如果沒有這個&#xff0c;就會是覆蓋效果了 1.4 總結&#xf…

翻譯: 深入分析LLMs like ChatGPT 一

大家好&#xff0c;我想做這個視頻已經有一段時間了。這是一個全面但面向普通觀眾的介紹&#xff0c;介紹像ChatGPT這樣的大型語言模型。我希望通過這個視頻讓大家對這種工具的工作原理有一些概念性的理解。 首先&#xff0c;我們來談談你在這個文本框里輸入內容并點擊回車后背…

Ubuntu 下 nginx-1.24.0 源碼分析 - ngx_conf_add_dump

ngx_conf_add_dump 定義在src\core\ngx_conf_file.c static ngx_int_t ngx_conf_add_dump(ngx_conf_t *cf, ngx_str_t *filename) {off_t size;u_char *p;uint32_t hash;ngx_buf_t *buf;ngx_str_node_t *sn;ngx_conf_dump_t *cd;has…

Oracle 導出所有表索引的創建語句

在Oracle數據庫中&#xff0c;導出所有表的索引創建語句通常涉及到使用數據字典視圖來查詢索引的定義&#xff0c;然后生成對應的SQL語句。你可以通過查詢DBA_INDEXES或USER_INDEXES視圖&#xff08;取決于你的權限和需求&#xff09;來獲取這些信息。 使用DBA_INDEXES視圖 如…

快速搭建多語言網站的 FastAdmin 實踐

快速搭建多語言網站的 FastAdmin 實踐 引言 在全球化的背景下&#xff0c;越來越多的網站需要支持多種語言&#xff0c;以便滿足不同用戶的需求。FastAdmin 是一個基于 ThinkPHP 的快速后臺開發框架&#xff0c;提供了豐富的功能和靈活的擴展性&#xff0c;非常適合用于快速搭…

Python 實戰:構建分布式文件存儲系統全解析

Python 實戰&#xff1a;構建分布式文件存儲系統全解析 在當今數據爆炸的時代&#xff0c;分布式文件存儲系統憑借其高可擴展性、高可靠性等優勢&#xff0c;成為了數據存儲領域的熱門選擇。本文將詳細介紹如何使用 Python 構建一個簡單的分布式文件存儲系統。從系統架構設計&…

【綜合項目】api系統——基于Node.js、express、mysql等技術

目錄 0 前言 1 初始化 2 注冊登錄 2.1 注冊 2.1.1 功能&#xff1a;密碼加密&#xff08;2.3.3&#xff09; 2.1.1.1 操作 2.1.1.2 bcryptjs詳解 2.1.2 插入新用戶&#xff08;2.3.4&#xff09; 2.1.3 優化&#xff1a;表單數據驗證&#xff08;2.5&#xff09; …

tableau之標靶圖、甘特圖和瀑布圖

一、標靶圖 概念 標靶圖&#xff08;Bullet Chart&#xff09;是一種用于顯示數據與目標之間關系的可視化圖表&#xff0c;常用于業務和管理報告中。其設計旨在用來比較實際值與目標值&#xff0c;同時展示額外的上下文信息&#xff08;如趨勢&#xff09;。 作用 可視化目標…

Linux下的網絡通信編程

在不同主機之間&#xff0c;進行進程間的通信。 1解決主機之間硬件的互通 2.解決主機之間軟件的互通. 3.IP地址&#xff1a;來區分不同的主機&#xff08;軟件地址&#xff09; 4.MAC地址&#xff1a;硬件地址 5.端口號&#xff1a;區分同一主機上的不同應用進程 網絡協議…

網絡七層模型—OSI參考模型詳解

網絡七層模型&#xff1a;OSI參考模型詳解 引言 在網絡通信的世界中&#xff0c;OSI&#xff08;Open Systems Interconnection&#xff09;參考模型是一個基礎且核心的概念。它由國際標準化組織&#xff08;ISO&#xff09;于1984年提出&#xff0c;旨在為不同廠商的設備和應…

530 Login fail. A secure connection is requiered(such as ssl)-java發送QQ郵箱(簡單配置)

由于cs的csdN許多文章關于這方面的都是vip文章&#xff0c;而本文是免費的&#xff0c;希望廣大網友覺得有幫助的可以多點贊和關注&#xff01; QQ郵箱授權碼到這里去開啟 授權碼是16位的字母&#xff0c;填入下面的mail.setting里面的pass里面 # 郵件服務器的SMTP地址 host…

Sqlserver安全篇之_TLS的證書概念

證書的理解 參考Sqlserver的官方文檔https://learn.microsoft.com/zh-cn/sql/database-engine/configure-windows/certificate-overview?viewsql-server-ver16 TLS(Transport Layer Security)傳輸層安全和SSL(Secure Sockets Layer)安全套接字層協議位于應用程序協議層和TCP/…

【SQL】掌握SQL查詢技巧:數據分組與排序

目錄 1. GROUP BY 1.1 定義與用途1.2 示例說明1.3 注意事項1.4 可視化示例 2. ORDER BY 2.1 定義與用途2.2 升序說明&#xff08;默認&#xff09;2.3 降序排序2.4 多列排序2.5 可視化示例 3. GROUP BY 與 ORDER BY 的結合使用4. 可視化示例總結 在數據庫管理中&#xff0c;S…

SOME/IP-SD -- 協議英文原文講解6

前言 SOME/IP協議越來越多的用于汽車電子行業中&#xff0c;關于協議詳細完全的中文資料卻沒有&#xff0c;所以我將結合工作經驗并對照英文原版協議做一系列的文章。基本分三大塊&#xff1a; 1. SOME/IP協議講解 2. SOME/IP-SD協議講解 3. python/C舉例調試講解 5.1.3.1 E…

NameError: name ‘libpaddle‘ is not defined

問題場景&#xff1a; Error: Can not import paddle core while this file exists: C:\Users\Admin\AppData\Roaming\Python\Python38\site-packages\paddle\fluid\libpaddle.pyd Traceback (most recent call last): File "C:\Users\Admin\AppData\Roaming\Python\Pyth…

青少年編程與數學 02-010 C++程序設計基礎 11課題、程序結構

青少年編程與數學 02-010 C程序設計基礎 11課題、程序結構 一、C程序結構二、main函數1. main 函數的基本形式1.1 無參數形式1.2 帶參數形式 2. 參數解釋3. 示例3.1 無參數形式3.2 帶參數形式 4. 編譯和運行4.1 編譯4.2 運行 5. main 函數的返回值6. 總結 三、預處理指令1. #in…

【Linux】learning notes(3)make、copy、move、remove

文章目錄 1、mkdir &#xff08;make directory&#xff09;2、rmdir &#xff08;remove directory&#xff09;3、rm&#xff08;remove&#xff09;4、>5、touch 新建文件6、mv&#xff08;move&#xff09;7、cp&#xff08;copy&#xff09; 1、mkdir &#xff08;make…

智能AI替代專家系統(ES)、決策支持系統(DSS)?

文章目錄 前言一、專家系統&#xff08;ES&#xff09;是什么&#xff1f;二、決策支持系統&#xff08;DSS&#xff09;是什么&#xff1f;1.決策支持系統定義2.決策系統的功能與特點3.決策支持系統的組成 三、專家系統&#xff08;ES&#xff09;與決策支持系統&#xff08;D…

實現Python+Django+Transformers庫中的BertTokenizer和BertModel來進行BERT預訓練,并將其應用于商品推薦功能

一、環境安裝準備 #git拉取 bert-base-chinese 文件#創建 虛擬運行環境python -m venv myicrplatenv#刷新source myicrplatenv/bin/activate#python Django 集成nacospip install nacos-sdk-python#安裝 Djangopip3 install Django5.1#安裝 pymysql settings.py 里面需要 # 強制…

Qt Creator + CMake 構建教程

此教程基于: Qt 6.7.4Qt Creator 15.0.1CMake 3.26.4 Qt 6 以下的版本使用 CMake 構建可能會存在一些問題. 目錄 新建窗體工程更新翻譯添加資源軟件部署(Deploy) 此教程描述了如何一步步在 Qt Creator 中使用 CMake 構建應用程序工程. 涉及 新建窗體工程, 更新翻譯, 添加資源, …