zoukankan      html  css  js  c++  java
  • 从零开始,搭建一个简单的购物平台(十八)前端商城部分

    从零开始,搭建一个简单的购物平台(十七)前端商城部分:
    https://blog.csdn.net/time_____/article/details/108893925
    项目源码(持续更新):https://gitee.com/DieHunter/myCode/tree/master/shopping

    上篇文章对购物车进行了简单的介绍,商城的所有基础功能已经全部实现,这篇文章开始将介绍一下用户信息和订单相关的功能实现,用户信息登录在后端管理中已经实现,我们需要实现一个注册功能,和邮箱验证功能,具体实现可参照之前的一篇博客或是这篇文章

    实现效果如下,分别是用户名密码登录,邮箱验证登录,注册功能

    登录部分

    账号密码登录与后台管理系统的登录一样,若用户输入用户名密码登录成功,则将用户部分信息加密成token传至前端并保存至本地,每次通过将token值发送后端进行继续操作

    邮箱验证

    在服务端的config文件中新建EmailTransporter静态变量,用来配置发送邮箱使用到的nodemailer模块

    EmailTransporter: {
        // service: "qq", // 运营商  qq邮箱 网易  若使用QQ邮箱,则只需配置service:qq
        host: "smtp.163.com",// 若使用网易邮箱,则只需配置host:smtp.163.com
        port: 465, //端口
        auth: {
          user: "132*****516@163.com", //发送方的邮箱
          pass: "WAQM******WQEFKB", // pop3 授权码
        },
      },

    然后新建邮箱验证码配置EmailConfig

      EmailConfig: {
        time: 5,//验证码有效期,单位分钟
        codeLength: 4,//验证码长度
        sendTime: 1 * 60 * 1000,//后端验证码允许再次发送时间间隔,单位毫秒
        targetTime: 5 * 60 * 1000,//验证码有效期,单位毫秒
        title: "零食商贩",//验证码标题
      },

    接着在Utils中写一个生成随机验证码函数

      /* 生成随机
       * @method    codeLength   函数名
       * @for    Utils     附属于哪个类
       * @param {number/string} len  随机数长度
       * @return {string}  _count   生成len个随机数
       */
      static codeLength(len) {
        let _count = "";
        for (let i = 0; i < len; i++) {
          _count += Math.floor(Math.random() * 10); //生成len个随机数
        }
        return _count;
      }

    在Utils生成时间戳函数,记录验证码及验证码发送时间和有效时间

     /* 生成时间戳
       * @method    randomCode
       * @for    Utils
       * @param
       * @return {object}  _count   生成len个随机数
       */
      static randomCode() {
        return {
          code: this.codeLength(EmailConfig.codeLength), //生成的随机数
          sendTime: new Date().getTime() + EmailConfig.sendTime, //发送时间
          targetTime: new Date().getTime() + EmailConfig.targetTime, //截止时间
        };
      }

    在Utils文件夹下新建SendMail.js并新建发送邮件模块,在utils中调用

    const nodemailer = require("nodemailer");
    const Config = require("../config/config");
    module.exports = class SendMail {
      static transporter = nodemailer.createTransport(Config.EmailTransporter); //邮箱配置项
      static mailOptions = null; //邮箱配置
      /* 发送邮件模块
       * @method    sendEmail
       * @for       SendMail
       * @param   {String} mail  用户邮箱号
       * @param   {String} title  邮件标题
       * @param   {String} content  邮件内容
       * @return {Boolean}   是否发送成功
       */
      static async sendEmail(mail, title, content) {
        this.mailOptions = {
          from: '"邮箱验证" <' + Config.EmailTransporter.auth.user + ">",
          to: mail,
          subject: title,
          text: content,
        };
        try {
          let result = await this.transporter.sendMail(this.mailOptions);
          console.log("发送成功");
          return true;
        } catch (error) {
          console.log(error);
          console.log("发送失败");
          return false;
        }
      }
    };
    

    在utils中新建生成邮件内容的函数

    /* 生成邮件内容
       * @method    sendEmailCode
       * @for    Utils
       * @param   {String} code  验证码内容
       * @param   {String} email   用户邮箱
       */
      static async sendEmailCode(code, email) {
        return await SendMail.sendEmail(
          email,
          EmailConfig.title,
          `您的验证码为:${code},${EmailConfig.time}分钟内有效`
        );
      }

    最后在utils编写一个异步发送邮箱的函数

    /* 异步发送邮箱验证
       * @method    createEmailCode
       * @for    Utils
       * @param   {Object} codeList  邮箱验证码列表
       * @param   {String} email   用户邮箱
       * @param   {Object} findRes  数据库搜寻到的用户信息
       * @return {Boolean}  isSuccess   是否发送成功
       */
      static async createEmailCode(codeList, email, findRes) {
        if (!codeList[email] || new Date().getTime() > codeList[email].sendTime) {
          //已过1分钟,防止多次请求邮箱
          codeList[email] = this.randomCode();
          codeList[email].info = findRes;
          return await this.sendEmailCode(codeList[email].code, email);
        } else {
          //未过1分钟
          return false;
        }
      }

    一个发送邮件的完整模块就实现完成,下一步要做的是验证码的验证功能

     /* 核对验证码
       * @method    checkEmailCode
       * @for    Utils
       * @param   {Object} codeList  用户验证码列表
       * @param   {String} key   用户邮箱
       * @param   {Object} _data   用户提交的表单信息
       * @return   {Object} res   请求响应返回值
       */
      static checkEmailCode(codeList, key, _data) {
        if (!codeList[key]) {
          //未发送验证码
          return {
            result: 0,
            msg: "验证码错误",
          };
        } else if (
          new Date().getTime() < codeList[key].targetTime &&
          _data.mailcode == codeList[key].code
        ) {
          //验证码校对成功
          let _obj = {
            result: 1,
            token: Utils.createToken(
              codeList[key].info.userType || "",
              codeList[key].info.username || "",
              _data.remember || ""
            ),
            msg: "操作成功",
          };
          codeList[key] = null;
          return _obj;
        } else if (new Date().getTime() > codeList[key].targetTime) {
          //验证码超时
          return {
            result: 0,
            msg: "验证码超时",
          };
        } else {
          return {
            result: 0,
            msg: "验证失败",
          };
        }
      }

    到这一步,关于验证码的所有准备工作已全部实现,下一步将实现注册登录功能,其中登录有两个途径,注册时邮箱为必填值,所以可以使用邮箱验证的方式进行登录

    服务端获取验证码接口,通过一个codeType区分用户登录获取验证码和注册获取验证码

    router.get(Config.ServerApi.getMailCode, async (_req, res) => {
      //用户邮箱验证
      let _data = Util.getCrypto(Util.parseUrl(_req, res).crypto);//解密参数
      //查询用户是否存在,若未找到用户,则返回错误响应值,否则异步发送邮件验证码
      let findRes = await findData(Mod, {
        mailaddress: _data.username.split('@')[0],
        mailurl: '@' + _data.username.split('@')[1],
      });
      if ((!findRes.length || !findRes) && _data.codeType !== 'reg') {//过滤区分用户注册登录
        res.send({
          result: 0,
          msg: "用户未注册"
        });
        return;
      }
      await Util.createEmailCode(userCodeList, _data.username, findRes[0] || {}) ? res.send({
        result: 1,
        msg: "发送成功",
      }) : res.send({
        result: 0,
        msg: "发送失败"
      });
    });

    在实现注册部分之前,我们要写一个工具方法,用于验证码倒计时,在此期间用户无法再次点击发送请求

    import Vue from "vue";
    import Config from "../config/config";
    const { GetCodeTime, CodeText } = Config;
    class TimeTick {
      static timer = GetCodeTime / 1000;//倒计时时间
      static _timeTick = null;//定时器
      static timeTick(fn) {
        if (!TimeTick._timeTick) {
          TimeTick._timeTick = setInterval(() => {
            if (TimeTick.timer-- <= 1) {
              // 重置倒计时和发送邮箱验证开关
              TimeTick.clearTick();
              fn({ content: CodeText, res: 1 });//倒计时归零
            } else {
              fn({ content: TimeTick.timer + "S", res: 0 });//倒计时中,阻止用户重复点击
            }
          }, 1000);
        }
      }
      static clearTick() {
        //清除定时器
        clearInterval(TimeTick._timeTick);
        TimeTick._timeTick = null;
      }
    }
    Vue.prototype.$timeTick = TimeTick;
    

     用户注册界面

    <template>
      <div class="login">
        <div>
          <mt-field
            placeholder="请输入用户名"
            :state="userInfo.username.length ? 'success' : 'error'"
            v-model="userInfo.username"
          ></mt-field>
          <mt-field
            placeholder="请输入密码"
            :state="userInfo.password.length ? 'success' : 'error'"
            v-model="userInfo.password"
            type="password"
          ></mt-field>
          <mt-field
            placeholder="请重复输入密码"
            :state="
              userInfo.repassword.length && userInfo.password == userInfo.repassword
                ? 'success'
                : 'error'
            "
            v-model="userInfo.repassword"
            type="password"
          ></mt-field>
          <mt-field
            placeholder="请输入邮箱"
            v-model="userInfo.mailaddress"
            :state="userInfo.mailaddress.length ? 'success' : 'error'"
          >
            <mt-button class="btn" @click="selectMail">{{
              userInfo.mailurl
            }}</mt-button>
          </mt-field>
          <mt-field
            placeholder="请输入验证码"
            :state="userInfo.mailcode.length == 4 ? 'success' : 'error'"
            v-model="userInfo.mailcode"
            type="number"
          >
            <mt-button class="btn" :disabled="canGetCode" @click="getCode">{{
              codeTime
            }}</mt-button>
          </mt-field>
          <mt-button class="btn" type="primary" @click="submit">注册</mt-button>
          <div class="shopPicker"></div>
          <ShopPicker :ShopMaxCount="address" pickerTitle="邮箱类型"></ShopPicker>
        </div>
      </div>
    </template>
    
    <script>
    import Config from "../../config/config";
    import Mail from "../../config/mail";
    import ShopPicker from "../shopPicker/shopPicker";
    import { Field, Button, Picker, Popup } from "mint-ui";
    import RegBussiness from "./bussiness";
    const { GetCodeTime, EventName, CodeText } = Config;
    const { address } = Mail;
    export default {
      components: {
        ShopPicker,
      },
      data() {
        return {
          codeTime: CodeText, //获取验证码按钮显示值
          address, //邮箱默认地址
          canGetCode: false, //防止重复点击开关
          userInfo: {
            //注册表单默认数据
            username: "",
            password: "",
            repassword: "",
            mailurl: address[0],
            mailaddress: "",
            mailcode: "",
          },
        };
      },
      created() {
        this.regBussiness = new RegBussiness(this);
        this.$events.onEvent(EventName.ChangeCount, (_count) => {
          this.userInfo.mailurl = _count; //切换邮箱地址
        });
      },
      destroyed() {
        this.$events.offEvent(EventName.ChangeCount);
      },
      methods: {
        selectMail() {
          this.$events.emitEvent(EventName.ShowPicker);
        },
        getCode() {
          if (this.canGetCode) {
            //是否允许发送邮箱验证
            return;
          }
          this.regBussiness.sendCode().then((res) => {
            this.canGetCode = true;//关闭点击开关
            this.$timeTick.timeTick((state) => {
              this.codeTime = state.content;
              switch (state.res) {
                case 0:
                  this.canGetCode = false;//允许用户点击
                  break;
              }
            });
          });
        },
        submit() {
          this.regBussiness.submitData();
        },
      },
    };
    </script>
    
    <style lang="less" scoped>
    @import "../../style/init.less";
    
    .login {
      .btn {
        .f_s(34);
         100%;
        .h(100);
      }
    }
    </style>

    注册业务逻辑部分,bussiness.js

    import Vue from 'vue'
    import {
      Toast
    } from "mint-ui";
    import config from "../../config/config"
    const {
      ServerApi,
      StorageName,
      EventName
    } = config
    export default class LoginBussiness extends Vue {
      constructor(_vueComponent) {
        super()
        this.vueComponent = _vueComponent
      }
      sendCode() {
        return new Promise((resolve, reject) => {
          if (!this.vueComponent.userInfo.mailaddress.length) {//过滤邮箱长度为0
            Toast('请填写正确的邮箱');
            return
          }
          this.$axios
            .get(ServerApi.user.getMailCode, {
              params: {
                crypto: this.$crypto.setCrypto({
                  codeType: "reg",//区分注册登录类型
                  username: this.vueComponent.userInfo.mailaddress + this.vueComponent.userInfo.mailurl
                })
              },
            }).then(res => {
              switch (res.result) {
                case 1:
                  Toast(res.msg);
                  resolve(res)
                  break;
                default:
                  reject(res)
                  break;
              }
            }).catch(err => {
              reject(err)
            })
        })
    
      }
      submitData() {
        for (const key in this.vueComponent.userInfo) {
          if (this.vueComponent.userInfo.hasOwnProperty(key) && !this.vueComponent.userInfo[key].length) {//过滤表单项长度为0
            Toast('请填写完整的信息');
            return
          }
        }
        this.$axios
          .post(ServerApi.user.userReg, {
            crypto: this.$crypto.setCrypto({
              ...this.vueComponent.userInfo
            })
          }).then(res => {
            //成功后重置用户信息
            this.vueComponent.userInfo.password = "";
            this.vueComponent.userInfo.repassword = "";
            this.vueComponent.userInfo.mailcode = "";
            switch (res.result) {
              case 1:
                Toast(res.msg);
                history.go(-1)//返回登录页面
                break;
              default:
                break;
            }
          })
      }
    }
    

    注册部分完成,登录与注册功能类似,这里只介绍一下服务端token的生成

    在服务端user.js中修改接口,和管理系统登录一样,新增邮箱验证登录,区分管理员和用户登录

    router.post(Config.ServerApi.userLogin, async (req, res) => {
      let _data = Util.getCrypto(Util.parseUrl(req, res).crypto); //解密前端入参
      switch (_data.loginType) {
        case "code"://验证码登录,验证邮箱验证码
          res.send(Util.checkEmailCode(userCodeList, _data.username, _data));
          break;
        case "psd"://密码登录
        default:
          let findRes = await findData(Mod, {
            $or: [
              {
                mailaddress: _data.username.split("@")[0],
                mailurl: "@" + _data.username.split("@")[1],
              },
              {
                username: _data.username,
              },
              {
                phoneNum: _data.username,
              },
            ],
          });
          if (findRes && findRes.length > 0) {
            Util.checkBcrypt(_data.password, findRes[0].password)
              ? res.send({
                  result: 1,
                  token: Util.createToken(//生成前端token
                    findRes[0].userType,
                    findRes[0].username,
                    _data.remember
                  ),
                  msg: "登录成功",
                })
              : res.send({
                  result: 0,
                  msg: "密码错误",
                });
            return;
          }
          res.send({
            result: 0,
            msg: "用户不存在",
          });
          break;
      }
    });

    总结
    本篇文章将用户的注册登录邮箱验证功能基本实现,主要功能参照之前的邮箱验证登录注册的博客,文章中的重点是邮箱验证功能模块,与注册登录配合使用,注册则新增用户,登录则更新token值。下一篇将介绍用户信息修改,及后续将实现订单的生成及查看功能

  • 相关阅读:
    Hadoop启动脚本分析
    java基础-Idea开发工具介绍
    Hadoop集群-HDFS集群中大数据运维常用的命令总结
    Hadoop部署方式-高可用集群部署(High Availability)
    Hadoop部署方式-完全分布式(Fully-Distributed Mode)
    Hadoop部署方式-伪分布式(Pseudo-Distributed Mode)
    Hadoop部署方式-本地模式(Local (Standalone) Mode)
    Hadoop基础原理
    Java基础-DBCP连接池(BasicDataSource类)详解
    nc命令的常用参数介绍
  • 原文地址:https://www.cnblogs.com/HelloWorld-Yu/p/13806817.html
Copyright © 2011-2022 走看看