B2C模式电商案例-用户注册与登录

 

1、用户注册-短信验证码

1.1分析

 

1.2后端

创建 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;

 
@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.3前端

步骤一:修改apiclient.js,发送短信ajax操作

//发短信

  sendSms : ( user )=> {

    return axios.post('/web-service/sms', user )

  }

步骤二:修改Register.vue页面,给“发送验证码”绑定点击事件 sendSmsFn

<button  @click.prevent="sendSmsFn" >

        发送验证码<span>5秒</span>

</button>

 

步骤三:修改Register.vue页面,编写sendSmsFn函数,建议采用  ajax..then()..catch 可以处理异常

sendSmsFn () {

      this.$request.sendSms( this.user )

      .then(( response )=>{

        //发送短信的提示信息

        this.userMsg.smsData = response.data

      })

      .catch(( error )=>{

        //错误提示信息

        alert( error.message )

      })

    }

步骤四:修改Register.vue页面,提供变量smsData

userMsg : { //错误提示数据

        usernameData : "",

        mobileData : "",

        smsData : ""

      }

步骤五:修改Register.vue页面,显示 smsData提示信息 

 <p :class="userMsg.smsData.code == 1 ? 'success' : 'error'">{{userMsg.smsData.message}}

</p>

1.4发送验证码倒计时

步骤一:提供3个变量,用于控制倒计时

 

步骤二:在标签上面控制倒计时的显示

<button :disabled="btnDisabled" @click.prevent="sendSmsFn" >

      发送验证码<span v-show="btnDisabled">{{seconds}}</span>

</button>

步骤三:发送短信后,开启倒计时控制

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 )
      })
    }

2、用户注册

2.1:后端

  1. 保存前需要再次进行服务端校验
    1. 用户名是否注册
    2. 手机号是否注册
    3. 验证码是否失效
    4. 验证码是否错误
  2. 密码需要使用 BCrypt进行加密

 步骤一:修改UserService接口,添加register方法

/**
 * 用户注册
 * @param user
 * @return
 */
public boolean register(User user) ;

步骤二:完善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;
}

 步骤三:修改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("注册失败");
}

2.2:日期处理

编写 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;


@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);
    }
}

完善User JavaBean,设置填充方式

@TableField(value="created_at",fill = FieldFill.INSERT)
private Date createdAt;

@TableField(value="updated_at",fill = FieldFill.INSERT_UPDATE)
private Date updatedAt;

 

2.3:前端

步骤一:修改 api.js ,添加注册函数

//注册

  register : ( user )=> {

    return axios.post('/web-service/user/register', user )

  }        

步骤二:处理表单,验证码input绑定数据,提交按钮绑定事件

 

步骤三:完善data区域的user数据

 

步骤四:编写registerFn函数

async registerFn() {

      let { data } = await this.$request.register( this.user )

      if( data.code == 20000) {

        //成功

        this.$router.push('/login')

      } else {

        //失败--与发送验证码使用一个位置显示错误信息

        this.userMsg.smsData = data

      }

    }

 3、用户登录

3.1:构建页面:Login.vue

步骤一:创建Login.vue

 

步骤二:绘制通用模块

<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>

步骤三:绘制登录表单

<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>

3.2:分析

 

3.3:验证码:生成与显示

步骤一:后端生产验证码,并将用户保存Redis

1存放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;


@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());

    }
}

步骤二:点击“换一张”显示验证码

默认不显示验证码

点击“换一张”获得验证码 

methods: {

changeVerifyCode() {

// console.info(this)

this.imgSrc = `${this.$axios.defaults.baseURL}/web-service/verifycode?username=${this.user.username}&t=${new Date().getTime()}`

},

}

 

 

3.4:通过用户名查询:实现

修改UserController,添加 findByUsername函数

/**
 * 通过用户名查询
 * @param user
 * @return 返回用户对象
 */
@PostMapping("/findByUsername")
public User findByUsername(@RequestBody User user){
    //查询用户
    User findUser = userService.findByUsername( user.getUsername() );
    return findUser;
}

3.5:认证服务:构建项目(changgou4-service-auth)

创建项目

导入pom文件

<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>
        <!--    feign    -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
</dependencies>

创建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 # 过期时间,单位分钟

 

配置启动类

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;


@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CGAuthServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CGAuthServiceApplication.class, args);
    }
}

 配置类

 

3.6:认证服务:用户登录后端

步骤一:创建AuthUser 封装对象(与User比较,缺数据库相关注解)

package com.czxy.changgou4.domain;

