配置MySQL与登录模块

 使用技术

MySQL,Mybatis-plus,spring-security,jwt验证,vue   

1. 配置Mysql

1.1 下载

MySQL :: Download MySQL Installer
1.2 安装

     
其他页面全选默认即可

1.3 配置环境变量
将C:\Program Files\MySQL\MySQL Server 8.0\bin(如果安装到了其他目录,填写相应目录的地址即可)添加到环境变量PATH中,这样就可以在任意目录的终端中执行mysql命令了。

1.4 mysql服务的关闭与启动(默认开机自动启动,如果想手动操作,可以参考如下命令)

关闭:net stop mysql80
启动:net start mysql80
1.5 mysql的常用操作连接用户名为root,密码为123456的数据库服务:mysql -uroot -p123456

show databases;:列出所有数据库
create database kob;:创建数据库
drop database kob;:删除数据库
use kob;:使用数据库kob
show tables;:列出当前数据库的所有表
create table user(id int, username varchar(100)):创建名称为user的表,表中包含id和username两个属性。
drop table user;:删除表
insert into user values(1, 'yxc');:在表中插入数据
select * from user;:查询表中所有数据
delete from user where id = 2;:删除某行数据

2. 配置SpringBoot

Maven仓库地址:https://mvnrepository.com/

MyBatis-Plus官网:MyBatis-Plus
在pom.xml文件中添加依赖:
Spring Boot Starter JDBC
Project Lombok
MySQL Connector/J
mybatis-plus-boot-starter
mybatis-plus-generator
spring-boot-starter-security
jjwt-api
jjwt-impl
jjwt-jackson
在application.properties中添加数据库配置:

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver


SpringBoot中的常用模块

pojo层:将数据库中的表对应成Java中的Class
mapper层(也叫Dao层):将pojo层的class中的操作,映射成sql语句
service层:写具体的业务逻辑,组合使用mapper中的操作
controller层:负责请求转发,接受页面过来的参数,传给Service处理,接到返回值,再传给页面

3. 修改Spring Security

传统方式:session登录认证

实现security与数据库对接 

实现service.impl.UserDetailsServiceImpl类,继承自UserDetailsService接口,用来接入数据库信息
实现config.SecurityConfig类,用来实现用户密码的加密存储

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

jwt可实现跨域认证,不需要在服务器端存储

加入jwt的三个依赖:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.12.5</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.12.5</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.12.5</version><scope>runtime</scope>
</dependency>

实现utils.JwtUtil类

为jwt工具类(实现加密信息,解析token),用来创建、解析jwt token

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;@Component
public class JwtUtil {public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";public static String getUUID() {return UUID.randomUUID().toString().replaceAll("-", "");}public static String createJWT(String subject) {JwtBuilder builder = getJwtBuilder(subject, null, getUUID());return builder.compact();}private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;SecretKey secretKey = generalKey();long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if (ttlMillis == null) {ttlMillis = JwtUtil.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);return Jwts.builder().setId(uuid).setSubject(subject).setIssuer("sg").setIssuedAt(now).signWith(signatureAlgorithm, secretKey).setExpiration(expDate);}public static SecretKey generalKey() {byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");}public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(jwt).getBody();}
}

实现config.filter.JwtAuthenticationTokenFilter类

用来验证jwt token,如果验证成功,则将User信息注入上下文中

import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate UserMapper userMapper;@Overrideprotected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("Authorization");if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {filterChain.doFilter(request, response);return;}token = token.substring(7);String userid;try {Claims claims = JwtUtil.parseJWT(token);userid = claims.getSubject();} catch (Exception e) {throw new RuntimeException(e);}User user = userMapper.selectById(Integer.parseInt(userid));if (user == null) {throw new RuntimeException("用户名未登录");}UserDetailsImpl loginUser = new UserDetailsImpl(user);UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request, response);}
}

配置config.SecurityConfig类

package com.kob.backend.config;import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/user/account/token/", "/user/account/register/").permitAll().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}
}

放行登录、注册等接口

4. 编写API

将数据库中的id域变为自增
在数据库中将id列变为自增
在pojo.User类中添加注解:@TableId(type = IdType.AUTO)
实现/user/account/token/:验证用户名密码,验证成功后返回jwt token(令牌)
实现/user/account/info/:根据令牌返回用户信息
实现/user/account/register/:注册账号

