Login + getInfo + Register 后端 API 书写, login + logout 前端书写

This commit is contained in:
flykhan 2023-02-20 22:15:52 +08:00
parent 78db9a28c8
commit 0badc0f83d
23 changed files with 802 additions and 8 deletions

View File

@ -68,6 +68,30 @@
<version>3.0.2</version>
</dependency>
<!-- 用于使用 JWT 验证-->
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -83,6 +107,12 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>

View File

@ -1,17 +1,55 @@
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;
// TODO: 2023/2/19 WebSecurityConfigurerAdapter 已被弃用,注意替换
@Configuration
@EnableWebSecurity
public class SecurityConfig {
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
// 配置密码加密方式
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/*
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
用于配置公开链接
*/
@Override
protected 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);
}
}

View File

@ -0,0 +1,68 @@
package com.kob.backend.config.filter;
//filter 类用于验证
/*
主要作用:
1. 用来验证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 com.sun.xml.internal.bind.v2.TODO;
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 {
@Autowired
private UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
// TODO 这里可以更改 bearar 为其他字符作为验证前缀
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);
}
}

View File

@ -12,7 +12,8 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
//实现对用户类 User 的增删查操作
//实现对用户类 User 的增删查操作->这个类仅用于调试, Deparecated
@Deprecated
@RestController
public class UserController {
@ -58,7 +59,7 @@ public class UserController {
// 加密明文密码并存入密文到数据库
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPassword = passwordEncoder.encode(password);
User user = new User(userId, username, encodedPassword);
User user = new User(userId, username, encodedPassword,"");
userMapper.insert(user);
return "Add User Successfully";
}

View File

@ -0,0 +1,20 @@
package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.InfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class InfoController {
@Autowired
private InfoService infoService;
// 获取信息
@GetMapping("/user/account/info/")
public Map<String,String> getInfo(){
return infoService.getInfo();
}
}

View File

@ -0,0 +1,27 @@
package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
//@RestController 配合 @PostMapping("/.../.../") 使用:映射 Url 地址
@RestController
public class LoginController {
// @Autowired 注入接口
@Autowired
private LoginService loginService;
// 登录使用 POST 请求,密文传输,更安全
@PostMapping("/user/account/token/")
public Map<String,String> getToken(@RequestParam Map<String,String> map){
String username = map.get("username");
String password = map.get("password");
return loginService.getToken(username,password);
}
}

View File

@ -0,0 +1,24 @@
package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.RegisterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class RegisterController {
@Autowired
private RegisterService registerService;
@PostMapping("/user/account/register/")
public Map<String, String> register(@RequestParam Map<String, String> map) {
String username = map.get("username");
String password = map.get("password");
String confirmedPassword = map.get("confirmedPassword");
return registerService.register(username, password, confirmedPassword);
}
}

View File

@ -6,6 +6,8 @@ Data 用于编译时自动生成 getter setter 方法;
NoArgsConstructor 用于生成无参构造函数;
AllArgsConstructor 用于生成所有参数构造函数.
*/
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -15,9 +17,12 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class User {
// 使用对象类型定义而不是 Int 防止 Mybatis 报错
// 使用对象类型定义而不是 Int 防止 Mybatis 报错
// @TableId(type = IdType.AUTO) 用于 id 的自增
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String photo;
}

View File

