1. 用户模块(8081)
1.1 搭建环境
1.1.1 后端web服务:changgou4-service-web
l 修改pom.xml文档
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>changgou4-parent-ali</artifactId> <groupId>com.czxy.changgou</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>changgou4_service_web</artifactId> <dependencies> <!--自定义项目--> <dependency> <groupId>com.czxy.changgou</groupId> <artifactId>changgou4_common_db</artifactId> </dependency> <dependency> <groupId>com.czxy.changgou</groupId> <artifactId>changgou4_pojo</artifactId> </dependency> <!--web起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- nacos 客户端 --> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </dependency> <!-- nacos 服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> </dependencies> </project>
l 创建application.yml文档
#端口号 server: port: 8081 spring: application: name: web-service #服务名 datasource: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/changgou_db?useUnicode=true&characterEncoding=utf8 username: root password: 1234 druid: #druid 连接池配置 initial-size: 1 #初始化连接池大小 min-idle: 1 #最小连接数 max-active: 20 #最大连接数 test-on-borrow: true #获取连接时候验证,会影响性能 redis: database: 0 host: 127.0.0.1 port: 6379 cloud: nacos: discovery: server-addr: 127.0.0.1:8848 #nacos服务地址 sentinel: transport: dashboard: 127.0.0.1:8080
l 创建启动类
package com.czxy.changgou4; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; / * @author * @email */ @SpringBootApplication @EnableDiscoveryClient public class Web-serviceApplication { public static void main(String[] args) { SpringApplication.run( Web-serviceApplication.class , args ); } }
1.1.2 后端创建JavaBean:User
l 在changgou4-pojo项目中添加User对象
![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/7ae80b7958183f3857772a9121f9c6af.png)
package com.czxy.changgou4.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import java.beans.Transient; import java.util.Date; / 与数据库对应JavaBean * Created by liangtong. */ @TableName("tb_user") @Data @NoArgsConstructor @AllArgsConstructor public class User { /* CREATE TABLE `tb_user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Email', `mobile` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '手机号码', `username` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '昵称', `password` char(60) COLLATE utf8_unicode_ci NOT NULL COMMENT '密码', `face` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '头像', `expriece` int(10) unsigned DEFAULT '0' COMMENT '经验值', PRIMARY KEY (`id`), UNIQUE KEY `users_mobile_unique` (`mobile`), UNIQUE KEY `users_name_unique` (`name`), UNIQUE KEY `users_email_unique` (`email`) ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci */ @TableId(value="id",type = IdType.AUTO) private Long id; @TableField(value="username") private String username; @TableField(value="password") private String password; @TableField(value="face") private String face; @TableField(value="expriece") private Integer expriece; @TableField(value="email") private String email; @TableField(value="mobile") private String mobile; @TableField(value="created_at") private Date createdAt; @TableField(value="updated_at") private Date updatedAt; @TableField(exist = false) private String code; @TableField(exist = false) private String password_confirm; }
1.1.3 前端页面:创建公共组件
l 1)删除components目录下所有内容,并创建3个新组件
l 2)创建 TopNav.vue组件,用于配置“顶部导航”
<template> <!-- 顶部导航 start --> <div class="topnav"> <div class="topnav_bd w990 bc"> <div class="topnav_left"> </div> <div class="topnav_right fr"> <ul> <li>您好,欢迎来到畅购![<a href="login.html">登录</a>] [<a href="register.html">免费注册</a>] </li> <li class="line">|</li> <li>我的订单</li> <li class="line">|</li> <li>客户服务</li> </ul> </div> </div> </div> <!-- 顶部导航 end --> </template> <script> export default { } </script> <style> </style>
l 3)创建 HeaderLogo.vue组件,用于配置“页面头部,仅有LOGO”
![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/1c9fd567ca240f85346fac57434ea42e.png)
<template> <!-- 页面头部 start --> <div class="header w990 bc mt15"> <div class="logo w990"> <h2 class="fl"><a href="index.html"><img src="/images/logo.png" alt="畅购商城"></a></h2> </div> </div> <!-- 页面头部 end --> </template> <script> export default { } </script> <style> </style>
l 4)创建 Footer.vue组件,用于配置“底部版权”
![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/203c6aca21fd8ce4461724dd6a5859ba.png)
<template> <!-- 底部版权 start --> <div class="footer w1210 bc mt15"> <p class="links"> <a href="">关于我们</a> | <a href="">联系我们</a> | <a href="">人才招聘</a> | <a href="">商家入驻</a> | <a href="">千寻网</a> | <a href="">奢侈品网</a> | <a href="">广告服务</a> | <a href="">移动终端</a> | <a href="">友情链接</a> | <a href="">销售联盟</a> | <a href="">畅购论坛</a> </p> <p class="copyright"> © 2006-2020 畅购网上商城 版权所有,并保留所有权利。 ICP备案证书号:京ICP证070359号 </p> <p class="auth"> <a href=""><img src="/images/xin.png" alt="" /></a> <a href=""><img src="/images/kexin.jpg" alt="" /></a> <a href=""><img src="/images/police.jpg" alt="" /></a> <a href=""><img src="/images/beian.gif" alt="" /></a> </p> </div> <!-- 底部版权 end --> </template> <script> export default { } </script> <style> </style>
1.2 用户注册:用户名占用
1.2.1 接口
http://localhost:10010/web-service/user/checkusername { "username":"jack1" }
1.2.2 后端
l 创建三层需要的接口或类
l 步骤一:创建UserMapper,编写findByUsername()完成“通过用户名查询用户”
package com.czxy.changgou4.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.czxy.changgou4.pojo.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; / * Created by liangtong. */ @org.apache.ibatis.annotations.Mapper public interface UserMapper extends BaseMapper<User> { / * 通过用户名查询 * @param username * @return */ @Select("select * from tb_user where username = #{username}") User findByUsername(@Param("username") String username); }
l 步骤二:创建UserService接口,查询功能
package com.czxy.changgou4.service; import com.baomidou.mybatisplus.extension.service.IService; import com.czxy.changgou4.pojo.User; / * @author * @email */ public interface UserService extends IService<User> { / * 通过用户名查询 * @param username * @return */ public User findByUsername(String username); }
l 步骤三:创建UserServiceImpl实现类,查询功能
package com.czxy.changgou4.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.czxy.changgou4.mapper.UserMapper; import com.czxy.changgou4.pojo.User; import com.czxy.changgou4.service.UserService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; / * @author * @email */ @Service @Transactional public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Override public User findByUsername(String username) { return baseMapper.findByUsername(username); } }
l 步骤四:创建UserController,完成用户名检查
package com.czxy.changgou4.controller; import com.czxy.changgou4.pojo.User; import com.czxy.changgou4.service.UserService; import com.czxy.changgou4.vo.BaseResult; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; / * Created by liangtong. */ @RestController @RequestMapping("/user") public class UserController { @Resource private UserService userService; @PostMapping("/checkusername") public BaseResult checkUsername(@RequestBody User user){ //查询用户 User findUser = userService.findByUsername( user.getUsername() ); //判断 if(findUser != null){ return BaseResult.error("用户名已经存在"); } else { return BaseResult.ok("用户名可用"); } } }
1.2.3 前端
l 步骤一:创建Register.vue
l 步骤二:添加公共组件
<template> <div> <TopNav></TopNav> <div style="clear:both;"></div> <HeaderLogo></HeaderLogo> <div style="clear:both;"></div> <!-- 正文 --> <div style="clear:both;"></div> <Footer></Footer> </div> </template> <script> import TopNav from '../components/TopNav' import HeaderLogo from '../components/HeaderLogo' import Footer from '../components/Footer' export default { head: { title: '用户注册' }, components : { TopNav, HeaderLogo, Footer } } </script> <style> </style>
l 步骤三:编写注册表单,并导入独有样式
<template> <div> <TopNav></TopNav> <div style="clear:both;"></div> <HeaderLogo></HeaderLogo> <div style="clear:both;"></div> <!-- 正文 --> <!-- 登录主体部分start --> <div class="login w990 bc mt10 regist"> <div class="login_hd"> <h2>用户注册</h2> <b></b> </div> <div class="login_bd"> <div class="login_form fl"> <form action="" method="post"> <ul> <li> <label for="">用户名:</label> <input type="text" class="txt" name="username" /> <p>3-20位字符,可由中文、字母、数字和下划线组成</p> <p class="error">用户名已存在</p> </li> <li> <label for="">密码:</label> <input type="password" class="txt" name="password" /> <p>6-20位字符,可使用字母、数字和符号的组合,不建议使用纯数字、纯字母、纯符号</p> </li> <li> <label for="">确认密码:</label> <input type="password" class="txt" name="password" /> <p>请再次输入密码</p> </li> <li> <label for="">手机号码:</label> <input type="text" class="txt" name="mobile" /> <p>请输入手机号码</p> </li> <li class="checkcode"> <label for="">验证码:</label> <input type="text" name="checkcode" /> <button > 发送验证码<span>5秒</span> </button> </li> <li> <label for=""> </label> <input type="checkbox" class="chb" checked="checked" /> 我已阅读并同意《用户注册协议》 </li> <li> <label for=""> </label> <input type="submit" value="" class="login_btn" /> </li> </ul> </form> </div> <div class="mobile fl"> <h3>手机快速注册</h3> <p>中国大陆手机用户,编辑短信 “<strong>XX</strong>”发送到:</p> <p><strong>1069099988</strong></p> </div> </div> </div> <!-- 登录主体部分end --> <div style="clear:both;"></div> <Footer></Footer> </div> </template> <script> import TopNav from '../components/TopNav' import HeaderLogo from '../components/HeaderLogo' import Footer from '../components/Footer' export default { head: { title: '用户注册', link: [ {rel:'stylesheet',href:'style/login.css'} ], script: [ { type: 'text/javascript', src: 'js/header.js' }, { type: 'text/javascript', src: 'js/index.js' }, ] }, components : { TopNav, HeaderLogo, Footer } } </script> <style> </style>
l 步骤四:修改api.js,编写检查用户名的ajax函数
const request = { test : ()=> { return axios.get('/test') }, //检查用户名 checkUsername : ( username )=> { return axios.post('/web-service/user/checkusername', { username }) } }
l 步骤五:修改Register.vue页面,完成检查功能
n 发送ajax进行用户名是否可用检查
n 如果可用,显示对应信息,并使用success样式显示
n 如果不可用,显示对应信息,并使用error样式提示
<template> <div> <TopNav></TopNav> <div style="clear:both;"></div> <HeaderLogo></HeaderLogo> <div style="clear:both;"></div> <!-- 正文 --> <!-- 登录主体部分start --> <div class="login w990 bc mt10 regist"> <div class="login_hd"> <h2>用户注册</h2> <b></b> </div> <div class="login_bd"> <div class="login_form fl"> <form action="" method="post"> <ul> <li> <label for="">用户名:</label> <input type="text" class="txt" name="username" v-model="user.username" @blur="checkUsernameFn" /> <p>3-20位字符,可由中文、字母、数字和下划线组成</p> <p :class="userMsg.usernameData.code == 1 ? 'success' : 'error'">{{userMsg.usernameData.message}} </p> </li> <li> <label for="">密码:</label> <input type="password" class="txt" name="password" /> <p>6-20位字符,可使用字母、数字和符号的组合,不建议使用纯数字、纯字母、纯符号</p> </li> <li> <label for="">确认密码:</label> <input type="password" class="txt" name="password" /> <p>请再次输入密码</p> </li> <li> <label for="">手机号码:</label> <input type="text" class="txt" name="mobile" /> <p>请输入手机号码</p> </li> <li class="checkcode"> <label for="">验证码:</label> <input type="text" name="checkcode" /> <button > 发送验证码<span>5秒</span> </button> </li> <li> <label for=""> </label> <input type="checkbox" class="chb" checked="checked" /> 我已阅读并同意《用户注册协议》 </li> <li> <label for=""> </label> <input type="submit" value="" class="login_btn" /> </li> </ul> </form> </div> <div class="mobile fl"> <h3>手机快速注册</h3> <p>中国大陆手机用户,编辑短信 “<strong>XX</strong>”发送到:</p> <p><strong>1069099988</strong></p> </div> </div> </div> <!-- 登录主体部分end --> <div style="clear:both;"></div> <Footer></Footer> </div> </template> <script> import TopNav from '../components/TopNav' import HeaderLogo from '../components/HeaderLogo' import Footer from '../components/Footer' export default { head: { title: '用户注册', link: [ {rel:'stylesheet',href:'style/login.css'} ], script: [ { type: 'text/javascript', src: 'js/header.js' }, { type: 'text/javascript', src: 'js/index.js' }, ] }, components : { TopNav, HeaderLogo, Footer }, data() { return { user : { //表单绑定对象 username : "" }, userMsg : { //错误信息 usernameData : "" } } }, methods: { async checkUsernameFn() { let {data} = await this.$request.checkUsername( this.user.username ) this.userMsg.usernameData = data } }, } </script> <style> </style>
1.3 用户注册:手机号检查
1.3.1 接口
http://localhost:10010/web-service/user/checkmobile
{
"mobile":"13344445555"
}
1.3.2 后端
l 步骤一:修改UserService,添加 findByMobile() 方法,进行电话号码的查询
/ * 通过手机号查询 * @param mobile * @return */ User findByMobile(String mobile);
l 步骤二:编写UserServiceImpl,实现findByMobile() 方法
@Override public User findByMobile(String mobile) { // 拼凑条件 QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.eq("mobile", mobile); // 查询一个 List<User> list = baseMapper.selectList(queryWrapper); if(list.size() == 1) { return list.get(0); } return null; }
l 步骤三:修改UserController,添加checkMobile() 方法
/ * 通过手机号查询 * @param user * @return */ @PostMapping("/checkmobile") public BaseResult checkMobile(@RequestBody User user){ //查询用户 User findUser = userService.findByMobile( user.getMobile() ); //判断 if(findUser != null){ return BaseResult.error("电话号码已经注册"); } else { return BaseResult.ok("电话号码可用"); } }
1.3.3 前端
l 步骤一:修改api.js,添加 checkMobile() 函数
const request = { test : ()=> { return axios.get('/test') }, //检查用户名 checkUsername : ( username )=> { return axios.post('/web-service/user/checkusername', { username }) }, //检查电话号码 checkMobile : ( mobile )=> { return axios.post('/web-service/user/checkmobile', { mobile }) } }
l 步骤二:修改Register.vue,添加 checkMobileFn() 进行手机号检查
methods: { async checkUsernameFn() { //检查用户名 let {data} = await this.$request.checkUsername( this.user.username ) this.userMsg.usernameData = data }, async checkMobileFn() { //检查电话 let {data} = await this.$request.checkMobile( this.user.mobile ) this.userMsg.mobileData = data } },
l 步骤三:编写需要的2个变量
data() { return { user : { //表单封装数据 username : "", mobile : "" }, userMsg : { //错误提示数据 usernameData : "", mobileData : "" } } },
l 步骤四:处理页面
<li> <label for="">手机号码:</label> <input type="text" class="txt" name="mobile" v-model="user.mobile" @blur="checkMobileFn" /> <p>请输入手机号码</p> <p :class="userMsg.mobileData.code == 1 ? 'success' : 'error'">{{userMsg.mobileData.message}} </p> </li>
l 完整版
<template> <div> <TopNav></TopNav> <div style="clear:both;"></div> <HeaderLogo></HeaderLogo> <div style="clear:both;"></div> <!-- 正文 --> <!-- 登录主体部分start --> <div class="login w990 bc mt10 regist"> <div class="login_hd"> <h2>用户注册</h2> <b></b> </div> <div class="login_bd"> <div class="login_form fl"> <form action="" method="post"> <ul> <li> <label for="">用户名:</label> <input type="text" class="txt" name="username" v-model="user.username" @blur="checkUsernameFn" /> <p>3-20位字符,可由中文、字母、数字和下划线组成</p> <p :class="userMsg.usernameData.code == 1 ? 'success' : 'error'">{{userMsg.usernameData.message}} </p> </li> <li> <label for="">密码:</label> <input type="password" class="txt" name="password" /> <p>6-20位字符,可使用字母、数字和符号的组合,不建议使用纯数字、纯字母、纯符号</p> </li> <li> <label for="">确认密码:</label> <input type="password" class="txt" name="password" /> <p>请再次输入密码</p> </li> <li> <label for="">手机号码:</label> <input type="text" class="txt" name="mobile" v-model="user.mobile" @blur="checkMobileFn" /> <p>请输入手机号码</p> <p :class="userMsg.mobileData.code == 1 ? 'success' : 'error'">{{userMsg.mobileData.message}} </p> </li> <li class="checkcode"> <label for="">验证码:</label> <input type="text" name="checkcode" /> <button > 发送验证码<span>5秒</span> </button> </li> <li> <label for=""> </label> <input type="checkbox" class="chb" checked="checked" /> 我已阅读并同意《用户注册协议》 </li> <li> <label for=""> </label> <input type="submit" value="" class="login_btn" /> </li> </ul> </form> </div> <div class="mobile fl"> <h3>手机快速注册</h3> <p>中国大陆手机用户,编辑短信 “<strong>XX</strong>”发送到:</p> <p><strong>1069099988</strong></p> </div> </div> </div> <!-- 登录主体部分end --> <div style="clear:both;"></div> <Footer></Footer> </div> </template> <script> import TopNav from '../components/TopNav' import HeaderLogo from '../components/HeaderLogo' import Footer from '../components/Footer' export default { head: { title: '用户注册', link: [ {rel:'stylesheet',href:'style/login.css'} ], script: [ { type: 'text/javascript', src: 'js/header.js' }, { type: 'text/javascript', src: 'js/index.js' }, ] }, components : { TopNav, HeaderLogo, Footer }, data() { return { user : { //表单封装数据 username : "", mobile : "", password : "", code : "" }, userMsg : { //错误提示数据 usernameData : "", mobileData : "" } } }, methods: { async checkUsernameFn() { //检查用户名 let {data} = await this.$request.checkUsername( this.user.username ) this.userMsg.usernameData = data }, async checkMobileFn() { //检查电话 let {data} = await this.$request.checkMobile( this.user.mobile ) this.userMsg.mobileData = data } }, } </script> <style> </style>
1.4 用户注册:前置技术–Redis
1.5 用户注册:前置技术–阿里大鱼
1.6 用户注册:短信验证码
1.6.1 分析
1.6.2 接口
http://localhost:10010/web-service/sms
{
"mobile":"13344445555", "username": "jack"
}
1.6.3 后端
l 创建 SmsController类,调用阿里大鱼工具类,发送短信。
package com.czxy.changgou4.controller; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.czxy.changgou4.pojo.User; import com.czxy.changgou4.utils.SmsUtil; import com.czxy.changgou4.vo.BaseResult; import org.apache.commons.lang.RandomStringUtils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; / * Created by liangtong. */ @RestController @RequestMapping("/sms") public class SmsController { @Resource private StringRedisTemplate redisTemplate; @PostMapping public BaseResult sendSms(@RequestBody User user){ long start = System.currentTimeMillis(); try { //发送短信 //1 生产验证码 String code = RandomStringUtils.randomNumeric(4); System.out.println("验证码:" + code); //2 并存放到reids中 , key: "sms_register" + 手机号 , value:验证码 , 1小时 redisTemplate.opsForValue().set( "sms_register" + user.getMobile() , code , 1 , TimeUnit.HOURS); // //3 发送短信 SendSmsResponse smsResponse = SmsUtil.sendSms(user.getMobile(), user.getUsername() , code, "", ""); //https://help.aliyun.com/document_detail/55284.html?spm=5176.doc55322.6.557.KvvIJx if("OK".equalsIgnoreCase(smsResponse.getCode())){ return BaseResult.ok("发送成功"); } else { return BaseResult.error(smsResponse.getMessage()); } /* //模拟数据 System.out.println("验证码:" + code); return BaseResult.ok("发送成功"); */ } catch (Exception e) { long end = System.currentTimeMillis(); System.out.println( end - start); return BaseResult.error("发送失败" ); } } }
1.6.4 前端
l 步骤一:修改apiclient.js,发送短信ajax操作
//发短信 sendSms : ( user )=> { return axios.post('/web-service/sms', user ) }
l 步骤二:修改Register.vue页面,给“发送验证码”绑定点击事件 sendSmsFn
<button @click.prevent="sendSmsFn" > 发送验证码<span>5秒</span> </button>
l 步骤三:修改Register.vue页面,编写sendSmsFn函数,建议采用 ajax…then()…catch 可以处理异常
sendSmsFn () { this.$request.sendSms( this.user ) .then(( response )=>{ //发送短信的提示信息 this.userMsg.smsData = response.data }) .catch(( error )=>{ //错误提示信息 alert( error.message ) }) }
l 步骤四:修改Register.vue页面,提供变量smsData
userMsg : { //错误提示数据 usernameData : "", mobileData : "", smsData : "" }
l 步骤五:修改Register.vue页面,显示 smsData提示信息
<p :class="userMsg.smsData.code == 1 ? 'success' : 'error'">{{userMsg.smsData.message}} </p>
1.6.5 倒计时
l 步骤一:提供3个变量,用于控制倒计时
btnDisabled : false, //倒计时控制变量 seconds : 5, //默认倒计时秒数 timer : null, //接收定时器,清除定时器
l 步骤二:在标签上面控制倒计时的显示
<button :disabled="btnDisabled" @click.prevent="sendSmsFn" > 发送验证码<span v-show="btnDisabled">{{seconds}}秒</span> </button>
l 步骤三:发送短信后,开启倒计时控制
sendSmsFn () { this.$request.sendSms( this.user ) .then(( response )=>{ //发送短信的提示信息 this.userMsg.smsData = response.data //按钮不可用 this.btnDisabled = true; //倒计时 this.timer = setInterval( ()=>{ if(this.seconds <= 1){ //结束 // 重置秒数 this.seconds = 5; // 按钮可用 this.btnDisabled = false; // 停止定时器 clearInterval(this.timer); } else { this.seconds --; } } , 1000); }) .catch(( error )=>{ //错误提示信息 alert( error.message ) }) }
1.7 用户注册
1.7.1 接口
http://localhost:10010/web-service/user/register
{
"mobile":"13612345677", "password":"1234", "username":"jack3", "code":"3919"
}
1.7.2 后端
l 保存前需要再次进行服务端校验
n 用户名是否注册
n 手机号是否注册
n 验证码是否失效
n 验证码是否错误
l 密码需要使用 BCrypt进行加密
l 步骤一:修改UserService接口,添加register方法
/ * 用户注册 * @param user * @return */ public boolean register(User user) ;
l 步骤二:完善UserServiceImpl实现类
@Override public boolean register(User user) { //密码加密 String newPassword = BCrypt.hashpw(user.getPassword()); user.setPassword(newPassword); //处理数据 user.setCreatedAt(new Date()); user.setUpdatedAt(user.getCreatedAt()); int insert = baseMapper.insert(user); return insert == 1; }
l 步骤三:修改UserController,添加register方法
/ * 用户注册 * @param user * @return */ @PostMapping("/register") public BaseResult register(@RequestBody User user){ //服务端校验 User findUser = userService.findByUsername(user.getUsername()); if(findUser != null) { return BaseResult.error("用户名已经存在"); } findUser = userService.findByMobile(user.getMobile()); if(findUser != null) { return BaseResult.error("电话号码已经存在"); } //验证码 String code = stringRedisTemplate.opsForValue().get("sms_register" + user.getMobile()); //删除redis中的验证码 stringRedisTemplate.delete("sms_register" + user.getMobile()); if(code == null) { return BaseResult.error("验证码失效"); } if(!code.equals(user.getCode())) { return BaseResult.error("验证码不正确"); } //注册 boolean register = userService.register(user); if(register) { return BaseResult.ok("注册成功"); } return BaseResult.error("注册失败"); }
1.7.3 日期处理(可选)
l 编写 DateMetaObjectHandler 用于处理“创建时间”和“修改日期”
package com.czxy.changgou4.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.util.Date; / * @author * @email */ @Component public class DateMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("createdAt", new Date(), metaObject); this.setFieldValByName("updatedAt", new Date(), metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updatedAt", new Date(), metaObject); } }
l 完善User JavaBean,设置填充方式
@TableField(value="created_at",fill = FieldFill.INSERT) private Date createdAt; @TableField(value="updated_at",fill = FieldFill.INSERT_UPDATE) private Date updatedAt;
1.7.4 前端
l 步骤一:修改 api.js ,添加注册函数
//注册 register : ( user )=> { return axios.post('/web-service/user/register', user ) }
l 步骤二:处理表单,验证码input绑定数据,提交按钮绑定事件
<li class="checkcode"> <label for="">验证码:</label> <input type="text" name="checkcode" v-model="user.code" /> <button :disabled="btnDisabled" @click.prevent="sendSmsFn" > 发送验证码<span v-show="btnDisabled">{{seconds}}秒</span> </button> <p :class="userMsg.smsData.code == 1 ? 'success' : 'error'">{{userMsg.smsData.message}} </p> </li> <li> <label for=""> </label> <input type="checkbox" class="chb" checked="checked" /> 我已阅读并同意《用户注册协议》 </li> <li> <label for=""> </label> <input type="submit" value="" @click.prevent="registerFn" class="login_btn" /> </li>
l 步骤三:完善data区域的user数据
user : { //表单封装数据 username : "", //用户名 mobile : "13699282444", //手机号 password : "", //密码 code : "" //验证码 },
l 步骤四:编写registerFn函数
async registerFn() { let { data } = await this.$request.register( this.user ) if( data.code == 20000) { //成功 this.$router.push('/login') } else { //失败--与发送验证码使用一个位置显示错误信息 this.userMsg.smsData = data } }
1.8 用户登录
1.8.1 构建页面:Login.vue
l 步骤一:创建Login.vue
l 步骤二:绘制通用模块
<template> <div> <TopNav></TopNav> <div style="clear:both;"></div> <HeaderLogo></HeaderLogo> <div style="clear:both;"></div> <!-- 正文 --> <div style="clear:both;"></div> <Footer></Footer> </div> </template> <script> import TopNav from '../components/TopNav' import HeaderLogo from '../components/HeaderLogo' import Footer from '../components/Footer' export default { head: { title: '用户登录', link: [ {rel:'stylesheet',href:'style/login.css'} ], script: [ ] }, components : { TopNav, HeaderLogo, Footer }, } </script> <style> </style
l 步骤三:绘制登录表单
<template> <div> <TopNav></TopNav> <div style="clear:both;"></div> <HeaderLogo></HeaderLogo> <div style="clear:both;"></div> <!-- 正文 --> <!-- 登录主体部分start --> <div class="login w990 bc mt10"> <div class="login_hd"> <h2>用户登录</h2> <b></b> </div> <div class="login_bd"> <div class="login_form fl"> <form action="" method="post"> <ul> <li> <label for="">用户名:</label> <input type="text" class="txt" name="username" /> </li> <li> <label for="">密码:</label> <input type="password" class="txt" name="password" /> <a href="">忘记密码?</a> </li> <li class="checkcode"> <label for="">验证码:</label> <input type="text" name="checkcode" /> <img src="images/checkcode1.jpg" alt="" /> <span>看不清?<a href="">换一张</a></span> </li> <li> <label for=""> </label> <input type="checkbox" class="chb" /> 保存登录信息 </li> <li> <label for=""> </label> <input type="submit" value="" class="login_btn" /> </li> </ul> </form> <div class="coagent mt15"> <dl> <dt>使用合作网站登录商城:</dt> <dd class="qq"><a href=""><span></span>QQ</a></dd> <dd class="weibo"><a href=""><span></span>新浪微博</a></dd> <dd class="yi"><a href=""><span></span>网易</a></dd> <dd class="renren"><a href=""><span></span>人人</a></dd> <dd class="qihu"><a href=""><span></span>奇虎360</a></dd> <dd class=""><a href=""><span></span>百度</a></dd> <dd class="douban"><a href=""><span></span>豆瓣</a></dd> </dl> </div> </div> <div class="guide fl"> <h3>还不是商城用户</h3> <p>现在免费注册成为商城用户,便能立刻享受便宜又放心的购物乐趣,心动不如行动,赶紧加入吧!</p> <a href="regist.html" class="reg_btn">免费注册 >></a> </div> </div> </div> <!-- 登录主体部分end --> <div style="clear:both;"></div> <Footer></Footer> </div> </template> <script> import TopNav from '../components/TopNav' import HeaderLogo from '../components/HeaderLogo' import Footer from '../components/Footer' export default { head: { title: '用户登录', link: [ {rel:'stylesheet',href:'style/login.css'} ], script: [ ] }, components : { TopNav, HeaderLogo, Footer }, } </script> <style> </style>
1.8.2 分析
1.8.3 验证码:接口
http://localhost:10010/web-service/verifycode?username=jack
1.8.4 验证码:生成与显示
l 步骤一:后端生产验证码,并将用户保存Redis
n 存放redis中验证码key格式:“login” + 用户名
package com.czxy.changgou4.controller; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletResponse; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Random; import java.util.concurrent.TimeUnit; / * @author * @email */ @Controller @RequestMapping("/verifycode") public class VerifyCodeController { @Resource private StringRedisTemplate stringRedisTemplate; @GetMapping public void verifyCode(String username , HttpServletResponse response ) throws IOException { //字体只显示大写,去掉了1,0,i,o几个容易混淆的字符 String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; int IMG_WIDTH = 72; int IMG_HEIGTH = 27; Random random = new Random(); //创建图片 BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGTH, BufferedImage.TYPE_INT_RGB); //画板 Graphics g = image.getGraphics(); //填充背景 g.setColor(Color.WHITE); g.fillRect(1,1,IMG_WIDTH-2,IMG_HEIGTH-2); g.setFont(new Font("楷体", Font.BOLD,25)); StringBuilder sb = new StringBuilder(); //写字 for(int i = 1 ; i <= 4 ; i ++){ //随机颜色 g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255))); int len = random.nextInt(VERIFY_CODES.length()); String str = VERIFY_CODES.substring(len,len+1); sb.append(str); g.drawString(str, IMG_WIDTH / 6 * i , 22 ); } //将验证码存放到redis stringRedisTemplate.opsForValue().set( "login" + username , sb.toString() , 1 , TimeUnit.HOURS); // 生成随机干扰线 for (int i = 0; i < 30; i++) { //随机颜色 g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255))); int x = random.nextInt(IMG_WIDTH - 1); int y = random.nextInt(IMG_HEIGTH - 1); int x1 = random.nextInt(12) + 1; int y1 = random.nextInt(6) + 1; g.drawLine(x, y, x - x1, y - y1); } //响应到浏览器 ImageIO.write(image,"jpeg", response.getOutputStream()); } }
l 步骤二:点击“换一张”显示验证码
n 默认不显示验证码
n 点击“换一张”获得验证码
<template> <div> <TopNav></TopNav> <div style="clear:both;"></div> <HeaderLogo></HeaderLogo> <div style="clear:both;"></div> <!-- 正文 --> <!-- 登录主体部分start --> <div class="login w990 bc mt10"> <div class="login_hd"> <h2>用户登录</h2> <b></b> </div> <div class="login_bd"> <div class="login_form fl"> <form action="" method="post"> <ul> <li> <label for="">用户名:</label> <input type="text" class="txt" name="username" v-model="user.username" /> </li> <li> <label for="">密码:</label> <input type="password" class="txt" name="password" v-model="user.password" /> <a href="">忘记密码?</a> </li> <li class="checkcode"> <label for="">验证码:</label> <input type="text" name="checkcode" /> <!-- <img src="images/checkcode1.jpg" alt="" /> --> <img :src="imgSrc" alt="" /> <span>看不清?<a href="" @click.prevent="changeVerifyCode">换一张</a></span> </li> <li v-if="errorMsg != ''"> <label for=""> </label> <span style="color: #ff5b5b">{{errorMsg}}</span> </li> <li> <label for=""> </label> <input type="checkbox" class="chb" /> 保存登录信息 </li> <li> <label for=""> </label> <input type="submit" value="" class="login_btn" /> </li> </ul> </form> <div class="coagent mt15"> <dl> <dt>使用合作网站登录商城:</dt> <dd class="qq"><a href=""><span></span>QQ</a></dd> <dd class="weibo"><a href=""><span></span>新浪微博</a></dd> <dd class="yi"><a href=""><span></span>网易</a></dd> <dd class="renren"><a href=""><span></span>人人</a></dd> <dd class="qihu"><a href=""><span></span>奇虎360</a></dd> <dd class=""><a href=""><span></span>百度</a></dd> <dd class="douban"><a href=""><span></span>豆瓣</a></dd> </dl> </div> </div> <div class="guide fl"> <h3>还不是商城用户</h3> <p>现在免费注册成为商城用户,便能立刻享受便宜又放心的购物乐趣,心动不如行动,赶紧加入吧!</p> <a href="regist.html" class="reg_btn">免费注册 >></a> </div> </div> </div> <!-- 登录主体部分end --> <div style="clear:both;"></div> <Footer></Footer> </div> </template> <script> import TopNav from '../components/TopNav' import HeaderLogo from '../components/HeaderLogo' import Footer from '../components/Footer' export default { head: { title: '用户登录', link: [ {rel:'stylesheet',href:'style/login.css'} ], script: [ ] }, components : { TopNav, HeaderLogo, Footer }, data() { return { imgSrc:'', errorMsg: '', user: { } } }, methods: { changeVerifyCode() { if( this.user.username ) { this.imgSrc = `http://localhost:10010/web-service/verifycode?t=${new Date().getTime()}&username=${this.user.username }` } else { this.errorMsg = '用户名不能为空' } } }, watch: { 'user' : { handler(v) { if(v) { //如果user数据发生改变,修改提示信息 this.errorMsg = '' } }, deep: true } }, } </script> <style> </style>
1.8.5 通过用户名查询:接口
http://localhost:10010/web-service/user/findByUsername { "username":"jack" }
1.8.6 通过用户名查询:实现
l 修改UserController,添加 findByUsername函数
/ * 通过用户名查询 * @param user * @return 返回用户对象 */ @PostMapping("/findByUsername") public User findByUsername(@RequestBody User user){ //查询用户 User findUser = userService.findByUsername( user.getUsername() ); return findUser; }
1.8.7 认证服务:构建项目(changgou4-service-auth)
l 步骤一:构建项目
l 步骤二:创建pom.xml文件
<dependencies> <!--自定义项目--> <dependency> <groupId>com.czxy.changgou</groupId> <artifactId>changgou4_common_auth</artifactId> </dependency> <!--web起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- nacos 客户端 --> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </dependency> <!-- nacos 服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> </dependencies>
l 步骤三:创建yml文件
server: port: 8085 spring: application: name: auth-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 #nacos服务地址 sc: jwt: secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥 pubKeyPath: D:/rsa/rsa.pub # 公钥地址 priKeyPath: D:/rsa/rsa.pri # 私钥地址 expire: 360 # 过期时间,单位分钟
l 步骤四:配置启动类
package com.czxy.changgou4; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; / * @author * @email */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class CGAuthServiceApplication { public static void main(String[] args) { SpringApplication.run(CGAuthServiceApplication.class, args); } }
l 步骤五:配置类
1.8.8 认证服务:用户登录后端
l 步骤一:创建AuthUser 封装对象(与User比较,缺数据库相关注解)
package com.czxy.changgou4.domain; import lombok.Data; import java.util.Date; / * @author * @email */ @Data public class AuthUser { private Long id; private String username; private String password; private String face; private Integer expriece; private String email; private String mobile; private Date createdAt; private Date updatedAt; private String code; private String password_confirm; }
l 步骤二:创建UserFeign,完成远程用户查询功能
package com.czxy.changgou4.feign; import com.czxy.changgou4.domain.AuthUser; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; / * @author * @email */ @FeignClient(value = "web-service",path="/user") public interface UserFeign { @PostMapping("/findByUsername") public AuthUser findByUsername(@RequestBody AuthUser user); }
l 步骤三:创建AuthService接口,编写登录方法 login()
package com.czxy.changgou4.service; import com.czxy.changgou4.domain.AuthUser; / * @author * @email */ public interface AuthService { / * 用户登录 * @param user * @return */ public AuthUser login(AuthUser user ) ; }
l 步骤四:创建AuthService实现类,并通过BCrypt校验密码
package com.czxy.changgou4.service.impl; import com.czxy.changgou4.domain.AuthUser; import com.czxy.changgou4.feign.UserFeign; import com.czxy.changgou4.service.AuthService; import com.czxy.changgou4.utils.BCrypt; import org.springframework.stereotype.Service; import javax.annotation.Resource; / * @author * @email */ @Service public class AuthServiceImpl implements AuthService { @Resource private UserFeign userFeign; / * 用户登录 * @param user * @return */ public AuthUser login(AuthUser user ) { //远程查询用户 AuthUser findUser = userFeign.findByUsername(user); if(findUser == null) { return null; } //校验密码是否正确 boolean checkpw = BCrypt.checkpw( user.getPassword(), findUser.getPassword()); if(checkpw){ return findUser; } return null; } }
l 步骤五:创建AuthController,添加login方法
n redis中登录验证码和用户输入的验证码进行匹配
package com.czxy.changgou4.controller; / * @author * @email */ import com.czxy.changgou4.domain.AuthUser; import com.czxy.changgou4.service.AuthService; import com.czxy.changgou4.vo.BaseResult; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; / * Created by liangtong. */ @RestController @RequestMapping("/auth") public class AuthController { @Resource private AuthService authService; @Resource private StringRedisTemplate stringRedisTemplate; @PostMapping("/login") public BaseResult login(@RequestBody AuthUser user){ //校验验证码--使用后删除 String redisCode = stringRedisTemplate.opsForValue().get( "login" + user.getUsername() ); stringRedisTemplate.delete( "login" + user.getUsername() ); if(redisCode == null) { return BaseResult.error("验证码无效"); } if(! redisCode.equalsIgnoreCase(user.getCode())) { return BaseResult.error("验证码错误"); } //登录 AuthUser loginUser = authService.login(user); if(loginUser != null ) { return BaseResult.ok("登录成功").append("loginUser",loginUser); } else { return BaseResult.error("用户名或密码不匹配"); } } }
1.8.9 认证服务:用户登录前端
l 步骤一:修改apiclient.js,添加login函数
//登录
//登录 login : ( user )=> { return axios.post('/auth-service/auth/login', user ) }
l 步骤二:修改Login.vue,给验证码绑定变量
<li class="checkcode"> <label for="">验证码:</label> <input type="text" name="checkcode" v-model="user.code" /> <!-- <img src="images/checkcode1.jpg" alt="" /> -->
l 步骤三:修改Login.vue,给提交按钮绑定事件
<li> <label for=""> </label> <input type="submit" value="" @click.prevent="loginFn" class="login_btn" /> </li>
l 步骤四:编写loginFn完成登录功能
n 登录成功,跳转到首页
n 登录失败,给出提示
async loginFn() { let { data } = await this.$request.login( this.user ) if( data.code == 20000) { //成功 sessionStorage.setItem('user' , JSON.stringify(data.other.loginUser) ) //跳转到首页 this.$router.push('/') } else { this.errorMsg = data.message } }
l 步骤五:创建首页 ~/pages/index.vue,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TkXlK1om-1641474467247)(C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps2061.tmp.jpg)]
<template> <div> <TopNav></TopNav> </div> </template> <script> import TopNav from '../components/TopNav' export default { head: { title: '首页', link: [ {rel:'stylesheet',href:'style/index.css'}, {rel:'stylesheet',href:'style/bottomnav.css'} ], script: [ { type: 'text/javascript', src: 'js/header.js' }, { type: 'text/javascript', src: 'js/index.js' }, ] }, components : { TopNav, }, } </script> <style> </style>
l 步骤六:重命名静态页面 ~/static/index.html 为 ~/static/home.html
1.8.10 修改 TopNav.vue 组件
l 完善导航条,根据vuex中的数据,显示不同内容
![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/6e43cd03da6fa0bb28cc91542de892a9.png) ![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/770236d68dc0bae4dc45e8e381e28b11.png)
l 步骤一:创建 ~/store/index.js ,并编写vuex内容
![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/6b18ca10a56849ad84777ad61cb30e5f.png)
export const state = () => ({ user: null }) //通用设置 export const mutations = { setData( state , obj) { state[obj.key] = obj.value } }
l 步骤二:页面登录成功,将用户信息保存到vuex中
// 将用户信息保存到vuex中 this.$store.commit('setData', {key:'user',value: data.data })
l 步骤三:修改顶部导航TopNav.vue
n 从vuex中的数据
<template> <!-- 顶部导航 start --> <div class="topnav"> <div class="topnav_bd w990 bc"> <div class="topnav_left"> </div> <div class="topnav_right fr"> <ul> <li v-if="user != null">您好,{{user.username}} 欢迎来到畅购! <a href="" @click.prevent="logout">退出</a></li> <li v-if="user != null" class="line">|</li> <li v-if="user == null">[<a href="/login">登录</a>] [<a href="/register">免费注册</a>] </li> <li v-if="user == null" class="line">|</li> <li v-if="user != null">我的订单</li> <li v-if="user != null" class="line">|</li> <li>客户服务</li> </ul> </div> </div> </div> <!-- 顶部导航 end --> </template> <script> import {mapState,mapMutations} from 'vuex' export default { mounted() { let userStr = sessionStorage.getItem('user') if(userStr){ // 将string数据转换object,并填充到vuex中 this.setData({key:'user',value: JSON.parse(userStr)}) } }, methods: { logout() { sessionStorage.removeItem('user') sessionStorage.removeItem('token') //设置vuex中的数据为空 this.setData({key:'user',value: null }) this.$router.push('/login') }, ...mapMutations(['setData']) }, computed: { ...mapState(['user']) }, } </script> <style> </style>
1.8.11 vuex刷新数据丢失
l 刷新操作:
n 点击刷新按钮
n 点击回退按钮
n 地址栏直接输入地址
l 现象:
n vuex在刷新操作后,数据丢失了
l 解决方案
n 方案1:不是公共组件:页面在pages目录下,可以nuxt.js提供 fetch进行操作。
n 方案2:是公共组件:组件在components目录下,借助第三方进行存储(cookie、localStorage、sessionStorage)
l 具体操作:
n 如果vuex中没有数据,使用sessionStorage的数据填充vuex。
n 修改TopNav.vue页面
<template> <!-- 顶部导航 start --> <div class="topnav"> <div class="topnav_bd w990 bc"> <div class="topnav_left"> </div> <div class="topnav_right fr"> <ul> <li v-if="user!=null">您好,{{user.username}}欢迎来到畅购! </li> <li v-if="user==null"> [<nuxt-link to="/Login">登录</nuxt-link>] </li> <li v-if="user==null"> [<nuxt-link to="/Register">免费注册</nuxt-link>] </li> <li v-if="user!=null"> [<nuxt-link to="/Login">退出</nuxt-link>] </li> <li v-if="user!=null" class="line">|</li> <li v-if="user!=null">我的订单</li> <li class="line">|</li> <li>客户服务</li> </ul> </div> </div> </div> <!-- 顶部导航 end --> </template> <script> import {mapState, mapMutations} from 'vuex' export default { computed: { //计算属性整合vuex // user() { // return this.$store.state.user // } ...mapState(['user']) }, mounted() { // 从sessionStorage获得信息,如果存在,直接添加vuex中 let userStr = sessionStorage.getItem('user') if(userStr) { let user = JSON.parse(userStr) //将数据存放到vuex中 this.setData({key:'user',value: user }) } }, methods: { ...mapMutations(['setData']), }, } </script> <style> </style>
1.9 整合JWT
l 生成token:在用户登录成功,根据用户的登录信息,生成登录标识token,并返回给浏览器。
l 使用token:完善ajax请求,在请求之前添加请求头,设置token
l 校验token:在网关中编写过滤器,进行请求进行拦截,并校验token。
l 白名单:在白名单中的请求,是不需要token可以直接访问的。
1.9.1 生成Token
l 用户登录成功,生成token,并将token响应给浏览器。(认证服务 AuthService)
l 步骤一:查看 application.yml文件,确定 jwt配置信息
l 步骤二:创建JwtProperties文件,用于加载sc.jwt配置信息
package com.czxy.changgou4.config; import com.czxy.changgou4.utils.RsaUtils; import lombok.Data; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.File; import java.security.PrivateKey; import java.security.PublicKey; / * @author * @email */ @Data @ConfigurationProperties(prefix = "sc.jwt") @Component public class JwtProperties { private String secret; // 密钥 private String pubKeyPath;// 公钥 private String priKeyPath;// 私钥 private int expire;// token过期时间 private PublicKey publicKey; // 公钥 private PrivateKey privateKey; // 私钥 private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); @PostConstruct public void init(){ try { File pubFile = new File(this.pubKeyPath); File priFile = new File(this.priKeyPath); if( !pubFile.exists() || !priFile.exists()){ RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret); } this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath ); this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath ); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } }
l 步骤三:修改AuthController,注入JwtProperties,并使用JwtUtils生成token
package com.czxy.changgou4.controller; / * @author * @email */ import com.czxy.changgou4.config.JwtProperties; import com.czxy.changgou4.domain.AuthUser; import com.czxy.changgou4.service.AuthService; import com.czxy.changgou4.utils.JwtUtils; import com.czxy.changgou4.vo.BaseResult; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; / * Created by liangtong. */ @RestController @RequestMapping("/auth") public class AuthController { @Resource private AuthService authService; @Resource private StringRedisTemplate stringRedisTemplate; @Resource private JwtProperties jwtProperties; @PostMapping("/login") public BaseResult login(@RequestBody AuthUser user){ //校验验证码--使用后删除 String redisCode = stringRedisTemplate.opsForValue().get( "login" + user.getUsername() ); stringRedisTemplate.delete( "login" + user.getUsername() ); if(redisCode == null) { return BaseResult.error("验证码无效"); } if(! redisCode.equalsIgnoreCase(user.getCode())) { return BaseResult.error("验证码错误"); } //登录 AuthUser loginUser = authService.login(user); if(loginUser != null ) { //生成Token String token = JwtUtils.generateToken(loginUser, jwtProperties.getExpire(), jwtProperties.getPrivateKey()); return BaseResult.ok("登录成功").append("loginUser",loginUser).append("token", token); } else { return BaseResult.error("用户名或密码不匹配"); } } }
1.9.2 使用token
l 步骤一:登录成功后保存token,修改 Login.vue页面
async loginFn() { let { data } = await this.$request.login( this.user ) if( data.code == 20000) { //成功 sessionStorage.setItem('user' , JSON.stringify(data.other.loginUser) ) //保存token sessionStorage.setItem('token' , data.other.token ) //跳转到首页 this.$router.push('/') } else { this.errorMsg = data.message } }
l 步骤二:请求是自动携带token,修改apiclient.js,将token添加到请求头
//参考 https://axios.nuxtjs.org/helpers let token = sessionStorage.getItem('token') if( token ) { // Adds header: `Authorization: 123` to all requests // this.$axios.setToken('123') $axios.setToken( token ) }
l 步骤三:检查 nuxt.conf.js,插件模式改成“client”
n 否则抛异常“sessionStorage is not defined”
plugins: [ { src: '~plugins/apiclient.js', mode: 'client' } ],
1.9.3 校验token
l token的校验在网关项目处完成
l 步骤一:修改application.yml添加jwt配置
#自定义内容 sc: jwt: secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥 pubKeyPath: D:/rsa/rsa.pub # 公钥地址 priKeyPath: D:/rsa/rsa.pri # 私钥地址 expire: 360 # 过期时间,单位分钟
l 步骤二:创建 JwtProperties,用于加载配置文件
![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/49c42c373141ea478f5c9b7fd920e26d.png)
package com.czxy.changgou4.config; import com.czxy.changgou4.utils.RsaUtils; import lombok.Data; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.File; import java.security.PrivateKey; import java.security.PublicKey; / * @author * @email */ @Data @ConfigurationProperties(prefix = "sc.jwt") public class JwtProperties { private String secret; // 密钥 private String pubKeyPath;// 公钥 private String priKeyPath;// 私钥 private int expire;// token过期时间 private PublicKey publicKey; // 公钥 private PrivateKey privateKey; // 私钥 private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); @PostConstruct public void init(){ try { File pubFile = new File(this.pubKeyPath); File priFile = new File(this.priKeyPath); if( !pubFile.exists() || !priFile.exists()){ RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret); } this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath ); this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath ); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } }
l 步骤三:编写过滤器,对所有路径进行拦截
package com.czxy.changgou4.filter; import com.czxy.changgou4.config.FilterProperties; import com.czxy.changgou4.config.JwtProperties; import com.czxy.changgou4.pojo.User; import com.czxy.changgou4.utils.JwtUtils; import com.czxy.changgou4.utils.RsaUtils; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.nio.charset.StandardCharsets; / * @author * @email */ @Component public class LoginFilter implements GlobalFilter, Ordered { @Resource private JwtProperties jwtProperties; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1 获得请求路径 ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); System.out.println(path); //2 白名单放行 //3 获得token String token = request.getHeaders().getFirst("Authorization"); //4 校验token try { JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class); return chain.filter(exchange); } catch (Exception e) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().add("Content-Type","application/json;charset=UTF-8"); DataBuffer wrap = response.bufferFactory().wrap("没有权限".getBytes(StandardCharsets.UTF_8)); return exchange.getResponse().writeWith(Flux.just(wrap)); } } @Override public int getOrder() { return 1; } }
l 步骤四:修改前端 apiclient.js 文件,用于处理401异常
//处理响应异常 $axios.onError(error => { // token失效,服务器响应401 if(error.response.status === 401) { console.error(error.response.data) redirect('/login') } })
l api.js 完整代码
var axios = null export default ({ $axios, redirect, process }, inject) => { //参考 https://axios.nuxtjs.org/helpers let token = sessionStorage.getItem('token') if( token ) { // Adds header: `Authorization: 123` to all requests // this.$axios.setToken('123') $axios.setToken( token ) } //处理响应异常 $axios.onError(error => { // token失效,服务器响应401 if(error.response.status === 401) { console.error(error.response.data) redirect('/login') } }) //赋值 axios = $axios //4) 将自定义函数交于nuxt // 使用方式1:在vue中,this.$request.xxx() // 使用方式2:在nuxt的asyncData中,content.app.$request.xxx() inject('request', request) }
1.9.4 白名单
l 不需要拦截的资源都配置到yml文件中,在过滤器直接放行
l 步骤一:修改application.yml文件
![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/f1c8d3621336acef6bf6c88da44d8366.png)
#自定义内容 sc: jwt: secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥 pubKeyPath: D:/rsa/rsa.pub # 公钥地址 priKeyPath: D:/rsa/rsa.pri # 私钥地址 expire: 360 # 过期时间,单位分钟 filter: allowPaths: - /checkusername - /checkmobile - /sms - /register - /login - /verifycode - /categorys - /news - /brands - /specifications - /search - /goods - /comments - swagger - /api-docs
l 步骤二:创建FilterProperties配置文件,用于存放允许放行的路径
![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/825cfaf44043d33945317a6fae573c41.png)
package com.czxy.changgou4.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.List; / * @author * @email */ @Data @ConfigurationProperties(prefix="sc.filter") public class FilterProperties { //允许访问路径集合 private List<String> allowPaths; }
l 步骤三:修改 LoginFilter,放行名单中配置的路径
package com.czxy.changgou4.filter; import com.czxy.changgou4.config.FilterProperties; import com.czxy.changgou4.config.JwtProperties; import com.czxy.changgou4.pojo.User; import com.czxy.changgou4.utils.JwtUtils; import com.czxy.changgou4.utils.RsaUtils; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.nio.charset.StandardCharsets; / * @author * @email */ @Component //2.1 加载JWT配置类 @EnableConfigurationProperties({FilterProperties.class} ) //加载配置类 public class LoginFilter implements GlobalFilter, Ordered { @Resource private FilterProperties filterProperties; @Resource private JwtProperties jwtProperties; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1 获得请求路径 ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); System.out.println(path); //2 白名单放行 for (String allowPath : filterProperties.getAllowPaths()) { //判断包含 if(path.contains(allowPath)){ return chain.filter(exchange); } } //3 获得token String token = request.getHeaders().getFirst("Authorization"); //4 校验token try { JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class); return chain.filter(exchange); } catch (Exception e) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().add("Content-Type","application/json;charset=UTF-8"); DataBuffer wrap = response.bufferFactory().wrap("没有权限".getBytes(StandardCharsets.UTF_8)); return exchange.getResponse().writeWith(Flux.just(wrap)); } } @Override public int getOrder() { return 1; } }