秒杀项目介绍&&登陆模块&&捕捉全局异常

一、秒杀项目介绍

1、介绍

秒杀无论是双十一购物还是 12306 抢票,秒杀场景已随处可见。简单来说,秒杀就是在同一时刻大量请求争抢购买同一商品并完成交易的过程。从架构视角来看,秒杀系统本质是一个高性能、高一致、高可用的三高系统。

通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动

2、特点

  • 高并发:秒杀的特点就是这样时间极短、 瞬间用户量大
  • 库存量少:一般秒杀活动商品量很少,这就导致了只有极少量用户能成功购买到。
  • 业务简单:流程比较简单,一般都是下订单、扣库存、支付订单
  • 恶意请求,数据库压力大
  • 秒杀的商品不需要添加到购物车
  • 秒杀系统独立部署

二、技术上点介绍

前端:Freemarker、LayUI、jQuery
后端:SpringBoot、MyBatisPlus、Lombok
中间件:RabbitMQ、Redis(redisson)
分布式协调框架:zookeeper

三、项目目标

1.安全优化:隐藏秒杀地址、验证码、接口限流
2.服务优化:RabbitMQ消息队列、接口优化、分布式锁
3.页面优化:缓存、静态化分离
4.分布式会话:用户登录、共享session
5.功能开发:商品列表、商品详情、秒杀、订单详情
6.系统压测:JMeter入门、自定义变量、压测

四、搭建环境

1、配置数据库以及表

导入sql代码

连接到IDEA

2、创建SpringBoot项目并配置POM

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xhy</groupId>
    <artifactId>code</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>code</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mybatis plus依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--mybatis-plus生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- MD5依赖 -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <!-- valid验证依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--hariki-->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.1</version>
                <configuration>
                    <mainClass>com.xhy.code.CodeApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3 、配置application.yml

spring:
  application:
    name: seckill
  datasource:
    url: jdbc:mysql://localhost:3306/seckill?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    hikari:
      # 最小空闲连接数量
      minimum-idle: 5
      # 空闲连接存活最大时间,默认600000(10分钟)
      idle-timeout: 180000
      # 连接池最大连接数,默认是10
      maximum-pool-size: 10
      # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
      auto-commit: true
      # 连接池名称
      pool-name: MyHikariCP
      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
      max-lifetime: 1800000
      # 数据库连接超时时间,默认30秒,即30000
      connection-timeout: 30000
  freemarker:
    #设置编码格式
    charset: UTF-8
    #后缀
    suffix: .ftl
    #文档类型
    content-type: text/html
    #模板前端
    template-loader-path: classpath:/templates/
    #启用模板
    enabled: true
    #开启静态资源
  mvc:
    static-path-pattern: /static/**
mybatis-plus:
  mapper-locations: classpath*:/mapper/*Mapper.xml
  type-aliases-package: com.xhy.seckill.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    com.xhy.seckill.mapper: debug

5、 使用Mybatis-plus反向生成代码

package com.code.ceckill.generator;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@SuppressWarnings("all")
@Slf4j
@Data
public class MybatisPlusGenerator {
   //url: jdbc:mysql://localhost:3306/seckill?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF8

    protected static String URL = "jdbc:mysql://localhost:3306/seckill?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF8";
    protected static String USERNAME = "root";
    protected static String PASSWORD = "123456";

    protected static DataSourceConfig.Builder DATA_SOURCE_CONFIG = new DataSourceConfig.Builder(URL, USERNAME, PASSWORD);

    public static void main(String[] args) {
        FastAutoGenerator.create(DATA_SOURCE_CONFIG)
                .globalConfig(
                        (scanner/*lamdba*/, builder/*变量*/) ->
                                builder.author(scanner.apply("请输入作者名称?"))
                                        .enableSwagger()
                                        .fileOverride()
                                        .outputDir(System.getProperty("user.dir") + "\\src\\main\\java")
                                        .commentDate("yyyy-MM-dd")
                                        .dateType(DateType.TIME_PACK)
                )
                .packageConfig((builder) ->
                        builder.parent("com.xhy.seckill")
                                .entity("pojo")
                                .service("service")
                                .serviceImpl("service.impl")
                                .mapper("mapper")
                                .xml("mapper.xml")
                                .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\mapper"))
                )
                .injectionConfig((builder) ->
                        builder.beforeOutputFile(
                                (a, b) -> log.warn("tableInfo: " + a.getEntityName())
                        )
                )
                .strategyConfig((scanner, builder) ->
                        builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                                .addTablePrefix("tb_", "t_")
                                .entityBuilder()
                                .enableChainModel()
                                .enableLombok()
                                .enableTableFieldAnnotation()
                                .addTableFills(
                                        new Column("create_time", FieldFill.INSERT)
                                )
                                .controllerBuilder()
                                .enableRestStyle()
                                .enableHyphenStyle()
                                .build())
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
    }

    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

}

 