@ -18,6 +18,12 @@ public class UserDetailsServiceImpl implements UserDetailsService {
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/*
从数据库里读取用户信息,比对用户名密码与数据库存在的用户名密码,信息一致,给用户发一个 sessionID ,
存到用户本地浏览器,服务器同时会自己存一份未来用户再登录时服务器会将自己的 sessionID 与用户发过来
请求包含的 sessionID 进行比照,如果一致,则授权成功
*/
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
User user = userMapper.selectOne(queryWrapper);

View File

@ -0,0 +1,34 @@
package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.InfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class InfoServiceImpl implements InfoService {
@Override
public Map<String, String> getInfo() {
UsernamePasswordAuthenticationToken authenticationToken =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
User user = loginUser.getUser();
Map<String,String> map = new HashMap<>();
map.put("error_message","success");
map.put("id",user.getId().toString());
map.put("username",user.getUsername());
map.put("photo",user.getPhoto());
return map;
}
}

View File

@ -0,0 +1,50 @@
package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.LoginService;
import com.kob.backend.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
//@Service 注解用于注入接口实现到 Spring 里面
@Service
public class LoginServiceImpl implements LoginService {
// @Autowired 用于注入
@Autowired
private AuthenticationManager authenticationManager;
@Override
public Map<String, String> getToken(String username, String password) {
// 封装用户名和密码成 UsernamePasswordAuthenticationToken :此类不存储明文,而是存储加密之后的字符串
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
// 验证是否正常登录,登录验证失败时会自动处理(报异常)
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 登录成功,取出用户
UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
User user = loginUser.getUser();
// 封装 jwt 信息: userID 转换为 String
String jwt = JwtUtil.createJWT(user.getId().toString());
/*
成功之后返回结果:
1. "error_message" success;失败之后会报异常,自动处理掉
2. "token" 返回 jwt-token 信息
*/
Map<String,String> map = new HashMap<>();
map.put("error_message","success");
map.put("token",jwt);
return map;
}
}

View File

@ -0,0 +1,81 @@
package com.kob.backend.service.impl.user.account;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.pojo.User;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.service.user.account.RegisterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
@Service
public class RegisterServiceImpl implements RegisterService {
// 这里使用 @Autowired 注入是为了调用数据库,然后进行数据库查询,比较是否有用户名重复
@Autowired
private UserMapper userMapper;
@Override
public Map<String, String> register(String username, String password, String confirmedPassword) {
Map<String, String> map = new HashMap<>();
// 如果用户名为空,则提示用户返回
if (username == null) {
map.put("error_message", "用户名不能为空");
return map;
}
if (password == null || confirmedPassword == null) {
map.put("error_message", "密码不能为空");
return map;
}
// 用户名需要将输入的首尾空格删掉
username = username.trim();
if (username.length() == 0) {
map.put("error_message", "用户名不能为空");
return map;
}
if (password.length() == 0) {
map.put("error_message", "密码不能为空");
return map;
}
if (username.length() > 100) {
map.put("error_message", "用户名过长");
return map;
}
if (password.length() > 100 || confirmedPassword.length() > 100) {
map.put("error_message", "密码过长");
return map;
}
// 密码验证是 String 类型比较,应该用 equals() 方法
if (!confirmedPassword.equals(password)) {
map.put("error_message", "两次密码输入不一致");
return map;
}
/*
查询数据库里是否有用户名 this.username 已存在的用户,并将结果存入 users ,
如果发现 users 不是空的,则告诉注册用户当前用户名已存在
*/
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.eq("username", username);
List<User> users = userMapper.selectList(queryWrapper);
if (!users.isEmpty()) {
map.put("error_message", "用户名已存在");
return map;
}
// 异常情况判断结束,开始将合法用户注册信息注入数据库
// 对密码进行加密
String encodedPassword = new BCryptPasswordEncoder().encode(password);
// 默认头像
String photo = "https://cdn.acwing.com/media/user/profile/photo/253652_lg_e3d8435b66.jpg";
// id 是数据库自增,这里生成新用户只需要将 id 参数写为 null 即可
User user = new User(null,username,encodedPassword,photo);
userMapper.insert(user);
map.put("error_message","成功注册");
return map;
}
}

View File

@ -0,0 +1,8 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//根据令牌返回用户信息
public interface InfoService {
public Map<String,String> getInfo();
}

View File

@ -0,0 +1,8 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//验证用户名密码验证成功后返回jwt token令牌
public interface LoginService {
public Map<String,String> getToken(String username, String password);
}

View File

@ -0,0 +1,9 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//注册账号
public interface RegisterService {
// confirmedPassword 密码确认
public Map<String,String> register(String username, String password, String confirmedPassword);
}

View File

@ -0,0 +1,74 @@
package com.kob.backend.utils;
//jwt工具类用来创建解析 jwt token
/*
主要作用:
1. 将字符串加上密钥,加上有效期,转换为加密后的字符串;
2. 给一个令牌,将其 userID 解析出来.
依赖:添加到 pom.xml ,然后使用 Maven 重新加载依赖项
jjwt-apt;
jjwt-impl;
jjwt-jackson.
*/
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天:单位-毫秒 ms
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232113afasdfad"; // 密钥:随机字符串-大小写英文字母+数字
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();
}
}

