电商4.0项目【三】: 用户模块(8081)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 电商4.0项目【三】: 用户模块(8081)

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="">&nbsp;</label>
              <input type="checkbox" class="chb" checked="checked" /> 我已阅读并同意《用户注册协议》
            </li>
            <li>
              <label for="">&nbsp;</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="">&nbsp;</label>
              <input type="checkbox" class="chb" checked="checked" /> 我已阅读并同意《用户注册协议》
            </li>
            <li>
              <label for="">&nbsp;</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="">&nbsp;</label>
              <input type="checkbox" class="chb" checked="checked" /> 我已阅读并同意《用户注册协议》
            </li>
            <li>
              <label for="">&nbsp;</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="">&nbsp;</label>
              <input type="checkbox" class="chb" checked="checked" /> 我已阅读并同意《用户注册协议》
            </li>
            <li>
              <label for="">&nbsp;</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="">&nbsp;</label>
                <input type="checkbox" class="chb"  /> 保存登录信息
              </li>
              <li>
                <label for="">&nbsp;</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="">&nbsp;</label>
                <span style="color: #ff5b5b">{{errorMsg}}</span>
              </li>
              <li>
                <label for="">&nbsp;</label>
                <input type="checkbox" class="chb"  /> 保存登录信息
              </li>
              <li>
                <label for="">&nbsp;</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="">&nbsp;</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;
    }
}



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
6月前
|
小程序
电商小程序06用户审核
电商小程序06用户审核
13分布式电商项目 - 品牌管理模块
13分布式电商项目 - 品牌管理模块
42 0
13分布式电商项目 - 品牌管理模块
|
SQL
淘东电商项目(55) -支付系统核心表设计
淘东电商项目(55) -支付系统核心表设计
196 0
|
前端开发 NoSQL 数据库
淘东电商项目(25) -门户注册功能
淘东电商项目(25) -门户注册功能
41 0
|
前端开发 NoSQL 数据库
淘东电商项目(26) -门户登录功能
淘东电商项目(26) -门户登录功能
38 0
|
JSON 前端开发 NoSQL
淘东电商项目(27) -门户登出功能
淘东电商项目(27) -门户登出功能
45 0
|
JSON 数据库 数据格式
17分布式电商项目 - 模板管理功能(二)
17分布式电商项目 - 模板管理功能(二)
57 0
|
SQL JSON JavaScript
16分布式电商项目 - 模板管理功能(一)
16分布式电商项目 - 模板管理功能(一)
93 0
|
XML JSON 缓存
电商一站式管理后台必备工具:电商API接口,网络pachong、数据抓取、批量处理订单
电商一站式管理后台必备工具:电商API接口,网络pachong、数据抓取、批量处理订单
|
XML JSON 缓存
电商API接口-电商OMS不可或缺的一块 调用代码展示
电商API接口-电商OMS不可或缺的一块 调用代码展示