6、启动类添加注解开启一些启动器

五、 用户登陆

登录密码的加密

用户登录的时候,登录信息由前端传递到后端是通过http协议进行传输的,由于http协议是明文传输的。所有的登录信息都在其body里面,所以将用户的登录密码进行明文传输安全性较低。所以在传输之前,我们在前端需要对password进行加密,通过MD5进行对称加密。

整个加密过程分为两次MD5加密:
(1)客户端:PASS = MD5(明文)
(2)服务端:PASS = MD5(用户输入+随机salt)

MD5加密的原理:MD5 (Message -digest algorithm 5)就是信息摘要的一种实现,它可以从任意长度的明文字符串生成128位的哈希值,也就是32位的16进制。

1、后端搭建

逆向生成代码生成user表的代码

 2、前端搭建(使用freemarker搭建)

 login.ftl

<!DOCTYPE html>
<html lang="zh">
<head>
    <#include "common/head.ftl"/>
    <style>
        .layui-panel {
            position: absolute;
            width: 400px;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            padding: 15px 15px 0px 15px;
            border-radius: 20px;
        }

        .layui-form-label {
            padding: 9px 0px;
        }

        h3 {
            text-align: center;
            line-height: 45px;
            font-size: 40px;
            color: white;
            padding-bottom: 15px;
        }
    </style>
</head>
<body>
<div>
    <div class="layui-panel layui-bg-cyan">
        <h3>用户登录</h3>
        <div class="layui-form-item">
            <label class="layui-form-label">用户账号</label>
            <div class="layui-input-block">
                <input type="text" id="mobile" autocomplete="on" class="layui-input" value="18684671234">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">用户密码</label>
            <div class="layui-input-block">
                <input type="password" id="password" autocomplete="on" class="layui-input" value="123456">
            </div>
        </div>
        <div class="layui-form-item" style="text-align:center;">
            <button class="layui-btn" id="login" style="width:46%">登录</button>
            <button class="layui-btn layui-btn-normal" id="register" style="width:46%">注册</button>
        </div>
    </div>
</div>
<script src="${ctx}/static/asset/js/project/md5.js"></script>
<script src="${ctx}/static/asset/js/project/login.js"></script>
</body>
</html>

head.ftl

<meta charset="UTF-8">
<title>秒杀项目</title>
<script src="/static/asset/js/layui/layui.js" type="text/javascript"></script>
<link href="/static/asset/js/layui/css/layui.css" rel="stylesheet" type="text/css"/>
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">
<#assign ctx>
    ${springMacroRequestContext.getContextPath()}
</#assign>

Layui 开发使用文档 - 入门指南

layui定义模块(login.js)

layui.define(()=>{
    /**
     * 得到layui中封装jQuery
     *
     */
    let $=layui.jquery
    //给登陆按钮设置事件
    $(login).click(()=>{
       // alert("-----")
        var mobile = $(mobile).val();
        var password=$(password).val();
    })
    //将数据给后台(前后端axios。普通开发ajax)
    $.ajax({
        url:"",//后台登录接口
        data:{
            // 需要携带的数据
            mobile,
            password
        },
        datatype: "json",//后端给你的数据类型
        success(e){
            // 成功的回调函数

        },
        error(e){
            // 报错的回调函数

        }

    })


})

专门用于跳转路径:PathController类

package com.code.seckill.controller;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class PathController {
    //    登录跳首页
    @RequestMapping("/")
    public String toPath(){
        return "login";
    }

    //    跳所有二级页面
    @RequestMapping("/{dir}/{path}")
    public String toPath(@PathVariable("dir") String dir,@PathVariable("path") String path){
        return dir+"/"+path;
    }



}

解决端口占用找不到页面 

