使用技术
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博客