import lombok.Data;

import java.util.Date;


@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;
}

步骤二:创建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;


@FeignClient(value = "web-service",path="/user")
public interface UserFeign {

    @PostMapping("/findByUsername")
    public AuthUser findByUsername(@RequestBody AuthUser user);

}

步骤三:创建AuthService接口,编写登录方法 login()

package com.czxy.changgou4.service;

import com.czxy.changgou4.domain.AuthUser;


public interface AuthService {

    /**
     * 用户登录
     * @param user
     * @return
     */
    public AuthUser login(AuthUser user ) ;
}

步骤四:创建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;

 
@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;
    }

}

步骤五:创建AuthController,添加login方法

        redis中登录验证码和用户输入的验证码进行匹配 

package com.czxy.changgou4.controller;

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;

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Resource
    private AuthService authService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @PostMapping("/login")
    public BaseResult login(@RequestBody AuthUser authUser) {
        if (authUser.getUsername() == null) {
            return BaseResult.error("请输入用户名");
        }
        if (authUser.getPassword() == null) {
            return BaseResult.error("请输入密码");
        }
        if (authUser.getCode() == null) {
            return BaseResult.error("请输入验证码");
        }
        //校验验证码,使用后删除
        String redisCode = stringRedisTemplate.opsForValue().get("login" + authUser.getUsername());
        //删除验证码,保证验证码的一次性
        stringRedisTemplate.delete("login" + authUser.getUsername());
        if (redisCode == null) {
            return BaseResult.error("无效验证码");
        }
        if (!redisCode.equalsIgnoreCase(authUser.getCode())) {
            return BaseResult.error("验证码错误");
        }
        AuthUser loginAuthUser = authService.login(authUser);
        if (loginAuthUser != null) {
            return BaseResult.ok("登录成功").append("loginAuthUser",loginAuthUser);
        }
        return BaseResult.error("用户名或密码错误");
    }
}

3.7:认证服务:用户登录前端

步骤一:修改apiclient.js,添加login函数

//登录

  login : ( user )=> {

    return axios.post('/auth-service/auth/login', user )

  }

步骤二:修改Login.vue,给验证码绑定变量

 

步骤三:修改Login.vue,给提交按钮绑定事件

 

步骤四:编写loginFn完成登录功能

async loginFn() {

let { data:baseResult } = await this.$request.login(this.user)

if(baseResult.code == 20000) {

sessionStorage.setItem('user',JSON.stringify(loginUser))

//跳转到首页

location.href = '/'

} else {

//失败给出提示

this.errorMsg = baseResult.message

}

},

步骤五:创建首页 ~/pages/index.vue

<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>

3.8:修改 TopNav.vue 组件

分析:完善导航条,根据vuex中的数据,显示不同内容

步骤一:创建 ~/store/index.js ,并编写vuex内容 

export const state = () => ({

  user: null

})

//通用设置

export const mutations = {

  setData( state , obj) {

    state[obj.key] = obj.value

  }

}

步骤二:页面登录成功,将用户信息保存到vuex中

// 将用户信息保存到vuex中

this.$store.commit('setData', {key:'user',value: data.data })

 

步骤三:修改顶部导航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}} 欢迎来到畅购! <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>

3.9:vuex刷新数据丢失

  • 刷新操作:
    • 点击刷新按钮
    • 点击回退按钮
    • 地址栏直接输入地址
  • 现象:
    • vuex在刷新操作后,数据丢失了
  • 解决方案
    • 方案1:不是公共组件:页面在pages目录下,可以nuxt.js提供 fetch进行操作。
    • 方案2:是公共组件:组件在components目录下,借助第三方进行存储(cookie、localStorage、sessionStorage)
      • 选择1:sessionStorage存放数据,如果vuex中没有,将sessionStorage同步过去。
      • 选择2:vuex中actions模块就可以发送ajax,从而同步数据。
  • 具体操作:
    • 如果vuex中没有数据,使用sessionStorage的数据填充vuex。
    • 修改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>

 4、整合JWT

4.1整合分析

  • 生成token:在用户登录成功,根据用户的登录信息,生成登录标识token,并返回给浏览器。
  • 使用token:完善ajax请求,在请求之前添加请求头,设置token
  • 校验token:在网关中编写过滤器,进行请求进行拦截,并校验token。
    • 白名单:在白名单中的请求,是不需要token可以直接访问的。

 4.2生成Token

        用户登录成功,生成token,并将token响应给浏览器。(认证服务 AuthService)