View File

@ -10,9 +10,56 @@ import NavBar from "./components/NavBar.vue";
// BootStrap css js BootStrap
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap/dist/js/bootstrap";
// import $ from "jquery";
export default {
components: { NavBar },
/* setup: () => {
$.ajax({
url: "http://localhost:3000/user/account/token/",
type: "POST",
data: {
username: "bb",
password: "pbb",
},
success(resp) {
console.log(resp.token, "\n成功了\n", resp.error_message);
},
error(resp) {
console.log(resp);
},
});
$.ajax({
url: "http://localhost:3000/user/account/info/",
type: "GET",
headers: {
Authorization:
"Bearer " +
"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIzODA2Yzc0ODkwYTE0NTgwYjcyOGQzOWI4NDYyYWY2ZSIsInN1YiI6IjYiLCJpc3MiOiJzZyIsImlhdCI6MTY3Njg4ODk5MywiZXhwIjoxNjc4MDk4NTkzfQ.mq7Xdt3G7VGvX7fgIYGN2MfH8bo9MuZ7V0nfzGmbDZ8",
},
success(resp) {
console.log(resp);
},
error(resp) {
console.log(resp);
},
});
$.ajax({
url: "http://localhost:3000/user/account/register/",
type: "POST",
data: {
username: "cc",
password: "pcc",
confirmedPassword: "pcc",
},
success(resp) {
console.log(resp);
},
error(resp) {
console.log(resp);
},
});
}, */
};
</script>
<style>

View File

@ -27,7 +27,8 @@
>
</li>
</ul>
<ul class="navbar-nav">
<!-- 判断是否登录成功:如果登录成功,则修改导航栏这部分内容 -->
<ul class="navbar-nav" v-if="$store.state.user.is_login">
<!-- 下拉菜单样式 -->
<li class="nav-item dropdown">
<a
@ -37,7 +38,7 @@
data-bs-toggle="dropdown"
aria-expanded="false"
>
flykhan
{{ $store.state.user.username }}
</a>
<ul class="dropdown-menu">
<li>
@ -47,10 +48,36 @@
</li>
<!-- 下拉菜单分割线 -->
<li><hr class="dropdown-divider" /></li>
<li><a class="dropdown-item" href="#">退出</a></li>
<!-- v-on:click 也可以写为 @click ; 给退出按钮加上手形状按钮样式 style="cursor: pointer"-->
<li>
<a class="dropdown-item" style="cursor: pointer" @click="logout">退出</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>
@ -59,15 +86,24 @@
<script>
import { useRoute } from "vue-router";
import { computed } from "vue";
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const route = useRoute();
// nav-link , nav-link nav-link active
let route_name = computed(() => route.name);
const logout = () => {
console.log("退出前:" + store.state.user.token);
store.dispatch("logout");
console.log("退出后:" + store.state.user.token);
};
return {
route_name,
logout,
};
},
};

View File