在yml文件中加入 

 访问端口8081

 六、MD5加密

MD5加密
* 用户端:password=MD5(明文+固定Salt)
* 服务端:password=MD5(用户输入+随机Salt)
* 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。

1、导入相关的工具包

加入工具类:MD5Utils.java

package com.code.seckill.util;

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * MD5加密
 * 用户端:password=MD5(明文+固定Salt)
 * 服务端:password=MD5(用户输入+随机Salt)
 * 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。
 */
@Component
@SuppressWarnings("all")
public class MD5Utils {

    //加密盐,与前端一致
    private static String salt = "f1g2h3j4";

    /**
     * md5加密
     *
     * @param src
     * @return
     */
    public static String md5(String src) {
        return DigestUtils.md5Hex(src);
    }

    /**
     * 获取加密的盐
     *
     * @return
     */
    public static String createSalt() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 将前端的明文密码通过MD5加密方式加密成后端服务所需密码
     * 注意:该步骤实际是在前端完成!!!
     *
     * @param inputPass 明文密码
     * @return
     */
    public static String inputPassToFormpass(String inputPass) {
        //混淆固定盐salt,安全性更可靠
        String str = salt.charAt(1) + "" + salt.charAt(5) + inputPass + salt.charAt(0) + "" + salt.charAt(3);
        return md5(str);
    }

    /**
     * 将后端密文密码+随机salt生成数据库的密码
     *
     * @param formPass
     * @param salt
     * @return
     */
    public static String formPassToDbPass(String formPass, String salt) {
        //混淆固定盐salt,安全性更可靠
        String str = salt.charAt(7) + "" + salt.charAt(9) + formPass + salt.charAt(1) + "" + salt.charAt(5);
        return md5(str);
    }

    /**
     * 将用户输入的密码转换成数据库的密码
     *
     * @param inputPass 明文密码
     * @param salt      盐
     * @return
     */
    public static String inputPassToDbPass(String inputPass, String salt) {
        String formPass = inputPassToFormpass(inputPass);
        String dbPass = formPassToDbPass(formPass, salt);
        return dbPass;
    }

    public static void main(String[] args) {
        String formPass = inputPassToFormpass("123456");
        System.out.println("前端加密密码:" + formPass);
        String salt = createSalt();
        System.out.println("后端加密随机盐:" + salt);
        String dbPass = formPassToDbPass(formPass, salt);
        System.out.println("后端加密密码:" + dbPass);
        String dbPass1 = inputPassToDbPass("123456", salt);
        System.out.println("最终加密密码:" + dbPass1);
    }
}

2、新建vo类:userVo

用于前后端传值:

package com.code.seckill.vo;

import lombok.Data;

@Data
public class UserVo {
    //手机号
    private String mobile;
    //密码
        private String password;
}

3、后端方法

IUserService

package com.code.seckill.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.code.seckill.pojo.User;
import com.code.seckill.util.response.ResponseResult;
import com.code.seckill.vo.UserVo;

/**
 * <p>
 * 用户信息表 服务类
 * </p>
 *
 * @author xhy-gitHub
 * @since 2022-03-15
 */
public interface IUserService extends IService<User> {

    ResponseResult<?> findByAccount(UserVo userVo);
}

UserServiceImpl

package com.code.seckill.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.code.seckill.mapper.UserMapper;
import com.code.seckill.pojo.User;
import com.code.seckill.service.IUserService;
import com.code.seckill.util.MD5Utils;
import com.code.seckill.util.ValidatorUtils;
import com.code.seckill.util.response.ResponseResult;
import com.code.seckill.util.response.ResponseResultCode;
import com.code.seckill.vo.UserVo;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author xhy-gitHub
 * @since 2022-03-15
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {


    @Override
    public ResponseResult<?> findByAccount(UserVo userVo) {
//先判断信息是否符合(账号是否是手机号,密码是不是空的)
        if (!ValidatorUtils.isMobile(userVo.getMobile())) {
            return ResponseResult.failure(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE);
        }
        if (!StringUtils.isBlank(userVo.getPassword())) {
            return ResponseResult.failure(ResponseResultCode.USER_PASSWORD_NOT_MATCH);
        }
//         再去数据库查出对应的用户(mobile)
        User user = this.getOne(new QueryWrapper<User>().eq("id", userVo.getMobile()));
        if (user == null) {
            return ResponseResult.failure(ResponseResultCode.USER_ACCOUNT_NOT_FIND);
        }
//        比较密码
        //        二重加密(前端->后端,后端->数据库)
        String salt=user.getSalt();
//        将前台的加密密码和后端的盐再次进行加密
        String newPassword= MD5Utils.formPassToDbPass(userVo.getPassword(),salt);
        if(!newPassword.equals(user.getPassword())){
            return ResponseResult.failure(ResponseResultCode.USER_PASSWORD_NOT_MATCH);
        }

        return ResponseResult.success();

    }
}