5.前端登录与注册

创建页面:
在 views 目录下创建 user ,新建 UserAccountLoginView.vue 和 UserAccountRegisterView.vue

UserAccountLoginView.vue

<template><ContentField><!-- div.row>div.col-3 --><div class="row justify-content-center"><div class="col-3"><!-- 绑定默认函数,提交触发login函数,并阻止掉默认行为 --><form @submit.prevent="login"><div class="mb-3"><label for="username" class="form-label">用户名</label><!-- 绑定username用 v-modle --><input v-model="username" type="text" class="form-control" id="username" aria-describedby="请输入用户名"></div><div class="mb-3"><label for="password" class="form-label">密码</label><input v-model="password" type="password" class="form-control" id="password"aria-describedby="请输入密码"></div><div class="error-message"><!-- 密码错误  -->{{ error_message }}</div><button type="submit" class="btn btn-primary">提交</button></form></div></div></ContentField>
</template><script>
import ContentField from "../../../components/ContentField.vue"
import { useStore } from "vuex";
import { ref } from 'vue';
import router from "../../../router/index"
export default {comments: {ContentField},setup() {const store = useStore();let username = ref('');let password = ref('');let error_message = ref('');const login = () => {//清空 error_messageerror_message.value = "";store.dispatch("login", {username: username.value,password: password.value,success() {// console.log(resp);//成功后调用回调函数,先获取用户信息,getinfo为store.user下自定义函数名store.dispatch("getinfo", {success() {//登录成功,跳转主页面router.push({ name: 'home' });console.log(store.state.user);}})},error() {error_message.value = "用户名或密码有错误";}})}return {username,password,error_message,login,}}
}
</script><style scoped>
button {width: 100%;
}div.error-message {color: red;
}
</style>

UserAccountRegisterView.vue

<template><ContentField>注册</ContentField>
</template><script>
import ContentField from "../../../components/ContentField.vue"
export default {comments: {ContentField}
}
</script><style scoped></style>

修改store--->user.js


import $ from "jquery"export default ({state: {id: "",username: "",photo: "",token: "",is_login: false,},getters: {},//修改数据mutations: {updateUser(state, user) {state.id = user.id;state.username = user.username;state.photo = user.photo;state.is_login = user.is_login;},updateToken(state, token) {state.token = token;},//退出登录只需在前端删除token,退出登录的辅助函数logout(state) {state.id = "";state.username = "";state.photo = "";state.token = "";state.is_login = false;}},// 修改state的辅助函数一般写在actionsactions: {login(context, data) {$.ajax({url: "http://127.0.0.1:8080/user/account/token/",type: "post",data: {username: data.username,password: data.password,},//获取tokensuccess(resp) {//在LoginServiceImpl中定义的error_message,tokenif (resp.error_message === "success") {//actions调用mutations中的函数需要用commit+字符串context.commit("updateToken", resp.token);data.success(resp);} else {data.error(resp);}},error(resp) {data.error(resp);}});},getinfo(context, data) {$.ajax({url: "http://127.0.0.1:8080/user/account/info/",type: "get",headers: {Authorization: "Bearer " + context.state.token,},success(resp) {if (resp.error_message === "success") {context.commit("updateUser", {...resp,is_login: true,});data.success(resp);} else {data.error(resp);}},error(resp) {data.error(resp);}});},logout(context) {//logout(3)调用 logout(4)context.commit("logout");}},modules: {}
})

修改rount.js 


import { createRouter, createWebHistory } from 'vue-router'
import NotFound from "../views/error/NotFound"
import PkIndexView from "../views/pk/PkIndexView"
import RanklistIndexView from "../views/ranklist/RanklistIndexView"
import RecordIndexView from "../views/record/RecordIndexView"
import UserBotIndexView from "../views/user/bot/UserBotIndexView"
import UserAccountLoginView from "../views/user/account/UserAccountLoginView"
import UserAccountRegisterView from "../views/user/account/UserAccountRegisterView"
import store from '../store/index'const routes = [{path: "/",name: "home",redirect: "/pk/",meta: {requestAuth: true}},{path: "/pk/",name: "pk_index",component: PkIndexView,meta: {requestAuth: true,}},{path: "/error/",name: "404",component: NotFound,meta: {requestAuth: false}},{path: "/record/",name: "record_index",component: RecordIndexView,meta: {requestAuth: true}},{path: "/ranklist/",name: "ranklist_index",component: RanklistIndexView,meta: {requestAuth: true}},{path: "/user/bot/",name: "user_bot_index",component: UserBotIndexView,meta: {requestAuth: true}},{path: "/user/account/login/",name: "user_account_login",component: UserAccountLoginView,meta: {requestAuth: false}},{path: "/user/account/register/",name: "user_account_register",component: UserAccountRegisterView,meta: {requestAuth: false}},// 重定向到404{path: "/:catchAll(.*)",redirect: "/error/"}]const router = createRouter({history: createWebHistory(),routes
})//router在起作用之前执行的一个函数
router.beforeEach((to, from, next) => {// 如果发现去的没有授权和登录,重定向到login,否则跳转默认页面if (to.meta.requestAuth && store.state.is_login) {next({ name: "user_account_login" });} else {next();}
})
export default router

 store--->index.js

import { createStore } from 'vuex'
import ModuleUser from './user'export default createStore({state: {},getters: {},mutations: {},actions: {},modules: {user: ModuleUser,}
})

修改components-->NavBar.vue

<!-- html -->
<template><nav class="navbar navbar-expand-lg  navbar-dark bg-dark"><div class="container"><!-- 刷新 --><!-- <a class="navbar-brand" href="/">King Of Bots</a> --><!-- 点击页面不刷新用router-link --><router-link class="navbar-brand" :to="{ name: 'pk_index' }">King Of Bots</router-link><div class="collapse navbar-collapse" id="navbarText"><ul class="navbar-nav me-auto mb-2 mb-lg-0"><li class="nav-item"><!-- active高亮 --><!-- <router-link class="nav-link active " :to="{ name: 'pk_index' }">对战</router-link> --><!-- 选中的高亮 --><router-link :class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'pk_index' }">对战</router-link></li><li class="nav-item"><router-link :class="route_name == 'record_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'record_index' }">对局列表</router-link></li><li class="nav-item"><router-link :class="route_name == 'ranklist_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'ranklist_index' }">排行榜</router-link></li></ul><ul class="navbar-nav" v-if="$store.state.user.is_login"><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"aria-expanded="false">{{ $store.state.user.username }}</a><ul class="dropdown-menu"><li><router-link class="dropdown-item" :to="{ name: 'user_bot_index' }">my bot</router-link></li><!--  @click="logout"点击调用logout(1)函数 --><li><a class="dropdown-item" href="#" @click="logout">exit</a></li></ul></li></ul><ul class="navbar-nav" v-else><li class="nav-item "><router-link class="nav-link " :to="{ name: 'user_account_login' }" role="button">登录</router-link></li><li class="nav-item "><router-link class="nav-link " :to="{ name: 'user_account_register' }" role="button">注册</router-link></li></ul></div></div></nav>
</template><!-- js -->
<script >
// 实现选中的页面高亮
import { useRoute } from 'vue-router';
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {setup() {const store = useStore();const route = useRoute();let route_name = computed(() => route.name)//触发函数logout(1) 调用logout(2)const logout = () => {// 调用user.js中的logout(3)store.dispatch("logout");}return {route_name,logout}}
}
</script><!-- css -->
<!-- scoped 作用:写的css会加上一个随机字符串,使得样式不会影响组件以外的部分 -->
<style scoped></style>

 

 前端页面授权

修改router-->index.js

import { createRouter, createWebHistory } from 'vue-router'
import NotFound from "../views/error/NotFound"
import PkIndexView from "../views/pk/PkIndexView"
import RanklistIndexView from "../views/ranklist/RanklistIndexView"
import RecordIndexView from "../views/record/RecordIndexView"
import UserBotIndexView from "../views/user/bot/UserBotIndexView"
import UserAccountLoginView from "../views/user/account/UserAccountLoginView"
import UserAccountRegisterView from "../views/user/account/UserAccountRegisterView"
import store from '../store/index'const routes = [{path: "/",name: "home",redirect: "/pk/",meta: {requestAuth: true}},{path: "/pk/",name: "pk_index",component: PkIndexView,meta: {requestAuth: true}},{path: "/error/",name: "404",component: NotFound,meta: {requestAuth: false}},{path: "/record/",name: "record_index",component: RecordIndexView,meta: {requestAuth: true}},{path: "/ranklist/",name: "ranklist_index",component: RanklistIndexView,meta: {requestAuth: true}},{path: "/user/bot/",name: "user_bot_index",component: UserBotIndexView,meta: {requestAuth: true}},{path: "/user/account/login/",name: "user_account_login",component: UserAccountLoginView,meta: {requestAuth: false}},{path: "/user/account/register/",name: "user_account_register",component: UserAccountRegisterView,meta: {requestAuth: false}},// 重定向到404{path: "/:catchAll(.*)",redirect: "/error/"}]const router = createRouter({history: createWebHistory(),routes
})//router在起作用之前执行的一个函数
router.beforeEach((to, from, next) => {// 如果发现去的没有授权和登录,重定向到login,否则跳转默认页面if (to.meta.requestAuth && !store.state.is_login) {next({ name: "user_account_login" });} else {next();}
})
export default router

