入门基础项目(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 给 bb = 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 Family:element-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
将 login
、register
组件加入 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中,拦截器的
preHandle
、postHandle
、afterCompletion
方法的第三个参数是一个 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-Disposition
为 attachment;filename=
,文件以附件形式下载;
📅Content-Disposition
为 inline;filename=
,图片和 pdf 可以预览,其他文件类型还是以附件形式下载。
4.6 个人信息修改、修改密码、重置密码
📚 ElementUI官网
📖 Element-UI自学实践
5. 相关学习网站
详尽的搭建过程可以参考:
📚使用ElementPlus页面布局搭建
📚[bilibili]VUE项目,VUE项目实战,vue后台管理系统,前端面试,前端面试项目
关于跨域和端口问题
相关文章:
入门基础项目(SpringBoot+Vue)
文章目录 1. css布局相关2. JS3. Vue 脚手架搭建4. ElementUI4.1 引入ElementUI4.2 首页4.2.1 整体框架4.2.2 Aside-logo4.2.3 Aside-菜单4.2.4 Header-左侧4.2.5 Header-右侧4.2.6 iconfont 自定义图标4.2.7 完整代码 4.3 封装前后端交互工具 axios4.3.1 安装 axios4.3.2 /src…...
C#调用CANoeCLRAdapter.dll文章(二)
一、引言 在上一篇指南中,我们介绍了如何通过C#调用CANoeCLRAdapter.dll实现基础功能,包括COM接口操作、DLL导入和PANL面板集成。本文将进一步探讨高级功能开发,涵盖事件驱动编程、CAPL脚本双向通信以及异步任务处理,帮助开发者构…...
ai大模型自动化测试-TensorFlow Testing 测试模型实例
AI大模型自动化测试是确保模型质量、可靠性和性能的关键环节,以下将从测试流程、测试内容、测试工具及测试挑战与应对几个方面进行详细介绍: 测试流程 测试计划制定 确定测试目标:明确要测试的AI大模型的具体功能、性能、安全性等方面的目标,例如评估模型在特定任务上的准…...
QT——c++界面编程库
非界面编程 QT编译的时候,依赖于 .pro 配置文件: SOURCES: 所有需要参与编译的 .cpp 源文件 HEADERS:所有需要参与编译的.h 头文件 QT:所有需要参与编译的 QT函数库 .pro文件一旦修改,注意需要键盘按 ctrls 才能加载最新的配置文…...
postman--接口测试工具安装和使用教程
postman–接口测试工具 postman是一款支持http协议的接口调试与测试工具,其主要特点就是功能强大,使用简单且易用性好 。 无论是开发人员进行接口调试,还是测试人员做接口测试,postman都是我们的首选工具之一 。 下面先通过一张…...
如何用python画一棵分形树
这个代码会生成一个彩色的分形树图案,可以通过调整draw_tree函数中的参数来改变树的形状和大小 import turtle import random# 递归函数绘制分形树 def draw_tree(branch_len, t):if branch_len > 5:t.color(random.choice(colors))t.pensize(branch_len / 10)t…...
【leetcode】二分查找专题
文章目录 1.二分查找1.题目2.解题思路3. 解题代码 2.在排序数组中查找元素的第一个和最后一个位置1.题目2.算法原理3. 代码 3.x的平方根1.题目2.代码 4.搜索插入位置1.题目2.解题思路3.解题代码 5.山脉数组的索引1.题目2.解题思路3. 代码 6.寻找峰值1.题目2.解题思路3.代码 7. …...
深度学习笔记17-马铃薯病害识别(VGG-16复现)
目录 一、 前期准备 1. 设置GPU 2. 导入数据 二、手动搭建VGG-16模型 1. 搭建模型 三、 训练模型 1. 编写训练函数 3. 编写测试函数 4. 正式训练 四、 结果可视化 1. Loss与Accuracy图 2. 指定图片进行预测 3. 模型评估 前言 🍨 本文为🔗365天深度学习训…...
【LeetCode】131.分割回文串
目录 题目描述输入输出示例及数据范围思路C 实现 题目描述 这道题目来自 LeetCode 131. 分割回文串。 题目描述如下: 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 输入输出示例及数据…...
【AIGC系列】5:视频生成模型数据处理和预训练流程介绍(Sora、MovieGen、HunyuanVideo)
AIGC系列博文: 【AIGC系列】1:自编码器(AutoEncoder, AE) 【AIGC系列】2:DALLE 2模型介绍(内含扩散模型介绍) 【AIGC系列】3:Stable Diffusion模型原理介绍 【AIGC系列】4࿱…...
基于C++“简单且有效”的“数据库连接池”
前言 数据库连接池在开发中应该是很常用的一个组件,他可以很好的节省连接数据库的时间开销;本文基使用C实现了一个简单的数据库连接池,代码量只有400行左右,但是压力测试效果很好;欢迎收藏 关注,本人将会…...
vulnhub靶场【kioptrix-4】靶机
前言 靶机:kioptrix-4,IP地址为192.168.1.75,后期IP地址为192.168.10.8 攻击:kali,IP地址为192.168.1.16,后期IP地址为192.168.10.6 都采用VMware虚拟机,网卡为桥接模式 这里的靶机…...
科普:ROC AUC与PR AUC
在评价二分类模型性能时,有许多评价指标,其中,有一对是用面积AUC(Area Under the Curve)做评价的:ROC AUC与PR AUC 本文我们对ROC AUC与PR AUC进行多维度对比分析: 一、定义与核心原理 维度RO…...
服务器IPMI用户名、密码批量检查
背景 大规模服务器部署的时候,少不了较多的网管和监测平台,这些平台会去监控服务器的性能、硬件等指标参数,为了便于管理和控制,则需要给服务器IPMI带外管理添加较多的用户,这就需要对较多的服务器检查所对应的IPMI用…...
51单片机中reg52.h与regx52.h在进行位操作时的不同
reg52.h中不能使用例如 P2_0;这样的定义 而只能使用 P2^0;这样的定义 但是都不可以对位进行直接赋值操作; 而 regx52.h中可以使用 P2_0和P2^0;但是只有使用下划线的才可以对位进行赋值操作 例如P2_0 1; 但不可以是P2^0 1; 在 C 语言中,…...
Apollo Cyber 学习笔记
目录 0 Introduction What Why Advantage 1 Example 2 Concept 3 Flow Chart 4 Module 4.1 Transport 4.1.1 Share Memory 4.1.1.1 Segment 4.1.1.1.1 State 4.1.1.1.2 Block 4.1.1.1.3 Common 4.1.1.2 Notifier 4.1.1.2.1 ConditionNotifier 4.1.1.2.2 Multi…...
【Electron入门】进程环境和隔离
目录 一、主进程和渲染进程 1、主进程(main) 2、渲染进程(renderer) 二、预加载脚本 三、沙盒化 为单个进程禁用沙盒 全局启用沙盒 四、环境访问权限控制:contextIsolation和nodeIntegration 1、contextIsola…...
拉链表介绍
拉链表 是处理 缓慢变化维(SCD) 的一种常用方法,特别适用于需要保留历史记录的场景。以下是拉链表的详细说明及实现方法: 1. 什么是拉链表? 拉链表是一种用于记录维度数据历史变化的表结构,通过 开始时间 …...
Spring Boot 日志配置与常见问题解析(详解)
目录 Spring Boot 日志配置与常见问题解析引言什么是日志?日志的重要性日志使用打印日志 日志框架介绍日志格式的说明⽇志级别日志级别的分类日志级别的使用 Spring Boot 日志配置1. 设置日志级别和格式2. 配置日志收集器3. 查看和分析日志4.日志的持久化5.设置日志…...
-bash: lsof: command not found
一、问题说明 执行如下命令时报错: # lsof |grep deleted > deleted_file -bash: lsof: command not found二、处理方法 # yum -y install lsof安装完成后可成功执行上面的命令。...
PC 端连接安卓手机恢复各类数据:安装、操作步骤与实用指南
软件介绍 这款用于恢复安卓手机数据的软件,虽运行在 PC 端,却专为安卓手机数据恢复打造,使用时得用数据线把手机和电脑连接起来。它的功能相当强大,能帮你找回安卓手机里已删除的短信、联系人、通话记录、文档,还有照…...
ES、OAS、ERP、电子政务、企业信息化(高软35)
系列文章目录 ES、OAS、ERP、电子政务、企业信息化 文章目录 系列文章目录前言一、专家系统(ES)二、办公自动化系统(OAS)三、企业资源规划(ERP)四、典型信息系统架构模型1.政府信息化和电子政务2.企业信息…...
android智能指针android::sp使用介绍
android::sp 是 Android 中的智能指针(Smart Pointer)的实现,用于管理对象的生命周期,避免手动管理内存泄漏等问题。它是 Android libutils 库中重要的一部分,常用于管理继承自 android::RefBase 的对象。 与标准库中…...
推荐一款最新开源,基于AI人工智能UI自动化测试工具!支持自然语言编写脚本!
随着互联网技术的飞速发展,Web应用越来越普及,前端页面也越来越复杂。为了确保产品质量,UI自动化测试成为了开发过程中不可或缺的一环。然而,传统的UI自动化测试工具往往存在学习成本高、维护困难等问题。特别是UI 自动化脚本里往…...
DeepSeek05-大模型WebUI
一、说明: 将DeepSeek部署到前台Web界面的方法主要有以下几种推荐方案,涵盖开源工具、第三方客户端及特定场景适配方案: Open WebUIChatbox AICherry StudioSillyTavern 二、Open WebUI 安装配置教程 特点:Open WebUI 是一个开…...
自然语言处理NLP入门 -- 第八节OpenAI GPT 在 NLP 任务中的应用
在前面的学习中,我们已经了解了如何使用一些经典的方法和模型来处理自然语言任务,如文本分类、命名实体识别等。但当我们需要更强的语言生成能力时,往往会求助于更先进的预训练语言模型。OpenAI 旗下的 GPT 系列模型(如 GPT-3、GP…...
FFmpeg av_read_frame 和iOS系统提供的 AVAudioRecorder 实现音频录制的区别
1. 第一种方式:使用 FFmpeg 的 av_read_frame 特点 底层实现:基于 FFmpeg,这是一个强大的多媒体处理库,直接操作音频流。灵活性:非常灵活,可以处理多种音频格式、编解码器和输入设备。复杂性:需要手动管理音频流、数据包(AVPacket)、内存释放等,代码复杂度较高。跨平…...
【区块链】深入理解区块链中的 Gas 机制
🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 💫个人格言: "如无必要,勿增实体" 文章目录 深入理解区块链中的 Gas 机制一、Gas 的基本概念1.1 为什么需要 Gas?…...
2020 年英语(一)考研真题 笔记(更新中)
Section I Use of English(完型填空) 原题 Directions:Read the following text. Choose the best word (s) for each numbered blank and mark A, B, C or D on the ANSWER SHEET. (10 points) Even if families are less likely to si…...
mamba_ssm和causal-conv1d详细安装教程
1.前言 Mamba是近年来在深度学习领域出现的一种新型结构,特别是在处理长序列数据方面表现优异。在本文中,我将介绍如何在 Linux 系统上安装并配置 mamba_ssm 虚拟环境。由于官方指定mamba_ssm适用于 PyTorch 版本高于 1.12 且 CUDA 版本大于 11.6 的环境…...
leetcode-442.数组中重复的数据
leetcode-442.数组中重复的数据 文章目录 leetcode-442.数组中重复的数据1.题目描述:数组中重复的数据2.第一次代码提交:(不符合仅使用常量额外空间)3.最终代码提交:只使用常数额外空间、时间复杂度为 O(n) 的做法,即“标记法” 1…...
UniApp 按钮组件 open-type 属性详解:功能、场景与平台差异
文章目录 引言一、open-type 基础概念1.1 核心作用1.2 通用使用模板 二、主流 open-type 值详解2.1 contact - 客服会话功能说明平台支持代码示例 2.2 share - 内容转发功能说明平台支持注意事项 2.3 getUserInfo - 获取用户信息功能说明平台支持代码示例 2.4 getPhoneNumber -…...
Android13修改多媒体默认音量
干就完了! 设置音量为最大音量,修改如下: /framework/base/media/java/android/media/AudioSystem.java/** hide */public static int[] DEFAULT_STREAM_VOLUME new int[] {4, // STREAM_VOICE_CALL7, // STREAM_SYSTEM5, // STREAM_RING-5, // STREAM_MUSIC15, // STREAM…...
【银河麒麟高级服务器操作系统】服务器测试业务耗时问题分析及处理全流程分享
更多银河麒麟操作系统产品及技术讨论,欢迎加入银河麒麟操作系统官方论坛 https://forum.kylinos.cn 了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer…...
HTTP 请求时传递多部分表单数据
HTTP 请求时传递多部分表单数据(multipart/form-data) --data-raw $------demo11111\r\nContent-Disposition: form-data; name"Filedata"; filename"截屏2025-02-27 15.45.46.png"\r\nContent-Type: image/png\r\n\r\n\r\n------d…...
【Python环境】配置极简描述
241220 241220 241220 Python环境配置 下载Python 稳定版本:Stable Releases【3.12.3】 下载地址:Python Releases for Windows | Python.org. 配环境 注意勾上Add Python 3.x to PATH,然后点“Install Now”即可完成安装。 配置完成&…...
1. HTTP 数据请求
相关资源: 图片素材📎图片素材.zip 接口文档 1. HTTP 数据请求 什么是HTTP数据请求: (鸿蒙)应用软件可以通过(鸿蒙)系统内置的 http 模块 和 Axios,通过 HTTP 协议和服务器进行通讯 学习核心Http请求技术: Http模块 - 属于鸿…...
基于深度学习+NLP豆瓣电影数据爬虫可视化推荐系统
博主介绍:资深开发工程师,从事互联网行业多年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的设计程序开发,开发过上千套设计程序,没有什么华丽的语言,只有…...
Apache Spark中的依赖关系与任务调度机制解析
Apache Spark中的依赖关系与任务调度机制解析 在Spark的分布式计算框架中,RDD(弹性分布式数据集)的依赖关系是理解任务调度、性能优化及容错机制的关键。宽依赖(Wide Dependency)与窄依赖(Narrow Dependency)作为两种核心依赖类型,直接影响Stage划分、Shuffle操作及容…...
SEO炼金术(4)| Next.js SEO 全攻略
在上一篇文章 SEO炼金术(3)| 深入解析 SEO 关键要素 中,我们深入解析了 SEO 关键要素,包括 meta 标签、robots.txt、canonical、sitemap.xml 和 hreflang,并探讨了它们在搜索引擎优化(SEO)中的作…...
DeepSeek开源周,第五弹再次来袭,3FS
Fire-Flyer 文件系统(3FS)总结: 一、核心特点 3FS 是一个专为 AI 训练和推理工作负载设计的高性能分布式文件系统,利用现代 SSD 和 RDMA 网络,提供共享存储层,简化分布式应用开发。其主要特点包括…...
conda怎么迁移之前下载的环境包,把python从3.9升级到3.10
克隆旧环境(保留旧环境作为备份) conda create -n cloned_env --clone old_env 在克隆环境中直接升级 Python conda activate cloned_env conda install python3.10 升级 Python 后出现 所有包导入失败 的问题,通常是因为依赖包与新 Pyth…...
一周一个Unity小游戏2D反弹球游戏 - 移动的弹板(鼠标版)
前言 本文将实现控制弹板移动,通过Unity的New Input System,可以支持鼠标移动弹板跟随移动,触控点击跟随移动,并且当弹板移动到边界时,弹板不会移动超过边界之外。 创建移动相关的InputAction 项目模版创建的时候默认会…...
wordpress子分类调用父分类名称和链接的3种方法
专为导航而生,在wordpress模板制作过程中常常会在做breadcrumbs导航时会用到,子分类调用父分类的名称和链接,下面这段简洁的代码,可以完美解决这个问题。 <?php echo get_category_parents( $cat, true, » ); ?…...
使用mermaid查看cursor程序生成的流程图
一、得到cursor生成的流程图文本 cursor写的程序正常运行后,在对话框输入框中输入诸如“请生成扫雷的代码流程图”,然后cursor就把流程图给生成了,但是看到的还是文本的样子,保留这部分内容待用 二、注册一个Mermaid绘图账号 …...
GC垃圾回收介绍及GC算法详解
目录 引言 GC的作用域 什么是垃圾回收? 常见的GC算法 1.引用计数法 2.复制算法 3.标记清除 4.标记整理 小总结 5.分代收集算法 ps:可达性分析算法? 可达性分析的作用 可达性分析与垃圾回收算法的关系 结论 引言 在编程世界中,…...
设计后端返回给前端的返回体
目录 1、为什么要设计返回体? 2、返回体包含哪些内容(如何设计)? 举例 3、总结 1、为什么要设计返回体? 在设计后端返回给前端的返回体时,通常需要遵循一定的规范,以确保前后端交互的清晰性…...
Pytorch为什么 nn.CrossEntropyLoss = LogSoftmax + nn.NLLLoss?
为什么 nn.CrossEntropyLoss LogSoftmax nn.NLLLoss? 在使用 PyTorch 时,我们经常听说 nn.CrossEntropyLoss 是 LogSoftmax 和 nn.NLLLoss 的组合。这句话听起来简单,但背后到底是怎么回事?为什么这两个分开的功能加起来就等于…...
Linux实操——在服务器上直接从百度网盘下载(/上传)文件
Linux Linux实操——在服务器上直接从百度网盘下载(/上传)文件 文章目录 Linux前言一、下载并安装bypy工具二、认证并授权网盘账号三、将所需文件转移至目的文件夹下四、下载文件五、上传文件六、更换绑定的百度云盘账户 前言 最近收到一批很大的数据&…...
【无标题】ABP更换MySql数据库
原因:ABP默认使用的数据库是sqlServer,本地没有安装sqlServer,安装的是mysql,需要更换数据库 ABP版本:9.0 此处以官网TodoApp项目为例 打开EntityFrameworkCore程序集,可以看到默认使用的是sqlServer&…...