UserController

package com.code.seckill.controller;

import com.code.seckill.service.IUserService;
import com.code.seckill.util.response.ResponseResult;
import com.code.seckill.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 * 用户信息表 前端控制器
 * </p>
 *
 * @author xhy-gitHub
 * @since 2022-03-15
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired

    private IUserService userService;

    //    用户登录
    @RequestMapping("/login")
    public ResponseResult<?> login(UserVo userVo){
//     调用service的登录验证
        return userService.findByAccount(userVo);
    }

}

4、前端代码

将md5.js放到js文件中的project文件下

/*
 * JavaScript MD5
 * https://github.com/blueimp/JavaScript-MD5
 *
 * Copyright 2011, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * https://opensource.org/licenses/MIT
 *
 * Based on
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/* global define */

(function ($) {
    'use strict'

    /*
    * Add integers, wrapping at 2^32. This uses 16-bit operations internally
    * to work around bugs in some JS interpreters.
    */
    function safeAdd (x, y) {
        var lsw = (x & 0xffff) + (y & 0xffff)
        var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
        return (msw << 16) | (lsw & 0xffff)
    }

    /*
    * Bitwise rotate a 32-bit number to the left.
    */
    function bitRotateLeft (num, cnt) {
        return (num << cnt) | (num >>> (32 - cnt))
    }

    /*
    * These functions implement the four basic operations the algorithm uses.
    */
    function md5cmn (q, a, b, x, s, t) {
        return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
    }
    function md5ff (a, b, c, d, x, s, t) {
        return md5cmn((b & c) | (~b & d), a, b, x, s, t)
    }
    function md5gg (a, b, c, d, x, s, t) {
        return md5cmn((b & d) | (c & ~d), a, b, x, s, t)
    }
    function md5hh (a, b, c, d, x, s, t) {
        return md5cmn(b ^ c ^ d, a, b, x, s, t)
    }
    function md5ii (a, b, c, d, x, s, t) {
        return md5cmn(c ^ (b | ~d), a, b, x, s, t)
    }

    /*
    * Calculate the MD5 of an array of little-endian words, and a bit length.
    */
    function binlMD5 (x, len) {
        /* append padding */
        x[len >> 5] |= 0x80 << (len % 32)
        x[((len + 64) >>> 9 << 4) + 14] = len

        var i
        var olda
        var oldb
        var oldc
        var oldd
        var a = 1732584193
        var b = -271733879
        var c = -1732584194
        var d = 271733878

        for (i = 0; i < x.length; i += 16) {
            olda = a
            oldb = b
            oldc = c
            oldd = d

            a = md5ff(a, b, c, d, x[i], 7, -680876936)
            d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
            c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
            b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
            a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
            d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
            c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
            b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
            a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
            d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
            c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
            b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
            a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
            d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
            c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
            b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)

            a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
            d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
            c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
            b = md5gg(b, c, d, a, x[i], 20, -373897302)
            a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
            d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
            c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
            b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
            a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
            d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
            c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
            b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
            a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
            d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
            c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
            b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)

            a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
            d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
            c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
            b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
            a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
            d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
            c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
            b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
            a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
            d = md5hh(d, a, b, c, x[i], 11, -358537222)
            c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
            b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
            a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
            d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
            c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
            b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)

            a = md5ii(a, b, c, d, x[i], 6, -198630844)
            d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
            c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
            b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
            a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
            d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
            c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
            b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
            a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
            d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
            c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
            b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
            a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
            d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
            c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
            b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)

            a = safeAdd(a, olda)
            b = safeAdd(b, oldb)
            c = safeAdd(c, oldc)
            d = safeAdd(d, oldd)
        }
        return [a, b, c, d]
    }

    /*
    * Convert an array of little-endian words to a string
    */
    function binl2rstr (input) {
        var i
        var output = ''
        var length32 = input.length * 32
        for (i = 0; i < length32; i += 8) {
            output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff)
        }
        return output
    }

    /*
    * Convert a raw string to an array of little-endian words
    * Characters >255 have their high-byte silently ignored.
    */
    function rstr2binl (input) {
        var i
        var output = []
        output[(input.length >> 2) - 1] = undefined
        for (i = 0; i < output.length; i += 1) {
            output[i] = 0
        }
        var length8 = input.length * 8
        for (i = 0; i < length8; i += 8) {
            output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32)
        }
        return output
    }

    /*
    * Calculate the MD5 of a raw string
    */
    function rstrMD5 (s) {
        return binl2rstr(binlMD5(rstr2binl(s), s.length * 8))
    }

    /*
    * Calculate the HMAC-MD5, of a key and some data (raw strings)
    */
    function rstrHMACMD5 (key, data) {
        var i
        var bkey = rstr2binl(key)
        var ipad = []
        var opad = []
        var hash
        ipad[15] = opad[15] = undefined
        if (bkey.length > 16) {
            bkey = binlMD5(bkey, key.length * 8)
        }
        for (i = 0; i < 16; i += 1) {
            ipad[i] = bkey[i] ^ 0x36363636
            opad[i] = bkey[i] ^ 0x5c5c5c5c
        }
        hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8)
        return binl2rstr(binlMD5(opad.concat(hash), 512 + 128))
    }

    /*
    * Convert a raw string to a hex string
    */
    function rstr2hex (input) {
        var hexTab = '0123456789abcdef'
        var output = ''
        var x
        var i
        for (i = 0; i < input.length; i += 1) {
            x = input.charCodeAt(i)
            output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f)
        }
        return output
    }

    /*
    * Encode a string as utf-8
    */
    function str2rstrUTF8 (input) {
        return unescape(encodeURIComponent(input))
    }

    /*
    * Take string arguments and return either raw or hex encoded strings
    */
    function rawMD5 (s) {
        return rstrMD5(str2rstrUTF8(s))
    }
    function hexMD5 (s) {
        return rstr2hex(rawMD5(s))
    }
    function rawHMACMD5 (k, d) {
        return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d))
    }
    function hexHMACMD5 (k, d) {
        return rstr2hex(rawHMACMD5(k, d))
    }

    function md5 (string, key, raw) {
        if (!key) {
            if (!raw) {
                return hexMD5(string)
            }
            return rawMD5(string)
        }
        if (!raw) {
            return hexHMACMD5(key, string)
        }
        return rawHMACMD5(key, string)
    }

    if (typeof define === 'function' && define.amd) {
        define(function () {
            return md5
        })
    } else if (typeof module === 'object' && module.exports) {
        module.exports = md5
    } else {
        $.md5 = md5
    }
})(this)