注册页面

<template><ContentField><div class="row justify-content-center"><div class="col-3"><!-- 绑定默认函数,提交触发login函数,并阻止掉默认行为 --><form @submit.prevent="register"><div class="mb-3"><label for="username" class="form-label">用户名</label><input v-model="username" type="text" class="form-control" id="username" placeholder="请输入用户名"></div><div class="mb-3"><label for="password" class="form-label">密码</label><input v-model="password" type="password" class="form-control" id="password" placeholder="请输入密码"></div><div class="mb-3"><label for="confirmedPassword" class="form-label">确认密码</label><input v-model="confirmedPassword" type="password" class="form-control" id="confirmedPassword"placeholder="请再次输入密码"></div><div class="error-message">{{ error_message }}</div><button type="submit" class="btn btn-primary">提交</button></form></div></div></ContentField>
</template><script>
import ContentField from "../../../components/ContentField.vue"
import { ref } from 'vue'
import router from "../../../router/index";import $ from 'jquery'export default {comments: {ContentField},setup() {let username = ref('');let password = ref('');let confirmedPassword = ref('');let error_message = ref('');const register = () => {$.ajax({url: "http://127.0.0.1:8080/user/account/register/",//如果是修改数据库用post,只获取数据用gettype: "post",data: {username: username.value,password: password.value,confirmedPassword: confirmedPassword.value,},// "success" : function(resp){//     console.log(resp);// },//简化版,关键字中的引号可以去掉,函数可以省略简写success(resp) {if (resp.error_message === "suceess") {router.push({ name: "user_account_login" });} else {//不成功,显示错误信息error_message.value = resp.error_message;}},"error": function (resp) {console.log(resp);}})}return {username,password,confirmedPassword,error_message,register,}}}
</script><style scoped>
button {width: 100%;
}div.error-message {color: red;
}
</style>

登录的持久化

UserAccountLoginView.vue

<template><!-- <ContentField v-if="show_content"> --><ContentField v-if="!$store.state.user.pulling_info"><!-- div.row>div.col-3 --><div class="row justify-content-center"><div class="col-3"><!-- 绑定默认函数,提交触发login函数,并阻止掉默认行为 --><form @submit.prevent="login"><div class="mb-3"><label for="username" class="form-label">用户名</label> --><!-- 绑定username用 v-modle --><input v-model="username" type="text" class="form-control" id="username" placeholder="请输入用户名"></div><div class="mb-3"><label for="password" class="form-label">密码</label><input v-model="password" type="password" class="form-control" id="password" placeholder="请输入密码"></div><div class="error-message"><!-- 密码错误  -->{{ error_message }}</div><button type="submit" class="btn btn-primary">提交</button></form></div></div></ContentField>
</template><script>
import ContentField from "../../../components/ContentField.vue"
import { useStore } from "vuex";
import { ref } from 'vue';
import router from "../../../router/index"
export default {comments: {ContentField},setup() {const store = useStore();let username = ref('');let password = ref('');let error_message = ref('');// let show_content = ref(false);//取出token,如果不为空,调用updateToken,getinfo//如果没有满足已经获取token,则不需要再调用登录页面,否则更新时会闪过loginconst jwt_token = localStorage.getItem("jwt_token");if (jwt_token) {store.commit("updateToken", jwt_token);store.dispatch("getinfo", {success() {router.push({ name: "home" });store.commit("updatePullingInfo", false);},error() {//如果token过期了,展示出登录页面// show_content.value = true;store.commit("updatePullingInfo", false);}})} else {//如果本地没有jwt_token的话也要展示出来// show_content.value = true;//拉取结束store.commit("updatePullingInfo", false);}const login = () => {//清空 error_messageerror_message.value = "";store.dispatch("login", {username: username.value,password: password.value,success() {// console.log(resp);//成功后调用回调函数,先获取用户信息,getinfo为store.user下自定义函数名store.dispatch("getinfo", {success() {//登录成功,跳转主页面router.push({ name: "home" });// 调// console.log(store.state.user);}})},error() {error_message.value = "用户名或密码有错误";}})}return {username,password,error_message,login,// show_content,}}
}
</script>

 修改store--->user.js

import $ from 'jquery'export default {state: {id: "",username: "",photo: "",token: "",is_login: false,pulling_info: true,  // 是否正在从云端拉取信息},getters: {},mutations: {updateUser(state, user) {state.id = user.id;state.username = user.username;state.photo = user.photo;state.is_login = user.is_login;},updateToken(state, token) {state.token = token;},logout(state) {state.id = "";state.username = "";state.photo = "";state.token = "";state.is_login = false;},updatePullingInfo(state, pulling_info) {state.pulling_info = pulling_info;}},actions: {login(context, data) {$.ajax({url: "http://127.0.0.1:8080/user/account/token/",type: "post",data: {username: data.username,password: data.password,},success(resp) {if (resp.error_message === "success") {localStorage.setItem("jwt_token", resp.token);context.commit("updateToken", resp.token);data.success(resp);} else {data.error(resp);}},error(resp) {data.error(resp);}});},getinfo(context, data) {$.ajax({url: "http://127.0.0.1:8080/user/account/info/",type: "get",headers: {Authorization: "Bearer " + context.state.token,},success(resp) {if (resp.error_message === "success") {context.commit("updateUser", {...resp,is_login: true,});data.success(resp);} else {data.error(resp);}},error(resp) {data.error(resp);}})},logout(context) {localStorage.removeItem("jwt_token");context.commit("logout");}},modules: {}
}

修改NavBar.vue

<!-- html -->
<template><nav class="navbar navbar-expand-lg  navbar-dark bg-dark"><div class="container"><!-- 刷新 --><!-- <a class="navbar-brand" href="/">King Of Bots</a> --><!-- 点击页面不刷新用router-link --><router-link class="navbar-brand" :to="{ name: 'pk_index' }">King Of Bots</router-link><div class="collapse navbar-collapse" id="navbarText"><ul class="navbar-nav me-auto mb-2 mb-lg-0"><li class="nav-item"><!-- active高亮 --><!-- <router-link class="nav-link active " :to="{ name: 'pk_index' }">对战</router-link> --><!-- 选中的高亮 --><router-link :class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'pk_index' }">对战</router-link></li><li class="nav-item"><router-link :class="route_name == 'record_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'record_index' }">对局列表</router-link></li><li class="nav-item"><router-link :class="route_name == 'ranklist_index' ? 'nav-link active' : 'nav-link'":to="{ name: 'ranklist_index' }">排行榜</router-link></li></ul><ul class="navbar-nav" v-if="$store.state.user.is_login"><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"aria-expanded="false">{{ $store.state.user.username }}</a><ul class="dropdown-menu"><li><router-link class="dropdown-item" :to="{ name: 'user_bot_index' }">my bot</router-link></li><!--  @click="logout"点击调用logout(1)函数 --><li><a class="dropdown-item" href="#" @click="logout">exit</a></li></ul></li></ul><!-- 没有拉取信息时再展示 --><ul class="navbar-nav" v-else-if="!$store.state.user.pulling_info"><li class="nav-item "><router-link class="nav-link " :to="{ name: 'user_account_login' }" role="button">登录</router-link></li><li class="nav-item "><router-link class="nav-link " :to="{ name: 'user_account_register' }" role="button">注册</router-link></li></ul></div></div></nav>
</template><!-- js -->
<script >
// 实现选中的页面高亮
import { useRoute } from 'vue-router';
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {setup() {const store = useStore();const route = useRoute();let route_name = computed(() => route.name)//触发函数logout(1) 调用logout(2)const logout = () => {// 调用user.js中的logout(3)store.dispatch("logout");}return {route_name,logout}}
}
</script><!-- css -->
<!-- scoped 作用:写的css会加上一个随机字符串,使得样式不会影响组件以外的部分 -->
<style scoped></style>

项目实战——配置MySQL与Spring Security模块_springsecurity数据库mysq设计-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/714814.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Spring Boot 源码学习】BootstrapRegistry 初始化器实现

《Spring Boot 源码学习系列》 BootstrapRegistry 初始化器实现 一、引言二、往期内容三、主要内容3.1 BootstrapRegistry3.2 BootstrapRegistryInitializer3.3 BootstrapRegistry 初始化器实现3.3.1 定义 DemoBootstrapper3.3.2 添加 DemoBootstrapper 四、总结 一、引言 前面…

Avalonia学习(二十八)-OpenGL

Avalonia已经继承了opengl&#xff0c;详细的大家可以自己查阅。Avalonia里面启用opengl继承OpenGlControlBase类就可以了。有三个方法。分别是初始化、绘制、释放。 这里把官方源码的例子扒出来给大家看一下。源码在我以前发布的单组件里面。地址在前面的界面总结博文里面。 …

YOLOv9有效改进|使用空间和通道重建卷积SCConv改进RepNCSPELAN4

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、改进点介绍 SCConv是一种即插即用的空间和通道重建卷积。 RepNCSPELAN4是YOLOv9中的特征提取模块&#xff0c;类似YOLOv5和v8中的C2f与C3模块。 …

MySQL进阶:MySQL事务、并发事务问题及隔离级别

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习、 &#x1f30c;上期文章&#xff1a;MySQL进阶&#xff1a;视图&&存储过程&&存储函数&&触发器 &#x1f4da;订阅专栏&#xff1a;MySQL进…

Docker Machine windows系统下 安装

如果你是 Windows 平台&#xff0c;可以使用 Git BASH&#xff0c;并输入以下命令&#xff1a; basehttps://github.com/docker/machine/releases/download/v0.16.0 &&mkdir -p "$HOME/bin" &&curl -L $base/docker-machine-Windows-x86_64.exe >…

点燃技能火花:探索PyTorch学习网站,开启AI编程之旅!

介绍&#xff1a;PyTorch是一个开源的Python机器学习库&#xff0c;它基于Torch&#xff0c;专为深度学习和科学计算而设计&#xff0c;特别适合于自然语言处理等应用程序。以下是对PyTorch的详细介绍&#xff1a; 历史背景&#xff1a;PyTorch起源于Torch&#xff0c;一个用于…

【真机Bug】异步加载资源未完成访问单例导致资源创建失败

1.错误表现描述 抽卡时&#xff0c;10抽展示界面为A。抽取内容可能是整卡或者碎片&#xff0c;抽到整卡&#xff0c;会有立绘展示和点击详情的按钮。点击详情后出现详情页B。【此时界面A预制体被销毁&#xff0c;卡片数据进入数据缓存池】点击页面B的返回按钮&#xff0c;单例…

C++——模版

前言&#xff1a;哈喽小伙伴们好久不见&#xff0c;这是2024年的第一篇博文&#xff0c;我们将继续C的学习&#xff0c;今天这篇文章&#xff0c;我们来习一下——模版。 目录 一.什么是模版 二.模版分类 1.函数模版 2.类模板 总结 一.什么是模版 说起模版&#xff0c;我们…

高性能通信之Netty

一, 同步IO(BIO)模型的架构 一般针对性能不高的情况下可以使用. 二,异步IO(NIO)模型的架构 多路复用(epoll模型):

【LeetCode:124. 二叉树中的最大路径和 + 二叉树+递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【力扣hot100】刷题笔记Day19

前言 回溯回溯回溯&#xff01;早上整理档案竟然用了桶排序&#xff0c;不愧是算法狂魔们 79. 单词搜索 - 力扣&#xff08;LeetCode&#xff09; DFS class Solution:def exist(self, board: List[List[str]], word: str) -> bool:m, n len(board), len(board[0])# used…

谈谈高并发系统的设计方法论

谈谈高并发系统的设计方法论 何为高并发系统&#xff1f;什么是并发&#xff08;Conurrent&#xff09;&#xff1f;什么是高并发&#xff08;Hight Concurrnet&#xff09;&#xff1f;高并发的衡量指标有哪些&#xff1f; 实现高并发系统的两大板块高并发系统应用程序侧的设计…

腾讯云学生服务器使用教程_申请腾讯云学生机详细流程

2024年腾讯云学生服务器优惠活动「云校园」&#xff0c;学生服务器优惠价格&#xff1a;轻量应用服务器2核2G学生价30元3个月、58元6个月、112元一年&#xff0c;轻量应用服务器4核8G配置191.1元3个月、352.8元6个月、646.8元一年&#xff0c;CVM云服务器2核4G配置842.4元一年&…

还在用Jenkins?快来试试这款简而轻的自动部署软件!

最近发现了一个比 Jenkins 使用更简单的项目构建和部署工具&#xff0c;完全可以满足个人以及一些小企业的需求&#xff0c;分享一下。 Jpom 是一款 Java 开发的简单轻量的低侵入式在线构建、自动部署、日常运维、项目监控软件。 日常开发中&#xff0c;Jpom 可以解决下面这些…

吴恩达机器学习全课程笔记第五篇

目录 前言 P80-P85 添加数据 迁移学习 机器学习项目的完整周期 公平、偏见与伦理 P86-P95 倾斜数据集的误差指标 决策树模型 测量纯度 选择拆分方式增益 使用分类特征的一种独热编码 连续的有价值特征 回归树 前言 这是吴恩达机器学习笔记的第五篇&#xff0c…

《2023跨境电商投诉大数据报告》发布|亚马逊 天猫国际 考拉海购 敦煌网 阿里巴巴

2023年&#xff0c;跨境电商API接口天猫国际、京东国际和抖音全球购以其强大的品牌影响力和市场占有率&#xff0c;稳坐行业前三的位置。同时&#xff0c;各大跨境电商平台消费纠纷问题层出不穷。依据国内知名网络消费纠纷调解平台“电诉宝”&#xff08;315.100EC.CN&#xff…

javaEE--后端环境变量配置

目录 pre 文件准备 最终运行成功结果 后端运行步骤 1.修改setenv文件 2.运行setenv&#xff0c;设置环境变量 3.查看jdk版本 4.修改mysql文件夹下的my文件 前端运行步骤 1.nodejs环境配置 2.查看node和npm版本 3.下载并运行npm 4.注册登录 pre 文件准备 最终运行…

VR转接器:破解虚拟与现实边界的革命性设备

VR转接器&#xff0c;这一革命性的设备&#xff0c;为虚拟现实体验带来了前所未有的自由度。它巧妙地连接了虚拟与现实&#xff0c;使得用户在享受VR眼镜带来的奇幻世界的同时&#xff0c;也能自由地在现实世界中活动。这一设计的诞生&#xff0c;不仅解决了VR眼镜续航的瓶颈问…

GO结构体

1. 结构体 Go语言可以通过自定义的方式形成新的类型&#xff0c;结构体就是这些类型中的一种复合类型&#xff0c;结构体是由零个或多个任意类型的值聚合成的实体&#xff0c;每个值都可以称为结构体的成员。 结构体成员也可以称为“字段”&#xff0c;这些字段有以下特性&am…

STM32 | 零基础 STM32 第一天

零基础 STM32 第一天 一、认知STM32 1、STM32概念 STM32:意法半导体基于ARM公司的Cortex-M内核开发的32位的高性能、低功耗单片机。 ST:意法半导体 M:基于ARM公司的Cortex-M内核的高性能、低功耗单片机 32&#xff1a;32位单片机 2、STM32开发的产品 STM32开发的产品&a…