目录
1、加载依赖
2、实现jwt工具类jwtUtil类
3、实现config.filter.JwtAuthenticationTokenFilter类
4、配置config.SecurityConfig类
5、创建后端api之前对数据库进行修改
6、写API一共需要的三个地方
7、实现三个接口
8、验证用户登录用API+调试
9、https://jwt.io/解析需要的小工具
10、完成第二个用户授权用API+调试
11、完成第三个用户授注册API+调试
12、前端的实现
传统模式都是用session验证,由于现在都是前后端分离可能会有跨域问题
用jwt验证会更容易一些,第二部分是写一些用户的api
2.1 登录获取一个令牌
2.2 获取某个用户的信息
2.3 注册账号
页面登录授权逻辑,传统逻辑是,我们有很多的url有很多页面,公开页面授权页面
登录之后如何授权用户登录
登陆一次之后只要不关浏览器,只要url一来我们就可以访问页面的逻辑
controller
我们在登录页面是如何授权的,我们在service里面有一个类
在数据库里读取用户信息,将用户传过来的的密码和数据库里的密码进行比对与数据库一致,就会给用户发一个sessionid,类似于身份证号用户会将id存到自己的本地,存到cookie里面,未来每次访问网站的时候都会将浏览器里存储的id发送给浏览器服务器发给用户之后同时自己会存一份,有可能存到数据库里有可能存到内存里都有可能,未来用户去访问一些授权页面的时候就会把sessionid带上服务器接收到用户想访问授权页面的时候会将sessionid提取出来去判断sessionid是否有效,因为我们在服务器端把id存下来了用户来了之后我们可以去数据库或者内存中查一下如果存在,服务器不仅会存储id还会存服务器对应的用户是哪个用户服务器存了一个映射会将id映射到用户,如果有效我们就会从服务器端将我们对应的用户信息提取出来,将user提取出来,提取到上下文中就是可以在controller中通过某种接口将用户提取出来了 提取到上下文当中就是我们在controller当中通过某些api获取用户了获取用户用户名密码这些,头像信息,下一步就可以正常访问我们的页面了访问我们的逻辑,其实就是访问controller,传统的登录验证的一个模式
现在我们有很多应该其实都是跨域的
很多应该不止有一个端,有web端也会有app端,一旦跨域session会很难用比较难以处理,jwt验证模式我们可能会多个服务器端,很容易实现跨域,它不需要在服务器端存储,只需要获取一个令牌就可以登录多个服务器,原理是,本来一个用户登录之后我们要给她一个sessionid,现在需要给他一个jwt-token、服务器给他token 之后自己是不会存太多信息的,那么未来我们如何知道哪个用户是哪个用户呢、我们可以将userid一些信息加入到我们的的jwt-token里面
1、加载依赖
jjwt-api
jjwt-impl
jjwt-jackson
加完之后重新加载
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.2</version></dependency><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.2</version> <scope>runtime</scope></dependency><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.2</version> <scope>runtime</scope></dependency>
2、实现jwt工具类jwtUtil类
编写、修改相关类
实现 JwtUtil 类
在 backend 目录下创建软件包 utils 并创建 JwtUtil 类。
JwtUtil 类为jwt 工具类,用来创建、解析 jwt token。
package com.kill9.backend.utils;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;@Componentpublic 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(); }}
这个类的模板可以直接用,大概知道每个函数是干什么的就可以了,jwt验证都是这样
3、实现config.filter.JwtAuthenticationTokenFilter类
实现 JwtAuthenticationTokenFilter 类
在 backend 的 config 目录下创建 filter 软件包,并创建 JwtAuthenticationTokenFilter 类。
实现 JwtAuthenticationTokenFilter 类,用来验证 jwt token ,如果验证成功,则将 User 信息注入上下文中。
package com.kill9.backend.config.filter;import com.kill9.backend.mapper.UserMapper;import com.kill9.backend.pojo.User;import com.kill9.backend.service.impl.utils.UserDetailsImpl;import com.kill9.backend.utils.JwtUtil;import io.jsonwebtoken.Claims;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;@Componentpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private UserMapper userMapper; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 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(); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response); }}
4、配置config.SecurityConfig类
放行登录、注册等接口
这两个链接是公开的
创建后端api之前要对数据库进行修改
package com.kill9.backend.config;import com.kill9.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@EnableWebSecuritypublic 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(); } @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); }}
5、创建后端api之前对数据库进行修改
修改数据库
将数据库中的 id 域变为自增。
在 pojo.User 类中添加注解:@TableId(type = IdType.AUTO)
右击 user ,点击 修改表 ,双击 id ,选择 自动增加。同时添加一列 photo,类型设置为 varchar(1000) ,用来存储照片,默认值可以设置为自己的头像。
6、写API一共需要的三个地方
在 service 下创建一个接口。
在 service 下创建一个类实现这个接口。
在 controller 下创建一个类进行操作。
实现 LoginService
验证用户名密码,验证成功后返回 jwt token(令牌)
创建接口:在 service下 创建 user 创建 account 新建一个接口 LoginService
package com.kill9.backend.service.user.account;import java.util.Map;public interface LoginService { public Map<String, String> getToken(String username, String password);}
创建实现类:在 service下 impl 下创建 user 创建 account 新建一个实现类LoginServiceImpl
package com.kill9.backend.service.impl.user.account;import com.kill9.backend.pojo.User;import com.kill9.backend.service.impl.utils.UserDetailsImpl;import com.kill9.backend.service.user.account.LoginService;import com.kill9.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;@Servicepublic class LoginServiceImpl implements LoginService { @Autowired private AuthenticationManager authenticationManager; @Override public Map<String, String> getToken(String username, String password) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password); Authentication authentication = authenticationManager.authenticate(authenticationToken); UserDetailsImpl loginUser = (UserDetailsImpl) authentication.getPrincipal(); User user = loginUser.getUser(); String jwt = JwtUtil.createJWT(user.getId().toString()); Map<String, String> map = new HashMap<>(); map.put("error_message","success"); map.put("token",jwt); return map; }}
创建controller:在 controller 创建 user 创建 account 新建一个LoginController
package com.kill9.backend.controller.user.account;import com.kill9.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;@RestControllerpublic class LoginController { @Autowired private LoginService loginService; @PostMapping("/user/account/token/") public Map<String,String> getToken(@RequestParam Map<String,String> map){ String username = map.get("username"); String password = map.get("password"); System.out.println(username + " "+ password); return loginService.getToken(username,password); }}
7、实现三个接口
配置InforService类 根据令牌返回用户信息。
创建接口:在 service下 创建 user 创建 account 新建一个接口 InfoService
创建实现类:在 service下 impl 下创建 user 创建 account 新建一个实现类InfoServiceImpl
package com.kill9.backend.service.impl.user.account;import com.kill9.backend.pojo.User;import com.kill9.backend.service.impl.utils.UserDetailsImpl;import com.kill9.backend.service.user.account.InfoService;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;@Servicepublic class InfoServiceImpl implements InfoService { /** * 根据token返回用户信息 * @return map存储的信息 */ @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; }}
创建controller:在 controller 创建 user 创建 account 新建一个InfoController
package com.kill9.backend.controller.user.account;import com.kill9.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;@RestControllerpublic class InfoController { @Autowired private InfoService infoService; @GetMapping("/user/account/info/") public Map<String,String> getInfo(){ return infoService.getInfo(); }}
配置RegisterService类
注册账号
创建接口:在 service下 创建 user 创建 account 新建一个接口 RegisterService
创建实现类:在 service下 impl 下创建 user 创建 account 新建一个实现类RegisterServiceImpl
package com.kill9.backend.service.impl.user.account;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.kill9.backend.mapper.UserMapper;import com.kill9.backend.pojo.User;import com.kill9.backend.service.user.account.RegisterService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Service;import java.util.HashMap;import java.util.HashSet;import java.util.List;import java.util.Map;@Servicepublic class RegisterServiceImpl implements RegisterService { @Autowired private UserMapper userMapper; @Autowired private PasswordEncoder passwordEncoder; @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 || confirmedPassword.length() == 0) { map.put("error_message", "密码不能为空"); return map; } if (username.length() > 100) { map.put("error_message", "用户名长度不能大于100"); return map; } if (password.length() > 100 || confirmedPassword.length() > 100) { map.put("error_message", "密码不能大于100"); return map; } if (!password.equals(confirmedPassword)) { map.put("error_message", "两次输入的密码不一致"); return map; } //查询用户名是否重复 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 = passwordEncoder.encode(password); //输入自己的图片地址 String photo = null; User user = new User(null, username, encodedPassword, photo); userMapper.insert(user); map.put("error_message", "success"); return map; }}
创建controller:在 controller 创建 user 创建 account 新建一个RegisterController
package com.kill9.backend.controller.user.account;import com.kill9.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;@RestControllerpublic 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); }}
8、验证用户登录用API+调试
验证用户登陆:
在 APP.vue 中编写:
注意:这里的Authorization: “Bearer “有空格,且token为自己的浏览器的token,需要更改token。
<template> <NavBar/> <router-view/></template><script>import NavBar from "@/components/NavBar.vue";import "bootstrap/dist/css/bootstrap.min.css";import "bootstrap/dist/js/bootstrap"import $ from 'jquery'export default{ components:{ NavBar, }, setup(){// 测试LoginService $.ajax({ url:"http://127.0.0.1:3000/user/account/token/", type:"post", data:{ username:"pys", password:"123456", }, success(resp){ console.log(resp); }, error(resp){ console.log(resp); } }); // 测试InfoService }}</script><style> body{ background-image: url("./assets/images/background.jpeg"); background-size: cover; }</style>
// 测试InfoService $.ajax({ url:"http://127.0.0.1:3000/user/account/info/", type:"get", headers:{ Authorization:"Bearer " + "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwYmZjYmFmNzU2ZTA0MGVjOTg1NzRlM2QxNDExM2QyZSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY2ODMzMzczNSwiZXhwIjoxNjY5NTQzMzM1fQ.lWUbia17EeSvMs1derpXhzGUPhS1pOEe5d2zjYROas8", }, success(resp){ console.log(resp); }, error(resp){ console.log(resp); } });
// 测试RegisterService $.ajax({ url:"http://127.0.0.1:3000/user/account/register/", type:"post", data:{ username:"cdwihdow", password:"121212", confirmedPassword:"121212", }, success(resp){ console.log(resp); }, error(resp){ console.log(resp); } })
12、前端的实现
1. 实现两个页面–登陆、注册的前端样式:
从 bootstrap 上去寻找合适的样式:
举个例子:
样式:
<div class="container">
<div class="row row-cols-2">
<div class="col">Column</div>
<div class="col">Column</div>
</div>
</div>
表单:
<div class="mb-3"> <label for="exampleFormControlInput1" class="form-label">Email address</label> <input type="email" class="form-control" id="exampleFormControlInput1" placeholder="name@example.com"></div><div class="mb-3"> <label for="exampleFormControlTextarea1" class="form-label">Example textarea</label> <textarea class="form-control" id="exampleFormControlTextarea1" rows="3"></textarea></div>
创建页面:
在 views 目录下创建 user ,新建两个文件 UserAccountLoginView.vue 和 UserAccountRegisterView.vue 。
实现 UserAccountRegisterView.vue 页面
<template> <!-- 最外层的大盒子 --> <div class="bigBox"> <div class="box" ref="box"> <!-- 滑动盒子 --> <div class="pre-box"> <h1>WELCOME</h1> <p>JOIN US!</p> <div class="img-box"> <img src="../../assets/images/menu_op2.jpg" alt="" id="avatar" /> </div> </div> <!-- 注册盒子 --> <div class="register-form"> <!-- 标题盒子 --> <div class="title-box"> <h1>Register</h1> </div> <!-- 输入框盒子 --> <div class="row justify-content-md-center"> <div class="col-6"> <form> <div class="mb-3"> <label for="username" class="form-label" style="color:white">Username</label> <input v-model="username" type="text" class="form-control" id="username" placeholder="Username" > </div> <div class="mb-3"> <label for="password" class="form-label" style="color:white">Password</label> <input v-model="password" type="password" class="form-control" id="password" placeholder="Password" > </div> <div class="mb-3"> <label for="password" class="form-label" style="color:white">Password</label> <input v-model="confirmedPassword" type="password" class="form-control" id="password" placeholder="Confirm Password" > </div> <div class="error-message">{{ error_message }}</div> </form> </div> </div> <!-- 按钮盒子 --> <div class="btn-box"> <button @click="register">Register</button> <!-- 绑定点击事件 --> <p @click="mySwitch">Login with an existing account</p> </div> </div> <!-- 登录盒子 --> <div class="login-form"> <!-- 标题盒子 --> <div class="title-box"> <h1>Login</h1> </div> <!-- 输入框盒子 --> <div class="row justify-content-md-center"> <div class="col-6"> <form> <div class="mb-3"> <label for="username" class="form-label" style="color:white">Username</label> <input v-model="username" type="text" class="form-control" id="username" placeholder="Username" > </div> <div class="mb-3"> <label for="password" class="form-label" style="color:white">Password</label> <input v-model="password" type="password" class="form-control" id="password" placeholder="Password" > </div> <div class="error-message">{{ error_message }}</div> </form> </div> </div> <!-- 按钮盒子 --> <div class="btn-box"> <button @click="login">Login</button> <!-- 绑定点击事件 --> <p @click="mySwitch">No account? To register</p> </div> </div> </div> </div> </template> <script> import { ref } from 'vue'; import { useRouter } from 'vue-router'; export default{ setup(){ const loginUser = ref({ username: '', password: '' }); const registerUser = ref({ username: '', password: '', confirmPassword: '' }); const router = useRouter(); let flag = ref(true); const mySwitch = () => { const pre_box = document.querySelector('.pre-box') const img = document.querySelector("#avatar") if (flag.value) { pre_box.style.transform = "translateX(100%)" pre_box.style.backgroundColor = "#c9e0ed" img.src = require("@/assets/images/menu_op.jpg") } else { pre_box.style.transform = "translateX(0%)" pre_box.style.backgroundColor = "#edd4dc" img.src = require("@/assets/images/menu_op2.jpg") } flag.value = !flag.value } return{ loginUser, registerUser, router, mySwitch, } } } </script> <style scoped> /* 去除input的轮廓 */ input { outline: none; } .bigBox { /* 溢出隐藏 */ height: 100vh; overflow-x: hidden; display: flex; /* 渐变方向从左到右 */ background: linear-gradient(to right, rgb(247, 209, 215), rgb(191, 227, 241)); } /* 最外层的大盒子 */ .box { width: 1050px; height: 600px; display: flex; /* 相对定位 */ position: relative; z-index: 2; margin: auto; /* 设置圆角 */ border-radius: 8px; /* 设置边框 */ border: 1px solid rgba(255, 255, 255, 0.6); /* 设置盒子阴影 */ box-shadow: 2px 1px 19px rgba(0, 0, 0, 0.1); } /* 滑动的盒子 */ .pre-box { /* 宽度为大盒子的一半 */ width: 50%; height: 100%; /* 绝对定位 */ position: absolute; /* 距离大盒子左侧为0 */ left: 0; /* 距离大盒子顶部为0 */ top: 0; z-index: 99; border-radius: 4px; background-color: #edd4dc; box-shadow: 2px 1px 19px rgba(0, 0, 0, 0.1); /* 动画过渡,先加速再减速 */ transition: 0.5s ease-in-out; } /* 滑动盒子的标题 */ .pre-box h1 { margin-top: 150px; text-align: center; /* 文字间距 */ letter-spacing: 5px; color: white; /* 禁止选中 */ user-select: none; /* 文字阴影 */ text-shadow: 4px 4px 3px rgba(0, 0, 0, 0.1); } /* 滑动盒子的文字 */ .pre-box p { height: 30px; line-height: 30px; text-align: center; margin: 20px 0; /* 禁止选中 */ user-select: none; font-weight: bold; color: white; text-shadow: 4px 4px 3px rgba(0, 0, 0, 0.1); } /* 图片盒子 */ .img-box { width: 200px; height: 200px; margin: 20px auto; /* 设置为圆形 */ border-radius: 50%; /* 设置用户禁止选中 */ user-select: none; overflow: hidden; box-shadow: 4px 4px 3px rgba(0, 0, 0, 0.1); } /* 图片 */ .img-box img { width: 100%; transition: 0.5s; } /* 登录和注册盒子 */ .login-form, .register-form { flex: 1; height: 100%; } /* 标题盒子 */ .title-box { height: 150px; line-height: 500px; } /* 标题 */ .title-box h1 { padding-top: 50px; text-align: center; color: white; /* 禁止选中 */ user-select: none; letter-spacing: 5px; text-shadow: 4px 4px 3px rgba(0, 0, 0, 0.1); } /* 输入框盒子 */ .el-form { display: flex; /* 纵向布局 */ flex-direction: column; /* 水平居中 */ align-items: center; } .el-form-item { width: 65%; } /* 输入框 */ input { /* width: 60%; */ height: 40px; margin-bottom: 20px; text-indent: 10px; border: 1px solid #fff; background-color: rgba(255, 255, 255, 0.3); border-radius: 120px; /* 增加磨砂质感 */ backdrop-filter: blur(10px); } input:focus { /* 光标颜色 */ color: #b0cfe9; } /* 聚焦时隐藏文字 */ input:focus::placeholder { opacity: 0; } /* 按钮盒子 */ .btn-box { display: flex; justify-content: center; } /* 按钮 */ button { width: 100px; height: 30px; margin: 0 7px; line-height: 30px; border: none; border-radius: 4px; background-color: #69b3f0; color: white; } /* 按钮悬停时 */ button:hover { /* 鼠标小手 */ cursor: pointer; /* 透明度 */ opacity: 0.8; } /* 按钮文字 */ .btn-box p { height: 30px; line-height: 30px; /* 禁止选中 */ user-select: none; font-size: 14px; color: white; } .btn-box p:hover { cursor: pointer; border-bottom: 1px solid white; } .error-message{ color: red; } </style>
在router目录下的index.js中加入路径
修改 index.js
import UserAccountLoginView from '../views/user/account/UserAccountLoginView'import UserAccountRegisterView from '../views/user/account/UserAccountRegisterView'const routes = [ { path: "/user/account/login", name: "user_account_login", component: UserAccountLoginView, }, { path: "/user/account/register", name: "user_account_register", component: UserAccountRegisterView, }]const router = createRouter({ history: createWebHistory(), routes})export default router
记几个比较容易遗忘的点:
1. import { useStore } from 'vuex'; //全局变量是调用全局变量
需要用view存储一些全局的用户信息
在store下的user.js存储用户信息
/store/index.js或者是自己新建立的/store/user.js中的
state: 存储所有数据,可以用modules属性划分成若干模块
getters:根据state中的值计算新的值
mutations:所有对state的修改操作都需要定义在这里,不支持异步,可以通过$store.commit()触发
actions:定义对state的复杂修改操作,支持异步,可以通过$store.dispatch()触发。注意不能直接修改state,只能通过mutations修改state。
modules:定义state的子模块
前后端交互的形式长得都和下面差不多:
login(context, data) { $.ajax({ url: "http://127.0.0.1:3000/user/account/token/", type: "post", data: { username: data.username, password: data.password, }, success(resp) { //调用mutations里的函数,需要用commit + 函数名 //这里的resp是返回的后端传来的值,都是自己定义的,注意要与后端相匹配 if (resp.message === "success") { context.commit("updateToken", resp.token); data.success(resp); } else { data.error(resp); } }, error(resp) { data.error(resp); } }); },
在 store 下创建 user.js
import $ from 'jquery'export default{ state:{ id:"", username:"", password:"", 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; } }, actions:{ }, modules:{ },}
然后将user.js的Module导入到store/index.js
import { createStore } from 'vuex'import ModuleUser from './user'export default createStore({ state: { }, getters: { }, mutations: { }, actions: { }, modules: { user:ModuleUser, }})
添加辅助函数login. 在 store 下的 user.js 修改。
import $ from 'jquery'export default{ state:{ id:"", username:"", password:"", photo:"", token:"", is_login:false, }, getters:{ }, mutations:{//修改state里面的值 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; } }, actions:{ //异步 login(context,data){ // 测试LoginService $.ajax({ url:"http://127.0.0.1:3000/user/account/token/", type:"post", data:{ username:data.username, password:data.password, }, success(resp){ if(resp.error_message === "success"){ //调用 mutations里面的函数用commit context上下文调用mutations里面的函数 修改state的内容 context.commit("updateToken",resp.token); //调用data的成功函数 data.success(resp); } }, error(resp){ data.error(resp); } }); } }, modules:{ },}
在 UserAccountLoginView.vue 中实现
<script> import { ref } from 'vue'; import { useRouter } from 'vue-router'; import { useStore } from 'vuex'; export default{ setup(){ const store = useStore(); let username = ref(''); let password = ref(''); let error_message = ref(''); //触发函数 const login = ()=>{ error_message.value = ""; // 调用store 里actions的函数 // login 对应函数名 后面的对应 data store.dispatch("login",{ //用 ref 获取值要用value username:username.value, password:password.value, success(resp){ console.log(resp); }, error(resp){ console.log(resp); } }) }
把内容和变量绑定起来
<!-- 输入框盒子 --> <div class="row justify-content-md-center"> <div class="col-6"> <form @submit.prevent="login"> <div class="mb-3"> <label for="username" class="form-label" style="color:white">Username</label> <input v-model="username" type="text" class="form-control" id="username" placeholder="Username" > </div> <div class="mb-3"> <label for="password" class="form-label" style="color:white">Password</label> <input v-model="password" type="password" class="form-control" id="password" placeholder="Password" > </div> <div class="error-message">{{ error_message }}</div> </form> </div> </div>
在登陆界面测试
输入用户名和密码,获取 token。
用户名和密码输入正确,点击提交跳到主页面
在 UserAccountLoginView.vue 中实现
success(){ //成功后跳转到主页面 router.push({name:'home'}); }, error(){ error_message.value = "用户名或密码错误"; }
登陆成功后获取用户的信息
在 user.js 中添加辅助函数
import $ from 'jquery'export default{ state:{ id:"", username:"", password:"", photo:"", token:"", is_login:false, }, getters:{ }, mutations:{//修改state里面的值 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; } }, actions:{ //异步 login(context,data){ // 测试LoginService $.ajax({ url:"http://127.0.0.1:3000/user/account/token/", type:"post", data:{ username:data.username, password:data.password, }, success(resp){ if(resp.error_message === "success"){ //调用 mutations里面的函数用commit context上下文调用mutations里面的函数 修改state的内容 context.commit("updateToken",resp.token); //调用data的成功函数 data.success(resp); } }, error(resp){ data.error(resp); } }); }, getinfo(context,data){ $.ajax({ url:"http://127.0.0.1:3000/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){ context.commit("logout"); } }, modules:{ },}
修改 UserAccountLoginView.vue
//触发函数 const login = ()=>{ error_message.value = ""; // 调用store 里actions的函数 // login 对应函数名 后面的对应 data store.dispatch("login",{ //用 ref 获取值要用value username:username.value, password:password.value, success(){ //成功后获取用户信息 store.dispatch("getinfo",{ success(){ router.push({name:'home'}); console.log(store.state.user); } }) }, error(){ error_message.value = "用户名或密码错误"; } }) }
把结果返回到右上角个人信息 – 修改导航栏
退出登陆
user.js 添加辅助函数
logout(state){ state.id = "", state.username = "", state.photo = "", state.token = "", state.is_login = false }
完成~