前端密码进行加密

login.js(上面基础上)

layui.define(()=>{
    // 得到layui中封装的jquery
    let $=layui.jquery
    let layer=layui.layer
    // 给登录按钮设置事件
    $(login).click(()=>{
        // 取到表单的值
        let mobile = $("#mobile").val();
        let password=$("#password").val();
        // 前端加密的盐
        let salt= "f1g2h3j4";
        if(password){
            // 将密码和盐混在一起
            password=salt.charAt(1) + "" + salt.charAt(5) + password + salt.charAt(0) + "" + salt.charAt(3);
            // 进行MD5加密
            password=md5(password)
        }
        console.log(password)
        // 将数据给后台(前后端分离axios,普通开发ajax)
        $.ajax({
            url:"/user/login",//后台登录接口
            data:{
                // 需要携带的数据
                mobile,
                password
            },
            datatype: "json",//后端给你的数据类型
            success(e){
                // 成功的回调函数
                layer.msg(e.message,{icon: 6});
            },
            error(e){
                // 报错的回调函数

            }

        })
    })


})

 

 七、全局异常抓获

1、创建validate文件下自定义注解

IsMobile
package com.code.seckill.util.validate;

import com.code.seckill.util.response.ResponseResultCode;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@SuppressWarnings("all")
@Documented
@Constraint(
        validatedBy = {IsMobileValidator.class}
)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsMobile {

    ResponseResultCode code() default ResponseResultCode.UNKNOWN;

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}
IsRequired
package com.code.seckill.util.validate;