步骤一:查看 application.yml文件,确定 jwt配置信息

 步骤二:创建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;

 
@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());
        }
    }

步骤三:修改AuthController,注入JwtProperties,并使用JwtUtils生成token

 

 

 

package com.czxy.changgou4.controller;

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;

@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 authUser) {
        if (authUser.getUsername() == null) {
            return BaseResult.error("请输入用户名");
        }
        if (authUser.getPassword() == null) {
            return BaseResult.error("请输入密码");
        }
        if (authUser.getCode() == null) {
            return BaseResult.error("请输入验证码");
        }
        //校验验证码,使用后删除
        String redisCode = stringRedisTemplate.opsForValue().get("login" + authUser.getUsername());
        //删除验证码,保证验证码的一次性
        stringRedisTemplate.delete("login" + authUser.getUsername());
        if (redisCode == null) {
            return BaseResult.error("无效验证码");
        }
        if (!redisCode.equalsIgnoreCase(authUser.getCode())) {
            return BaseResult.error("验证码错误");
        }
        AuthUser loginAuthUser = authService.login(authUser);
        if (loginAuthUser != null) {
            String token = JwtUtils.generateToken(loginAuthUser, jwtProperties.getExpire(), jwtProperties.getPrivateKey());
            return BaseResult.ok("登录成功").append("loginAuthUser",loginAuthUser).append("token",token);
        }
        return BaseResult.error("用户名或密码错误");
    }
}

4.3使用Token

步骤一:登录成功后保存token,修改 Login.vue页面

 

async loginFn() {

let { data:baseResult } = await this.$request.login(this.user)

if(baseResult.code == 20000) {

let loginUser = baseResult.other.loginAuthUser

sessionStorage.setItem('loginUser',JSON.stringify(loginUser))

//保存token

sessionStorage.setItem('token',baseResult.other.token)

//跳转到首页

location.href = '/'

} else {

//失败给出提示

this.errorMsg = baseResult.message

this.changeVerifyCode()

}

},

步骤二:请求是自动携带token,修改apiclient.js,将token添加到请求头 

 

//参考 https://axios.nuxtjs.org/helpers
    // 增强$axios,每一个请求都携带token
    let token = sessionStorage.getItem('token')
    if(token) {
      // 添加一个固定的请求头 Adds header:`Authorizatino: 123` to all requests
      $axios.setToken(token)
    }

步骤三:检查 nuxt.conf.js,插件模式改成“client”

否则抛异常“sessionStorage is not defined” 

plugins: [

    { src: '~plugins/apiclient.js', mode: 'client' }

  ],

4.4校验token(在网关项目处完成)

步骤一:修改application.yml添加jwt配置

#自定义内容
sc:
  jwt:
    secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
    pubKeyPath: D:/rsa/rsa.pub # 公钥地址
    priKeyPath: D:/rsa/rsa.pri # 私钥地址
    expire: 360 # 过期时间,单位分钟

 

步骤二:创建 JwtProperties,用于加载配置文件

 

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;

 
@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());
        }
    }


}

 步骤三:编写过滤器,对所有路径进行拦截

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;

 
@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;
    }
}

 步骤四:修改前端 apiclient.js 文件,用于处理401异常

 

//异常处理:401

$axios.onError(error => {

if(error.response.status === 401) {

//重定向

redirect('/Login')

}

})

api.js 完整代码

var axios = null

export default ({ $axios, redirect }, inject) => {

//参考 https://axios.nuxtjs.org/helpers

// 增强$axios,每一个请求都携带token

let token = sessionStorage.getItem('token')

if(token) {

// 添加一个固定的请求头 Adds header:`Authorizatino: 123` to all requests

$axios.setToken(token)

}

//异常处理:401

$axios.onError(error => {

if(error.response.status === 401) {

//重定向

redirect('/Login')

}

})

//赋值

axios = $axios

//4) 将自定义函数交于nuxt

// 使用方式1:在vue中,this.$request.xxx()

// 使用方式2:在nuxt的asyncData中,content.app.$request.xxx()

inject('request', request)

}

 

4.5白名单

不需要拦截的资源都配置到yml文件中,在过滤器直接放行

步骤一:修改application.yml文件

 

#自定义内容
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

步骤二:创建FilterProperties配置文件,用于存放允许放行的路径

 

 

package com.czxy.changgou4.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

 
@Data
@ConfigurationProperties(prefix="sc.filter")
public class FilterProperties {

    //允许访问路径集合
    private List<String> allowPaths;

 

步骤三:修改 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;

 
@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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值