赞
踩
秒杀无论是双十一购物还是 12306 抢票,秒杀场景已随处可见。简单来说,秒杀就是在同一时刻大量请求争抢购买同一商品并完成交易的过程。从架构视角来看,秒杀系统本质是一个高性能、高一致、高可用的三高系统。
通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动
- 高并发:秒杀的特点就是这样时间极短、 瞬间用户量大。
- 库存量少:一般秒杀活动商品量很少,这就导致了只有极少量用户能成功购买到。
- 业务简单:流程比较简单,一般都是下订单、扣库存、支付订单
- 恶意请求,数据库压力大
- 秒杀的商品不需要添加到购物车
- 秒杀系统独立部署
前端:Freemarker、LayUI、jQuery
后端:SpringBoot、MyBatisPlus、Lombok
中间件:RabbitMQ、Redis(redisson)
分布式协调框架:zookeeper
1.安全优化:隐藏秒杀地址、验证码、接口限流
2.服务优化:RabbitMQ消息队列、接口优化、分布式锁
3.页面优化:缓存、静态化分离
4.分布式会话:用户登录、共享session
5.功能开发:商品列表、商品详情、秒杀、订单详情
6.系统压测:JMeter入门、自定义变量、压测
导入sql代码
连接到IDEA
- <?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>

- 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

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

登录密码的加密
用户登录的时候,登录信息由前端传递到后端是通过http协议进行传输的,由于http协议是明文传输的。所有的登录信息都在其body里面,所以将用户的登录密码进行明文传输安全性较低。所以在传输之前,我们在前端需要对password进行加密,通过MD5进行对称加密。
整个加密过程分为两次MD5加密:
(1)客户端:PASS = MD5(明文)
(2)服务端:PASS = MD5(用户输入+随机salt)MD5加密的原理:MD5 (Message -digest algorithm 5)就是信息摘要的一种实现,它可以从任意长度的明文字符串生成128位的哈希值,也就是32位的16进制。
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.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加密
* 用户端:password=MD5(明文+固定Salt)
* 服务端:password=MD5(用户输入+随机Salt)
* 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。
加入工具类: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);
- }
- }

用于前后端传值:
- package com.code.seckill.vo;
-
- import lombok.Data;
-
- @Data
- public class UserVo {
- //手机号
- private String mobile;
- //密码
- private String password;
- }
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);
- }
-
- }

将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){
- // 报错的回调函数
-
- }
-
- })
- })
-
-
- })

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

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

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

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

密码错误时不会报错
登陆模块完成!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。