import com.code.seckill.util.response.ResponseResultCode;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@SuppressWarnings("all")
@Documented
@Constraint(
        validatedBy = {IsRequiredValidator.class}
)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsRequired {

    ResponseResultCode code() default ResponseResultCode.UNKNOWN;

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

加入各自的解析器

IsMobileValidator
package com.code.seckill.util.validate;

import com.code.seckill.util.ValidatorUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

@SuppressWarnings("all")
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {

    @Override
    public boolean isValid(String mobile, ConstraintValidatorContext context) {
        return ValidatorUtils.isMobile(mobile);
    }

}
IsRequiredValidator
package com.code.seckill.util.validate;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

@SuppressWarnings("all")
@Slf4j
public class IsRequiredValidator implements ConstraintValidator<IsRequired, String> {

    @Override
    public boolean isValid(String str, ConstraintValidatorContext context) {
        return StringUtils.isNotBlank(str);
    }

}

2、给Vo类加上注解(解析器)

package com.code.seckill.vo;

import com.code.seckill.util.response.ResponseResultCode;
import com.code.seckill.util.validate.IsMobile;
import com.code.seckill.util.validate.IsRequired;
import lombok.Data;

@Data
public class UserVo {
    //手机号
    @IsMobile(code = ResponseResultCode.USER_ACCOUNT_NOT_FIND)
    private String mobile;
    //密码
    @IsRequired(code = ResponseResultCode.USER_CREDENTIAL_NOT_BE_EMPTY)
    private String password;
}

3、创建response文件下的ThrowableAdvice(获取异常类)

package com.code.seckill.util.response;

import com.code.seckill.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@SuppressWarnings("all")
@RestControllerAdvice
@Slf4j
public class ThrowableAdvice {

    @JsonResponseResult
    @ExceptionHandler(value = {BusinessException.class})
    public ResponseResultCode globalBusinessException(Model m, Exception e) {
        log.error(e.toString());
        return ((BusinessException) e).getResponseResultCode();
    }

    @JsonResponseResult
    @ExceptionHandler(value = {BindException.class})
    public ResponseResultCode globalBindException(Model m, Exception e) {
        log.error(e.toString());
        BindException error = (BindException) e;
        return (ResponseResultCode) error.getFieldError().getArguments()[1];
    }

    @JsonResponseResult
    @ExceptionHandler(value = {Throwable.class})
    public ResponseResultCode globalException(Model m, Exception e) {
        log.error(e.toString());
        return ResponseResultCode.UNKNOWN;
    }

}

4、在UserController类方法中加入注解开启jsr303验证

@Valid

5、在实现类抛出异常

package com.code.seckill.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.code.seckill.exception.BusinessException;
import com.code.seckill.mapper.UserMapper;
import com.code.seckill.pojo.User;
import com.code.seckill.service.IUserService;
import com.code.seckill.util.MD5Utils;
import com.code.seckill.util.ValidatorUtils;
import com.code.seckill.util.response.ResponseResult;
import com.code.seckill.util.response.ResponseResultCode;
import com.code.seckill.vo.UserVo;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author xhy-gitHub
 * @since 2022-03-15
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public ResponseResult<?> findByAccount(UserVo userVo) {
//        先判断信息是否符合(账号是否是手机号码,密码是不是空)
        if(!ValidatorUtils.isMobile(userVo.getMobile())){
            throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE);
        }
        if(StringUtils.isBlank(userVo.getPassword())){
            throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH);
        }
//         再去数据库查出对应的用户(mobile)
        User user=this.getOne(new QueryWrapper<User>().eq("id",userVo.getMobile()));
        if(user==null){
            throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_FIND);
        }
//        比较密码
//        二重加密(前端->后端,后端->数据库)
        String salt=user.getSalt();
//        将前台的加密密码和后端的盐再次进行加密
        String newPassword=MD5Utils.formPassToDbPass(userVo.getPassword(),salt);
        if(!newPassword.equals(user.getPassword())){
            throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH);
        }
//        修改最后的登录时间
        this.update(new UpdateWrapper<User>().eq("id",userVo.getMobile()).set("last_login_date",new Date()).setSql("login_count=login_count+1"));
        return ResponseResult.success();
    }
}

密码错误时不会报错

登陆模块完成!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值