@ -5,6 +5,8 @@ import RanklistIndexView from '../views/ranklist/RanklistIndexView.vue'
import RecordIndexView from '../views/record/RecordIndexView.vue'
import UserBotIndexView from '../views/user/bot/UserBotIndexView.vue'
import NotFound from '../views/error/NotFound.vue'
import UserAccountLoginView from '@/views/user/account/UserAccountLoginView.vue'
import UserAccountRegisterView from '@/views/user/account/UserAccountRegisterView.vue'
// 定义所有页面的 URL 路由
@ -35,6 +37,16 @@ const routes = [
name:'user_bot_index',
component:UserBotIndexView
},
{
path:'/user/account/login/',
name:'user_account_login',
component:UserAccountLoginView
},
{
path:'/user/account/register/',
name:'user_account_register',
component:UserAccountRegisterView
},
{
path:'/404/',
name:'404',

View File

@ -1,4 +1,5 @@
import { createStore } from 'vuex'
import ModuleUser from './user'
export default createStore({
state: {
@ -10,5 +11,7 @@ export default createStore({
actions: {
},
modules: {
user: ModuleUser,
}
})

100
web/src/store/user.js Normal file
View File

@ -0,0 +1,100 @@
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;
},
// 更新用户 Token
updateToken(state, token){
state.token = token;
},
// 退出登录
logout(state){
state.id = "";
state.username = "";
state.photo = "";
state.token = "";
state.is_login = false;
}
},
// 异步事件
actions: {
// 登录函数
login(context, data){
$.ajax({
url: "http://localhost:3000/user/account/token/",
type: "POST",
data: {
username: data.username,
password: data.password,
},
success(resp) {
// console.log(resp.token, "\n成功了\n", resp.error_message);
if(resp.error_message === "success"){
/*
登录成功则将获取到的 resp 里的 token 传给 mutations 里的
updateToken 方法,对用户 token 信息进行更新
*/
context.commit("updateToken", resp.token);
data.success(resp);
} else {
data.error(resp);
}
},
error(resp) {
data.error(resp);
},
});
},
// 获取登录成功后的用户信息
getinfo(context,data){
$.ajax({
url: "http://localhost:3000/user/account/info/",
type: "GET",
headers: {
Authorization:
"Bearer " + context.state.token,
},
success(resp) {
if(resp.error_message === "success"){
// 更新用户信息
context.commit("updateUser",{
...resp, // 解构 resp 中的内容
is_login: true, // 登录成功,将 is_login 置为 true
});
data.success(resp); // 调用回调函数
} else {
data.error(resp);
}
},
error(resp) {
data.error(resp);
},
});
},
logout(context){
context.commit("logout");
}
},
modules: {
}
}

View File

@ -0,0 +1,98 @@
<template>
<ContentBase>
<!-- justify-content-md-center 用于居中 -->
<div class="row justify-content-md-center">
<div class="col-3">
<!-- 定义登录表单 @submita="login" 表示表单提交时触发 login 函数; submit.prevent 用于阻止 submit 的默认提交行为,
而是调用自己定义的 login 函数来实现提交行为
-->
<form @submit.prevent="login">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<!-- 使用 v-model 来绑定函数里的 username 变量 -->
<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>
<!-- float-start 左布局, float-end 右布局 -->
<!-- type="submit" 提交类型 -->
<button type="submit" class="btn btn-primary btn-sm float-center">登录</button>
</form>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from "../../../components/ContentBase.vue";
import { useStore } from "vuex";
import { ref } from "vue";
import router from "../../../router/index";
export default {
components: {
ContentBase,
},
setup: () => {
const store = useStore();
let username = ref("");
let password = ref("");
let error_message = ref("");
const login = () => {
error_message.value = "";
// , store/user.js login
store.dispatch("login", {
username: username.value,
password: password.value,
success() {
// ,
store.dispatch("getinfo", {
success() {
// ,
router.push({ name: "home" });
console.log(store.state.user);
},
});
},
error() {
// console.log(resp);
error_message.value = "用户名或密码错误";
},
});
};
return {
username,
password,
error_message,
login,
};
},
};
</script>
<style scoped>
div.error-message {
color: red;
}
button {
width: 100%;
}
</style>

View File

@ -0,0 +1,15 @@
<template>
<ContentBase>注册</ContentBase>
</template>
<script>
import ContentBase from "../../../components/ContentBase.vue";
export default {
components: {
ContentBase,
},
};
</script>
<style scoped></style>