├── .gitignore ├── README.md ├── jmeter ├── goods_to_list.jmx ├── tokens.txt └── user_config.txt ├── pom.xml ├── sql └── miaosha.sql └── src ├── main ├── java │ └── top │ │ └── imyzt │ │ └── study │ │ └── miaosha │ │ ├── MiaoshaApplication.java │ │ ├── ServletApplication.java │ │ ├── access │ │ ├── AccessInterceptor.java │ │ ├── AccessLimit.java │ │ └── UserContext.java │ │ ├── common │ │ ├── Constant.java │ │ └── redis │ │ │ ├── AbstractPrefix.java │ │ │ ├── AccessKey.java │ │ │ ├── GoodsKey.java │ │ │ ├── KeyPrefix.java │ │ │ ├── MiaoshaKey.java │ │ │ ├── MiaoshaUserKey.java │ │ │ ├── OrderKey.java │ │ │ └── UserKey.java │ │ ├── config │ │ ├── mq │ │ │ └── RabbitMqConfig.java │ │ ├── redis │ │ │ ├── JedisConfigution.java │ │ │ └── RedisConfig.java │ │ └── web │ │ │ ├── UserArgumentResolver.java │ │ │ └── WebMvcConfig.java │ │ ├── controller │ │ ├── GoodsController.java │ │ ├── LoginController.java │ │ ├── MiaoshaController.java │ │ ├── OrderController.java │ │ └── UserController.java │ │ ├── domain │ │ ├── Goods.java │ │ ├── MiaoshaGoods.java │ │ ├── MiaoshaOrder.java │ │ ├── MiaoshaUser.java │ │ ├── OrderInfo.java │ │ └── User.java │ │ ├── exception │ │ ├── GlobalException.java │ │ └── GlobalExceptionHandler.java │ │ ├── mapper │ │ ├── GoodsMapper.java │ │ ├── MiaoshaUserMapper.java │ │ ├── OrderMapper.java │ │ └── UserMapper.java │ │ ├── rabbitmq │ │ ├── MqReceiver.java │ │ ├── MqSender.java │ │ └── dto │ │ │ └── MiaoshaMessage.java │ │ ├── result │ │ ├── CodeMsg.java │ │ └── Result.java │ │ ├── service │ │ ├── GoodsService.java │ │ ├── MiaoshaService.java │ │ ├── MiaoshaUserService.java │ │ ├── OrderService.java │ │ ├── RedisService.java │ │ └── UserService.java │ │ ├── utils │ │ ├── ConvertUtil.java │ │ ├── MD5Util.java │ │ ├── UUIDUtil.java │ │ └── ValidatorUtil.java │ │ ├── validator │ │ ├── IsMobile.java │ │ └── IsMobileValidator.java │ │ └── vo │ │ ├── GoodsDetailVo.java │ │ ├── GoodsVo.java │ │ ├── LoginVo.java │ │ └── OrderDetailVo.java └── resources │ ├── application.yml │ ├── static │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.css.map │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap-theme.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.min.js │ │ │ └── npm.js │ ├── favicon.png │ ├── goods_detail.htm │ ├── img │ │ ├── iphonex.png │ │ └── meta10.png │ ├── jquery-validation │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.min.js │ │ └── localization │ │ │ └── messages_zh.min.js │ ├── js │ │ ├── common.js │ │ ├── jquery.min.js │ │ └── md5.min.js │ ├── layer │ │ ├── layer.js │ │ ├── mobile │ │ │ ├── layer.js │ │ │ └── need │ │ │ │ └── layer.css │ │ └── skin │ │ │ └── default │ │ │ ├── icon-ext.png │ │ │ ├── icon.png │ │ │ ├── layer.css │ │ │ ├── loading-0.gif │ │ │ ├── loading-1.gif │ │ │ └── loading-2.gif │ └── order_detail.htm │ └── templates │ ├── goods_detail.html │ ├── goods_list.html │ ├── hello.html │ ├── login.html │ ├── miaosha_fail.html │ ├── order_detail.htm │ └── order_detail.html └── test └── java └── top └── imyzt └── study └── miaosha ├── MiaoshaApplicationTests.java └── service └── MiaoshaUserServiceTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | .mvn/ 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imooc_miaosha 2 | 该项目是本人学习慕课网[Java秒杀系统方案优化 高性能高并发实战](https://coding.imooc.com/class/chapter/168.html) 3 | 后上传的源代码以及课程笔记. 4 | 5 | # 目录结构 6 | 7 | ├─jmeter 存放jmeter测试文件 8 | ├─sql 存放sql脚本 9 | ├─src 10 | │ ├─main 存放项目源代码 11 | │ ├─test 存放单元测试 12 | 13 | # 章节笔记 14 | ## 第1章-课程介绍及项目框架搭建 15 | 16 | ### 知识点 17 | - 使用spring boot 搭建项目基础框架 18 | - 使用Thymeleaf做页面展示,封装Result统一结果 19 | - 集成 mybatis + druid 做数据操作 20 | - 继承redis, 使用Jedis操作redis数据, 封装了统一的缓存key. 21 | 22 | ## 第2章-实现用户登录以及分布式session功能 23 | 24 | > 学习了自己通过 `cookie` 实现分布式session, 不使用spring boot默认提供的 25 | 26 | ### 知识点 27 | - 数据库设计 28 | - 明文密码两次md5处理 29 | - `JSR303`参数校验 + 全局异常处理器 30 | - 分布式session, 通过cookie完成分布式session功能. 将cookie存放到redis中. 31 | 32 | ## 第3章-秒杀功能开发及管理后台 33 | > 学习到了基础的商品下单流程, 查库存,查是否重复下单,减库存,创建订单等过程 34 | 35 | ### 知识点 36 | 1. 数据库设计(业务第一原则, 无需太注意三大范式) 37 | 2. 完成商品列表页 38 | 3. 完成商品详情页 39 | 4. 完成订单详情页 40 | 41 | ## 第4章-秒杀压测-Jmeter压力测试 42 | > 学习使用jmeter做压力测试, 学习jmeter变量定义, redis压力测试工具 43 | 44 | ### 知识点 45 | 1. jemter入门 46 | 2. 自定义变量模拟多用户(线程组 -> 添加 -> 配置元件 -> CVS数据文件设置) 47 | ![](http://ww1.sinaimg.cn/large/005SWfHCgy1g1ai04c47uj30zk0k0jtl.jpg) 48 | 3. jmeter在命令行下的使用. 49 | - 下载tgz,解压缩并替换windows视窗下面配置的CVS信息路径 50 | - 执行脚本 51 | ```bash 52 | ./bin/jmeter.sh -n -t xxx.jmx -l result.jtl 53 | ``` 54 | - 完成后可以将result下载下来导入到 `聚合报告` 中查看 55 | 4. spring boot 打war包. 56 | 1. 添加 plugins 插件 57 | ```xml 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-war-plugin 62 | 63 | false 64 | 65 | 66 | ``` 67 | 2. 添加tomcat依赖 68 | ```xml 69 | 70 | org.springframework.boot 71 | spring-boot-starter-tomcat 72 | provided 73 | 74 | ``` 75 | 3. 编写启动类 76 | ```java 77 | public class ServletApplication extends SpringBootServletInitializer { 78 | @Override 79 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 80 | return builder.sources(ServletApplication.class); 81 | } 82 | } 83 | ``` 84 | 85 | ## 第5章-页面级高并发秒杀优化(Redis缓存+静态化分离) 86 | 87 | > 将to_list页面加入到redis缓存中, 60秒有效期. 能够有效的降低商品列表页查询数据库的次数. 对业务也不会产生大的影响. 88 | 89 | ### 知识点 90 | 1. 页面缓存,URL缓存, 页面静态化, 为秒杀订单表添加唯一索引防止同一用户多次下单 91 | 2. 页面静态化, 前后端分离 92 | 3. 静态资源优化 93 | 4. CDN优化 94 | 95 | ## 第6章-服务级高并发秒杀优化(RabbitMQ+接口优化) 96 | > 系统加载的时候讲秒杀商品缓存在redis中. 后续秒杀时直接预减redis中的商品总数. 97 | 系统秒杀时返回排队, 98 | 99 | ### 知识点 100 | 1. redis预减缓存,减少对数据库访问 101 | 2. 内存标记减少redis的访问 102 | 3. RabbitMQ队列缓存, 异步下单, 增强用户体验, 削峰. 103 | 4. RabbitMQ的安装与SpringBoot继承 104 | 5. 访问NGINX水平扩展(LVS) 105 | 106 | ### RabbitMQ的四种模式 107 | linux安装rabbitmq 108 | rabbitmq的四种模式: 109 | - direct模式,监听队列就能收到消息 110 | - topic模式,类似于订阅, 通过Binding将交换机和队列绑定,通过routing key匹配, 能匹配到的收到消息 111 | - fanout模式,广播, 通过Binding将交换机和队列绑定,所有队列都能收到消息.相当于匹配所有的routing key 112 | - headers模式,通过绑定交换机和队列, 匹配 `headers` 的 `all` 或者 `any`.将消息发送出去 113 | 114 | 可以简单的理解 `direct` 是直接发向队列 115 | `fanout` 是直接发向所有 **绑定了** 的队列 116 | `topic`,`headers`是通过绑定交换机和队列,然后匹配绑定时的规则后才发向符合规则的队列 117 | 后面三种都是先发向交换机,然后通过绑定队列时的各种规则匹配到指定的队列. 118 | 119 | 120 | ### 秒杀业务时尽最大可能减少数据库访问 121 | 122 | 秒杀减少数据库访问的思路: 123 | 1. 系统初始化时, 把商品库存加入到redis中. 124 | 2. 收到请求, redis减库存, 库存不足时, 直接返回, 否则进入3 125 | 3. 请求入队, 返回排队中.(请求削峰) 126 | 127 | to_lilst页面缓存有60s, 所以如果秒杀完成退回首页,显示的可能还是秒杀前的总量. 128 | 129 | ## 第7章-图形验证码及恶意防刷 130 | 131 | ### 学习总结 132 | 1. 学到了怎么样将秒杀地址进行隐藏. 每次点击 `秒杀` 先通过 `path` 接口拿到一个随机生成的 md5 值, 该值 **60s内有效**, 133 | 将该值加入到 `do_miaosha` 接口用于秒杀下单. 134 | 1. 通过 拦截器+注解 实现对特定接口(加了注解的接口)做请求次数判断. **一定时间内, 只允许该用户访问该接口多少次** 135 | 136 | ### 知识点 137 | 1. 生成验证码, 点击秒杀接口前需要先填写验证码结果. 138 | 2. 点击秒杀按钮, 首先通过 `path` 接口生成一个随机的md5字符, 用于访问真正的秒杀接口. 139 | 3. path接口判断该用户 `一定时间段` 内的访问次数. 防止恶意刷请求. 140 | 4. 增加注解, 为需要登录, 访问限制的接口添加拦截器. 141 | 142 | 143 | # 回顾(总结) 144 | 1. 通过 `COOKIE` 实现分布式session 145 | 2. JMeter的基础使用 146 | 3. 页面缓存(将页面缓存到Redis中) 147 | 4. 秒杀请求通过Redis预减库存,下单操作放入RabbitMQ中,消费端拿到秒杀数据后 `从容的` 做秒杀 148 | 5. 排队中. 秒杀请求通过RabbitMQ消费端处理,前端轮训判断后台是否已经完成秒杀. 是否秒杀到了 149 | 6. 复习了RabbitMQ的四种模式. 150 | 7. 隐藏秒杀地址,每次点击 `秒杀` 先通过 `path` 接口拿到一个随机生成的 md5 值, 该值 **60s内有效** 151 | 8. 请求限制, **一定时间内, 只允许该用户访问该接口多少次** 152 | 9. **重点:** 学习了`HandlerMethodArgumentResolver`的使用. 自定义参数解析.这部分我记录在了 **[我的博客](http://blog.imyzt.top/article/61)** 153 | 154 | # 不足 155 | ## 问题1 156 | Q: 请求处理(排队中), 不应该由前端 **轮询** 后端判断是否完成秒杀 157 | A: MqReceiver#miaoshaQueueReceiver()处理完成后, 通过 `WebSocket` 将秒杀结果返回给前端订阅.减少无意义的轮询 158 | 159 | ## 问题2 160 | 代码缺少单元测试 -------------------------------------------------------------------------------- /jmeter/goods_to_list.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | continue 17 | 18 | false 19 | 10 20 | 21 | 5000 22 | 0 23 | false 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 127.0.0.1 33 | 8080 34 | http 35 | 36 | 37 | 6 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | goods/to_list 51 | GET 52 | true 53 | false 54 | true 55 | false 56 | 57 | 58 | 59 | 60 | 61 | 62 | false 63 | 64 | saveConfig 65 | 66 | 67 | true 68 | true 69 | true 70 | 71 | true 72 | true 73 | true 74 | true 75 | false 76 | true 77 | true 78 | false 79 | false 80 | false 81 | true 82 | false 83 | false 84 | false 85 | true 86 | 0 87 | true 88 | true 89 | true 90 | true 91 | true 92 | true 93 | 94 | 95 | 96 | 97 | 98 | 99 | false 100 | 101 | saveConfig 102 | 103 | 104 | true 105 | true 106 | true 107 | 108 | true 109 | true 110 | true 111 | true 112 | false 113 | true 114 | true 115 | false 116 | false 117 | false 118 | true 119 | false 120 | false 121 | false 122 | true 123 | 0 124 | true 125 | true 126 | true 127 | true 128 | true 129 | true 130 | 131 | 132 | 133 | 134 | 135 | 136 | false 137 | 138 | saveConfig 139 | 140 | 141 | true 142 | true 143 | true 144 | 145 | true 146 | true 147 | true 148 | true 149 | false 150 | true 151 | true 152 | false 153 | false 154 | false 155 | true 156 | false 157 | false 158 | false 159 | true 160 | 0 161 | true 162 | true 163 | true 164 | true 165 | true 166 | true 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | false 177 | ${userToken} 178 | = 179 | true 180 | token 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | user/info 189 | GET 190 | true 191 | false 192 | true 193 | false 194 | 195 | 196 | 197 | 198 | 199 | 200 | , 201 | 202 | D:/dev/imyzt/miaosha/jmeter/tokens.txt 203 | false 204 | false 205 | true 206 | shareMode.all 207 | false 208 | userId,token 209 | 210 | 211 | 212 | 213 | 214 | 215 | false 216 | 2 217 | = 218 | true 219 | goodsId 220 | 221 | 222 | false 223 | ${token} 224 | = 225 | true 226 | token 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | miaosha/do_miaosha 235 | POST 236 | true 237 | false 238 | true 239 | false 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /jmeter/user_config.txt: -------------------------------------------------------------------------------- 1 | 12222222222,d54238dabfa049dda2a45d0fd6aa9851 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.3.RELEASE 9 | 10 | 11 | top.imyzt.study 12 | miaosha 13 | 0.0.1-SNAPSHOT 14 | jar 15 | miaosha 16 | 秒杀项目学习 17 | 18 | 19 | 1.8 20 | 21 | 22 | 23 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-thymeleaf 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.mybatis.spring.boot 37 | mybatis-spring-boot-starter 38 | 2.0.0 39 | 40 | 41 | 42 | mysql 43 | mysql-connector-java 44 | runtime 45 | 46 | 47 | com.alibaba 48 | druid-spring-boot-starter 49 | 1.1.9 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-test 54 | test 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | 60 | 61 | redis.clients 62 | jedis 63 | 64 | 65 | com.alibaba 66 | fastjson 67 | 1.2.83 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-configuration-processor 72 | 73 | 74 | org.apache.commons 75 | commons-lang3 76 | 77 | 78 | commons-codec 79 | commons-codec 80 | 81 | 82 | cn.hutool 83 | hutool-all 84 | 5.8.21 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-starter-validation 89 | 90 | 91 | org.springframework.boot 92 | spring-boot-starter-amqp 93 | 94 | 95 | 100 | 101 | 102 | 103 | ${project.artifactId} 104 | 105 | 106 | org.springframework.boot 107 | spring-boot-maven-plugin 108 | 109 | 110 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/MiaoshaApplication.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /** 8 | * @author imyzt 9 | */ 10 | @SpringBootApplication 11 | @MapperScan("top.imyzt.study.miaosha.mapper") 12 | public class MiaoshaApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(MiaoshaApplication.class, args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/ServletApplication.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | /** 7 | * @author imyzt 8 | * @date 2019/3/15 11:44 9 | * @description tomcat启动引导类 10 | */ 11 | public class ServletApplication extends SpringBootServletInitializer { 12 | 13 | @Override 14 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 15 | return builder.sources(ServletApplication.class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/access/AccessInterceptor.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.access; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.method.HandlerMethod; 8 | import org.springframework.web.servlet.ModelAndView; 9 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 10 | import top.imyzt.study.miaosha.common.redis.AccessKey; 11 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 12 | import top.imyzt.study.miaosha.result.CodeMsg; 13 | import top.imyzt.study.miaosha.result.Result; 14 | import top.imyzt.study.miaosha.service.MiaoshaUserService; 15 | import top.imyzt.study.miaosha.service.RedisService; 16 | 17 | import javax.servlet.ServletOutputStream; 18 | import javax.servlet.http.Cookie; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | import java.util.Objects; 23 | 24 | /** 25 | * @author imyzt 26 | * @date 2019/3/21 15:31 27 | * @description AccessInterceptor 28 | */ 29 | @Component 30 | public class AccessInterceptor extends HandlerInterceptorAdapter { 31 | 32 | private final MiaoshaUserService userService; 33 | private final RedisService redisService; 34 | 35 | @Autowired 36 | public AccessInterceptor(MiaoshaUserService userService, RedisService redisService) { 37 | this.userService = userService; 38 | this.redisService = redisService; 39 | } 40 | 41 | @Override 42 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 43 | 44 | if (handler instanceof HandlerMethod) { 45 | HandlerMethod hm = (HandlerMethod) handler; 46 | 47 | MiaoshaUser miaoshaUser = getUser(request, response); 48 | UserContext.setUser(miaoshaUser); 49 | 50 | AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class); 51 | 52 | // 方法没有注解 53 | if (Objects.isNull(accessLimit)) { 54 | return true; 55 | } 56 | 57 | int maxCount = accessLimit.maxCount(); 58 | boolean needLogin = accessLimit.needLogin(); 59 | int seconds = accessLimit.seconds(); 60 | String key = request.getRequestURI(); 61 | 62 | if (needLogin) { 63 | if (Objects.isNull(miaoshaUser)) { 64 | render(response, CodeMsg.SERVER_ERROR); 65 | return false; 66 | } 67 | key += "_" + miaoshaUser.getId(); 68 | } 69 | 70 | AccessKey accessKey = AccessKey.withExpireSeconds(seconds); 71 | Integer currentCount = redisService.get(accessKey, key, Integer.class); 72 | if (null == currentCount) { 73 | redisService.set(accessKey, key, 1); 74 | } else if (currentCount < maxCount) { 75 | redisService.incr(accessKey, key); 76 | } else { 77 | render(response, CodeMsg.ACCESS_LIMIT_REACHED); 78 | return false; 79 | } 80 | } 81 | return true; 82 | } 83 | 84 | /** 85 | * 向响应流写数据.返回前端错误具体原因 86 | */ 87 | private void render(HttpServletResponse response, CodeMsg codeMsg) throws IOException { 88 | response.setContentType("application/json;charset=UTF-8"); 89 | response.setCharacterEncoding("UTF-8"); 90 | ServletOutputStream outputStream = response.getOutputStream(); 91 | String str = JSON.toJSONString(Result.error(codeMsg)); 92 | outputStream.write(str.getBytes("UTF-8")); 93 | outputStream.flush(); 94 | outputStream.close(); 95 | } 96 | 97 | @Override 98 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 99 | super.postHandle(request, response, handler, modelAndView); 100 | } 101 | 102 | @Override 103 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 104 | super.afterCompletion(request, response, handler, ex); 105 | } 106 | 107 | private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) { 108 | 109 | String paramToken = request.getParameter(MiaoshaUserService.COOKIE_NAME_TOKEN); 110 | String cookieToken = getCookieValue(request, MiaoshaUserService.COOKIE_NAME_TOKEN); 111 | 112 | if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) { 113 | return null; 114 | } 115 | 116 | String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken; 117 | return userService.getByToken(response, token); 118 | } 119 | 120 | private String getCookieValue(HttpServletRequest request, String cookieName) { 121 | Cookie[] cookies = request.getCookies(); 122 | if (null != cookies && cookies.length > 0) { 123 | for (Cookie cookie : cookies) { 124 | if (cookieName.equals(cookie.getName())) { 125 | return cookie.getValue(); 126 | } 127 | } 128 | } 129 | return null; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/access/AccessLimit.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.access; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author imyzt 10 | * @date 2019/3/21 15:28 11 | * @description 访问限制, 通过拦截器实现. {@link AccessInterceptor} 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.METHOD) 15 | public @interface AccessLimit { 16 | /** 几秒内 */ 17 | int seconds(); 18 | /** 最多允许几次访问 */ 19 | int maxCount(); 20 | /** 默认需要登录 */ 21 | boolean needLogin() default true; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/access/UserContext.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.access; 2 | 3 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 4 | 5 | /** 6 | * @author imyzt 7 | * @date 2019/3/21 15:38 8 | * @description 用户信息上下文 9 | */ 10 | public class UserContext { 11 | 12 | private static final ThreadLocal USER_HOLDER = new ThreadLocal <>(); 13 | 14 | public static MiaoshaUser getUser() { 15 | return USER_HOLDER.get(); 16 | } 17 | 18 | public static void setUser(MiaoshaUser user) { 19 | USER_HOLDER.set(user); 20 | } 21 | 22 | public static void remove() { 23 | USER_HOLDER.remove(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/Constant.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/20 11:22 6 | * @description Constant 7 | */ 8 | public interface Constant { 9 | 10 | String DEFAULT_QUEUE_NAME = "queue"; 11 | 12 | String TOPIC_QUEUE_1 = "topic.queue.1"; 13 | String TOPIC_QUEUE_2 = "topic.queue.2"; 14 | String TOPIC_EXCHANGE = "topicExchange"; 15 | String ROUTING_KEY_1 = "topic.key1"; 16 | String ROUTING_KEY_2 = "topic.#"; 17 | 18 | String FANOUT_QUEUE_1 = "fanout.queue.1"; 19 | String FANOUT_QUEUE_2 = "fanout.queue.2"; 20 | String FANOUT_EXCHANGE = "fanoutExchange"; 21 | 22 | String HEADERS_QUEUE = "headers.queue"; 23 | String HEADERS_EXCHANGE = "headersExchange"; 24 | 25 | String MIAOSHA_QUEUE = "miaosha.queue"; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/redis/AbstractPrefix.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common.redis; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/9 9:20 6 | * @description AbstractPrefix 7 | */ 8 | public abstract class AbstractPrefix implements KeyPrefix { 9 | 10 | private int expireSeconds; 11 | 12 | private String prefix; 13 | 14 | AbstractPrefix(String prefix) { 15 | this(0, prefix); 16 | } 17 | 18 | AbstractPrefix(int expireSeconds, String prefix) { 19 | this.expireSeconds = expireSeconds; 20 | this.prefix = prefix; 21 | } 22 | 23 | /** 24 | * 获取过期时间 25 | * 26 | * @return expire 27 | */ 28 | @Override 29 | public int getExpireSeconds() { 30 | return this.expireSeconds; 31 | } 32 | 33 | /** 34 | * 获取key前缀 35 | * 36 | * @return key prefix 37 | */ 38 | @Override 39 | public String getPrefix() { 40 | String simpleName = getClass().getSimpleName(); 41 | return simpleName + ":" + this.prefix; 42 | } 43 | 44 | @Override 45 | public String getRealKey(String key) { 46 | return getPrefix() + key; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/redis/AccessKey.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common.redis; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/21 15:56 6 | * @description AccessKey 7 | */ 8 | public class AccessKey extends AbstractPrefix { 9 | private AccessKey(int expireSeconds, String prefix) { 10 | super(expireSeconds, prefix); 11 | } 12 | public static AccessKey withExpireSeconds(int expireSeconds) { 13 | return new AccessKey(expireSeconds, "access"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/redis/GoodsKey.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common.redis; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/18 15:34 6 | * @description GoodsKey 7 | */ 8 | public class GoodsKey extends AbstractPrefix { 9 | 10 | private GoodsKey(int expireSeconds, String prefix) { 11 | super(expireSeconds, prefix); 12 | } 13 | 14 | public static GoodsKey getGoodsList = new GoodsKey(60, "gl"); 15 | public static GoodsKey getGoodsDetail = new GoodsKey(60, "gd"); 16 | public static GoodsKey getMiaoshaGoodsStock = new GoodsKey(0, "gs"); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/redis/KeyPrefix.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common.redis; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/9 9:17 6 | * @description KeyPrefix 7 | */ 8 | public interface KeyPrefix { 9 | 10 | /** 11 | * 获取过期时间 12 | * @return expire 13 | */ 14 | int getExpireSeconds(); 15 | 16 | /** 17 | * 获取key前缀 18 | * @return key prefix 19 | */ 20 | String getPrefix(); 21 | 22 | /** 23 | * 获取真正的key, 业务key 24 | * @param key redis key 25 | * @return 真正的key 26 | */ 27 | String getRealKey(String key); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/redis/MiaoshaKey.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common.redis; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/20 18:37 6 | * @description MiaoshaKey 7 | */ 8 | public class MiaoshaKey extends AbstractPrefix { 9 | private MiaoshaKey(String prefix) { 10 | super(prefix); 11 | } 12 | 13 | private MiaoshaKey(int expireSeconds, String prefix) { 14 | super(expireSeconds, prefix); 15 | } 16 | 17 | public static final MiaoshaKey IS_GOODS_OVER = new MiaoshaKey("go"); 18 | public static final MiaoshaKey GET_MIAOSHA_PATH = new MiaoshaKey(60, "gp"); 19 | public static final MiaoshaKey GET_MIAOSHA_VERIFY_CODE = new MiaoshaKey(300, "vc"); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/redis/MiaoshaUserKey.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common.redis; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/9 9:23 6 | * @description MiaoshaUserKey 7 | */ 8 | public class MiaoshaUserKey extends AbstractPrefix { 9 | 10 | private static final int TOKEN_EXPIRE = 3600 * 24 * 2; 11 | 12 | private MiaoshaUserKey(int expireSeconds, String prefix) { 13 | super(expireSeconds, prefix); 14 | } 15 | 16 | public static MiaoshaUserKey TOKEN = new MiaoshaUserKey(TOKEN_EXPIRE, "token"); 17 | public static MiaoshaUserKey getById = new MiaoshaUserKey(0, "getById"); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/redis/OrderKey.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common.redis; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/9 9:24 6 | * @description OrderKey 7 | */ 8 | public class OrderKey extends AbstractPrefix { 9 | 10 | 11 | private OrderKey(String prefix) { 12 | super(prefix); 13 | } 14 | 15 | public static OrderKey getMiaoshaOrderByUidGid = new OrderKey("moug"); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/common/redis/UserKey.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.common.redis; 2 | 3 | /** 4 | * @author imyzt 5 | * @date 2019/3/9 9:23 6 | * @description UserKey 7 | */ 8 | public class UserKey extends AbstractPrefix { 9 | 10 | private UserKey(String prefix) { 11 | super(prefix); 12 | } 13 | 14 | public static UserKey GET_BY_ID = new UserKey("id"); 15 | public static UserKey GET_BY_NAME = new UserKey("name"); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/config/mq/RabbitMqConfig.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.config.mq; 2 | 3 | import org.springframework.amqp.core.*; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import top.imyzt.study.miaosha.common.Constant; 7 | 8 | import java.util.HashMap; 9 | 10 | /** 11 | * @author imyzt 12 | * @date 2019/3/20 11:21 13 | * @description RabbitMqConfig 14 | */ 15 | @Configuration 16 | public class RabbitMqConfig { 17 | 18 | @Bean 19 | public Queue miaoshaQueue() { 20 | return new Queue(Constant.MIAOSHA_QUEUE, true); 21 | } 22 | 23 | /** 24 | * Direct模式,exchange 25 | */ 26 | @Bean 27 | public Queue queue() { 28 | return new Queue(Constant.DEFAULT_QUEUE_NAME, true); 29 | } 30 | 31 | /** 32 | * Topic模式, exchange 33 | * 订阅模式 34 | */ 35 | @Bean 36 | public TopicExchange topicExchange() { 37 | return new TopicExchange(Constant.TOPIC_EXCHANGE); 38 | } 39 | 40 | @Bean 41 | public Queue topicQueue1() { 42 | return new Queue(Constant.TOPIC_QUEUE_1, true); 43 | } 44 | 45 | @Bean 46 | public Queue topicQueue2() { 47 | return new Queue(Constant.TOPIC_QUEUE_2, true); 48 | } 49 | 50 | /* 51 | * 通过key, 将队列和交换机进行绑定 52 | */ 53 | @Bean 54 | public Binding topicBinding1() { 55 | return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with(Constant.ROUTING_KEY_1); 56 | } 57 | 58 | @Bean 59 | public Binding topicBinding2() { 60 | return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with(Constant.ROUTING_KEY_2); 61 | } 62 | 63 | 64 | /** 65 | * Fanout模式, exchange 66 | * 广播模式 67 | */ 68 | @Bean 69 | public FanoutExchange fanoutExchange() { 70 | return new FanoutExchange(Constant.FANOUT_EXCHANGE); 71 | } 72 | 73 | @Bean Queue fanoutQueue2() { 74 | return new Queue(Constant.FANOUT_QUEUE_2, true); 75 | } 76 | 77 | @Bean Queue fanoutQueue1() { 78 | return new Queue(Constant.FANOUT_QUEUE_1, true); 79 | } 80 | 81 | @Bean 82 | public Binding fanoutBinding1() { 83 | return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange()); 84 | } 85 | 86 | @Bean 87 | public Binding fanoutBinding2() { 88 | return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange()); 89 | } 90 | 91 | 92 | /** 93 | * headers模式, exchange 94 | */ 95 | @Bean 96 | public HeadersExchange headersExchange() { 97 | return new HeadersExchange(Constant.HEADERS_EXCHANGE); 98 | } 99 | 100 | @Bean Queue headersQueue() { 101 | return new Queue(Constant.HEADERS_QUEUE, true); 102 | } 103 | 104 | @Bean 105 | public Binding headersBinding() { 106 | 107 | HashMap hashMap = new HashMap(2){{ 108 | put("header1", "value1"); 109 | put("header2", "value2"); 110 | }}; 111 | 112 | // 只有当满足key和value的时候, 才会向队列发送 113 | return BindingBuilder.bind(headersQueue()).to(headersExchange()).whereAll(hashMap).match(); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/config/redis/JedisConfigution.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.config.redis; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import redis.clients.jedis.JedisPool; 7 | import redis.clients.jedis.JedisPoolConfig; 8 | 9 | /** 10 | * @author imyzt 11 | * @date 2019/3/8 18:02 12 | * @description JedisConfigution 13 | */ 14 | @Configuration 15 | public class JedisConfigution { 16 | 17 | private final RedisConfig redisConfig; 18 | 19 | @Autowired 20 | public JedisConfigution(RedisConfig redisConfig) { 21 | this.redisConfig = redisConfig; 22 | } 23 | 24 | @Bean 25 | public JedisPoolConfig jedisPoolConfig() { 26 | JedisPoolConfig poolConfig = new JedisPoolConfig(); 27 | poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal()); 28 | poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle()); 29 | poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait()); 30 | return poolConfig; 31 | } 32 | 33 | @Bean 34 | public JedisPool jedisPool(JedisPoolConfig poolConfig) { 35 | String password = "".equals(redisConfig.getPassword()) ? null : redisConfig.getPassword(); 36 | return new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(), 37 | redisConfig.getTimeout(), password, redisConfig.getDatabase()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/config/redis/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.config.redis; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * @author imyzt 9 | * @date 2019/3/8 17:59 10 | * @description RedisConfig 11 | */ 12 | @ConfigurationProperties("redis") 13 | @Data 14 | @Component 15 | public class RedisConfig { 16 | private String host; 17 | private Integer port; 18 | /** 19 | * s 20 | */ 21 | private Integer timeout; 22 | private String password; 23 | private Integer poolMaxTotal; 24 | private Integer poolMaxIdle; 25 | private Integer poolMaxWait; 26 | private Integer database; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/config/web/UserArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.config.web; 2 | 3 | import org.springframework.core.MethodParameter; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.bind.support.WebDataBinderFactory; 6 | import org.springframework.web.context.request.NativeWebRequest; 7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 8 | import org.springframework.web.method.support.ModelAndViewContainer; 9 | import top.imyzt.study.miaosha.access.UserContext; 10 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 11 | 12 | /** 13 | * @author imyzt 14 | * @date 2019/3/9 16:53 15 | * @description 实现此方法, 将自动对方法入参包含 {@link MiaoshaUser} 的对象的进行注入 16 | */ 17 | @Component 18 | public class UserArgumentResolver implements HandlerMethodArgumentResolver { 19 | 20 | @Override 21 | public boolean supportsParameter(MethodParameter parameter) { 22 | Class parameterType = parameter.getParameterType(); 23 | return parameterType == MiaoshaUser.class; 24 | } 25 | 26 | @Override 27 | public Object resolveArgument(MethodParameter parameter, 28 | ModelAndViewContainer mavContainer, 29 | NativeWebRequest webRequest, 30 | WebDataBinderFactory binderFactory) { 31 | return UserContext.getUser(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/config/web/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.config.web; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 6 | import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; 10 | import top.imyzt.study.miaosha.access.AccessInterceptor; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * @author imyzt 16 | * @date 2019/3/9 16:52 17 | * @description WebMvcConfig 18 | */ 19 | @Configuration 20 | public class WebMvcConfig extends WebMvcConfigurationSupport{ 21 | 22 | private final AccessInterceptor accessInterceptor; 23 | private final UserArgumentResolver userArgumentResolver; 24 | 25 | @Autowired 26 | public WebMvcConfig(AccessInterceptor accessInterceptor, 27 | UserArgumentResolver userArgumentResolver) { 28 | this.accessInterceptor = accessInterceptor; 29 | this.userArgumentResolver = userArgumentResolver; 30 | } 31 | 32 | @Override 33 | protected void addArgumentResolvers(List argumentResolvers) { 34 | argumentResolvers.add(userArgumentResolver); 35 | } 36 | 37 | @Override 38 | protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) { 39 | configurer.favorPathExtension(false); 40 | } 41 | 42 | @Override 43 | protected void addResourceHandlers(ResourceHandlerRegistry registry) { 44 | registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); 45 | } 46 | 47 | @Override 48 | protected void addInterceptors(InterceptorRegistry registry) { 49 | registry.addInterceptor(accessInterceptor); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/controller/GoodsController.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.*; 9 | import org.thymeleaf.context.WebContext; 10 | import org.thymeleaf.spring5.view.ThymeleafViewResolver; 11 | import top.imyzt.study.miaosha.common.redis.GoodsKey; 12 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 13 | import top.imyzt.study.miaosha.result.Result; 14 | import top.imyzt.study.miaosha.service.GoodsService; 15 | import top.imyzt.study.miaosha.service.RedisService; 16 | import top.imyzt.study.miaosha.vo.GoodsDetailVo; 17 | import top.imyzt.study.miaosha.vo.GoodsVo; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.time.Instant; 22 | import java.util.List; 23 | 24 | /** 25 | * @author imyzt 26 | * @date 2019/3/8 17:27 27 | * @description GoodsController 28 | */ 29 | @Controller 30 | @Slf4j 31 | @RequestMapping("goods") 32 | public class GoodsController { 33 | 34 | private final GoodsService goodsService; 35 | 36 | private final RedisService redisService; 37 | 38 | private final ThymeleafViewResolver thymeleafViewResolver; 39 | 40 | @Autowired 41 | public GoodsController(GoodsService goodsService, RedisService redisService, ThymeleafViewResolver thymeleafViewResolver) { 42 | this.goodsService = goodsService; 43 | this.redisService = redisService; 44 | this.thymeleafViewResolver = thymeleafViewResolver; 45 | } 46 | 47 | /** 48 | * QPS 5335 49 | */ 50 | @GetMapping(value = "/to_list", produces = "text/html;charset=UTF-8") 51 | public @ResponseBody String toList(HttpServletRequest request, HttpServletResponse response, 52 | Model model, MiaoshaUser user) { 53 | 54 | // 取缓存 55 | String html = redisService.get(GoodsKey.getGoodsList, "", String.class); 56 | if (StringUtils.isNotEmpty(html)) { 57 | return html; 58 | } 59 | 60 | List goodsVos = goodsService.listGoodsVo(); 61 | 62 | model.addAttribute("user", user); 63 | model.addAttribute("goodsList", goodsVos); 64 | 65 | // 手动渲染 66 | WebContext webContext = new WebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap()); 67 | html = thymeleafViewResolver.getTemplateEngine().process("goods_list", webContext); 68 | 69 | if (StringUtils.isNotEmpty(html)) { 70 | redisService.set(GoodsKey.getGoodsList, "", html); 71 | } 72 | return html; 73 | } 74 | 75 | @GetMapping(value = "/detail/{goodsId}") 76 | public @ResponseBody Result getDetail(MiaoshaUser user, @PathVariable Long goodsId) { 77 | 78 | // 手动渲染 79 | GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId); 80 | 81 | long startTme = goodsVo.getStartDate().getTime(); 82 | long endTime = goodsVo.getEndDate().getTime(); 83 | long now = Instant.now().getEpochSecond() * 1000; 84 | 85 | int miaoshaStatus; 86 | int remainSeconds; 87 | 88 | // 还没开始 89 | if (now < startTme) { 90 | miaoshaStatus = 0; 91 | remainSeconds = (int) (startTme - now) / 1000; 92 | // 已结束 93 | } else if (now > endTime) { 94 | miaoshaStatus = 2; 95 | remainSeconds = -1; 96 | } else { 97 | miaoshaStatus = 1; 98 | remainSeconds = 0; 99 | } 100 | 101 | GoodsDetailVo detail = new GoodsDetailVo(); 102 | detail.setGoods(goodsVo); 103 | detail.setMiaoshaStatus(miaoshaStatus); 104 | detail.setRemainSeconds(remainSeconds); 105 | detail.setUser(user); 106 | 107 | return Result.success(detail); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | import top.imyzt.study.miaosha.domain.User; 12 | import top.imyzt.study.miaosha.result.CodeMsg; 13 | import top.imyzt.study.miaosha.result.Result; 14 | import top.imyzt.study.miaosha.service.MiaoshaUserService; 15 | import top.imyzt.study.miaosha.utils.ValidatorUtil; 16 | import top.imyzt.study.miaosha.vo.LoginVo; 17 | 18 | import javax.servlet.http.HttpServletResponse; 19 | import javax.validation.Valid; 20 | 21 | /** 22 | * @author imyzt 23 | * @date 2019/3/8 17:27 24 | * @description LoginController 25 | */ 26 | @Controller 27 | @Slf4j 28 | @RequestMapping("login") 29 | public class LoginController { 30 | 31 | private final MiaoshaUserService userService; 32 | 33 | @Autowired 34 | public LoginController(MiaoshaUserService userService) { 35 | this.userService = userService; 36 | } 37 | 38 | @GetMapping({"/login"}) 39 | public String login() { 40 | return "login"; 41 | } 42 | 43 | @PostMapping("/do_login") 44 | @ResponseBody 45 | public Result login(HttpServletResponse response, @Valid LoginVo loginVo) { 46 | boolean login = userService.login(response, loginVo); 47 | return Result.success(true); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/controller/MiaoshaController.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.controller; 2 | 3 | import cn.hutool.core.util.ImageUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.*; 9 | import top.imyzt.study.miaosha.access.AccessLimit; 10 | import top.imyzt.study.miaosha.common.redis.GoodsKey; 11 | import top.imyzt.study.miaosha.domain.MiaoshaOrder; 12 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 13 | import top.imyzt.study.miaosha.rabbitmq.MqSender; 14 | import top.imyzt.study.miaosha.rabbitmq.dto.MiaoshaMessage; 15 | import top.imyzt.study.miaosha.result.CodeMsg; 16 | import top.imyzt.study.miaosha.result.Result; 17 | import top.imyzt.study.miaosha.service.GoodsService; 18 | import top.imyzt.study.miaosha.service.MiaoshaService; 19 | import top.imyzt.study.miaosha.service.OrderService; 20 | import top.imyzt.study.miaosha.service.RedisService; 21 | import top.imyzt.study.miaosha.vo.GoodsVo; 22 | 23 | import javax.imageio.ImageIO; 24 | import javax.servlet.ServletOutputStream; 25 | import javax.servlet.http.HttpServletResponse; 26 | import java.awt.image.BufferedImage; 27 | import java.io.IOException; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.concurrent.ConcurrentHashMap; 31 | 32 | /** 33 | * @author imyzt 34 | * @date 2019/3/13 18:12 35 | * @description MiaoshaController 36 | */ 37 | @Controller 38 | @RequestMapping("miaosha") 39 | @Slf4j 40 | public class MiaoshaController implements InitializingBean { 41 | 42 | private final GoodsService goodsService; 43 | 44 | private final RedisService redisService; 45 | 46 | private final MiaoshaService miaoshaService; 47 | 48 | private final MqSender sender; 49 | 50 | private final OrderService orderService; 51 | 52 | private static final Map LOCAL_GOODS_MAP = new ConcurrentHashMap <>(); 53 | 54 | @Autowired 55 | public MiaoshaController(GoodsService goodsService, 56 | RedisService redisService, 57 | MiaoshaService miaoshaService, 58 | MqSender sender, 59 | OrderService orderService) { 60 | this.goodsService = goodsService; 61 | this.redisService = redisService; 62 | this.miaoshaService = miaoshaService; 63 | this.sender = sender; 64 | this.orderService = orderService; 65 | } 66 | 67 | /** 68 | * 初始化系统时, 将所有商品的库存加入到redis缓存中 69 | */ 70 | @Override 71 | public void afterPropertiesSet() { 72 | List goodsVos = goodsService.listGoodsVo(); 73 | if (null != goodsVos) { 74 | goodsVos.parallelStream().forEach(goodsVo -> { 75 | redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goodsVo.getId(), goodsVo.getStockCount()); 76 | LOCAL_GOODS_MAP.put(goodsVo.getId(), false); 77 | }); 78 | } 79 | } 80 | 81 | /** 82 | * 2.0 页面静态化处理, 前后端通过json交互 83 | * 3.0 请求入队,削峰 qps = 2130 84 | * 4.0 隐藏秒杀地址,秒杀地址超时自动失效 85 | * 5.0 增加验证码验证 86 | */ 87 | @AccessLimit(seconds = 5, maxCount = 5) 88 | @PostMapping("/{path}/do_miaosha") 89 | public @ResponseBody Result miaosha(MiaoshaUser user, Long goodsId, @PathVariable String path) { 90 | 91 | // 未登录 92 | if (null == user) { 93 | return Result.error(CodeMsg.SESSION_ERROR); 94 | } 95 | 96 | if (goodsId <= 0) { 97 | return Result.error(CodeMsg.GOODS_NOT_EXIST); 98 | } 99 | 100 | // 判断秒杀接口,60s会自动失效 101 | boolean checkMiaoshaPath = miaoshaService.checkMiaoshaPath(user, goodsId, path); 102 | if (!checkMiaoshaPath) { 103 | return Result.error(CodeMsg.REQUEST_ILLEGAL); 104 | } 105 | 106 | // 内存标记, 减少redis访问 107 | if (LOCAL_GOODS_MAP.get(goodsId)) { 108 | return Result.error(CodeMsg.MIAO_SHA_OVER); 109 | } 110 | 111 | //判断库存, 先减一再判断减一后的结果是否大于0 112 | Long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, "" + goodsId); 113 | if (null != stock && stock < 0) { 114 | LOCAL_GOODS_MAP.put(goodsId, true); 115 | return Result.error(CodeMsg.MIAO_SHA_OVER); 116 | } 117 | 118 | //判断是否已经秒杀到了 119 | MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId); 120 | if(order != null) { 121 | return Result.error(CodeMsg.REPEATE_MIAOSHA); 122 | } 123 | 124 | // 请求入队 125 | MiaoshaMessage miaoshaMessage = new MiaoshaMessage(user, goodsId); 126 | sender.miaoshaSender(miaoshaMessage); 127 | 128 | return Result.success(0); 129 | } 130 | 131 | /** 132 | * orderId 秒杀成功 133 | * -1 秒杀失败 134 | * 0 排队中 135 | */ 136 | @GetMapping("result") 137 | public @ResponseBody Result miaoshaResult(MiaoshaUser user, Long goodsId) { 138 | 139 | if (null == user) { 140 | return Result.error(CodeMsg.SESSION_ERROR); 141 | } 142 | 143 | if (goodsId <= 0) { 144 | return Result.error(CodeMsg.GOODS_NOT_EXIST); 145 | } 146 | 147 | long orderId = miaoshaService.getMiaoshaResult(user.getId(), goodsId); 148 | return Result.success(orderId); 149 | } 150 | 151 | @AccessLimit(seconds = 5, maxCount = 5) 152 | @GetMapping("path") 153 | public @ResponseBody Result getMiaoshaPath(MiaoshaUser user, Long goodsId, Integer verifyCode) { 154 | 155 | if (null == user) { 156 | return Result.error(CodeMsg.SESSION_ERROR); 157 | } 158 | 159 | if (goodsId <= 0) { 160 | return Result.error(CodeMsg.GOODS_NOT_EXIST); 161 | } 162 | 163 | // 校验验证码是否正确 164 | boolean code = miaoshaService.checkMiaoshaVerifyCode(user, goodsId, verifyCode); 165 | if (!code) { 166 | return Result.error(CodeMsg.VERIFY_CODE_FAIL); 167 | } 168 | 169 | // 生成请求地址 170 | String path = miaoshaService.createMiaoshaPath(user, goodsId); 171 | 172 | return Result.success(path); 173 | } 174 | 175 | /** 176 | * 生成验证码, 防恶意刷请求. 5s max request < 50 177 | */ 178 | @AccessLimit(seconds = 5, maxCount = 50) 179 | @GetMapping("verifyCode") 180 | public @ResponseBody Result getMiaoshaVerifyCode(HttpServletResponse response, MiaoshaUser user, Long goodsId) { 181 | 182 | if (null == user) { 183 | return Result.error(CodeMsg.SESSION_ERROR); 184 | } 185 | 186 | if (goodsId <= 0) { 187 | return Result.error(CodeMsg.GOODS_NOT_EXIST); 188 | } 189 | 190 | BufferedImage bufferedImage = miaoshaService.createMiaoshaVerifyCode(user, goodsId); 191 | 192 | try (ServletOutputStream outputStream = response.getOutputStream()) { 193 | ImageIO.write(bufferedImage, ImageUtil.IMAGE_TYPE_JPEG, outputStream); 194 | outputStream.flush(); 195 | return null; 196 | } catch (IOException e) { 197 | log.error(e.getMessage()); 198 | return Result.error(CodeMsg.MIAOSHA_FAIL); 199 | } 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 9 | import top.imyzt.study.miaosha.domain.OrderInfo; 10 | import top.imyzt.study.miaosha.result.CodeMsg; 11 | import top.imyzt.study.miaosha.result.Result; 12 | import top.imyzt.study.miaosha.service.GoodsService; 13 | import top.imyzt.study.miaosha.service.OrderService; 14 | import top.imyzt.study.miaosha.vo.GoodsVo; 15 | import top.imyzt.study.miaosha.vo.OrderDetailVo; 16 | 17 | /** 18 | * @author imyzt 19 | * @date 2019/3/19 16:36 20 | * @description OrderController 21 | */ 22 | @RequestMapping("order") 23 | @Controller 24 | public class OrderController { 25 | 26 | private final OrderService orderService; 27 | private final GoodsService goodsService; 28 | 29 | @Autowired 30 | public OrderController(OrderService orderService, GoodsService goodsService) { 31 | this.orderService = orderService; 32 | this.goodsService = goodsService; 33 | } 34 | 35 | @GetMapping("detail") 36 | public @ResponseBody Result detail(MiaoshaUser user, Long orderId) { 37 | 38 | if (null == user) { 39 | return Result.error(CodeMsg.SESSION_ERROR); 40 | } 41 | 42 | // 查询order 43 | OrderInfo orderInfo = orderService.getById(orderId); 44 | // 查询goods 45 | if (null == orderInfo) { 46 | return Result.error(CodeMsg.ORDER_NOT_EXIST); 47 | } 48 | Long goodsId = orderInfo.getGoodsId(); 49 | GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId); 50 | 51 | OrderDetailVo orderDetailVo = new OrderDetailVo(); 52 | orderDetailVo.setGoods(goodsVo); 53 | orderDetailVo.setOrder(orderInfo); 54 | 55 | return Result.success(orderDetailVo); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.controller; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 8 | import top.imyzt.study.miaosha.rabbitmq.MqSender; 9 | import top.imyzt.study.miaosha.result.Result; 10 | 11 | /** 12 | * @author imyzt 13 | * @date 2019/3/8 17:27 14 | * @description UserController 15 | */ 16 | @RestController 17 | @RequestMapping("user") 18 | public class UserController { 19 | 20 | private final MqSender sender; 21 | 22 | @Autowired 23 | public UserController(MqSender sender) { 24 | this.sender = sender; 25 | } 26 | 27 | @GetMapping("/info") 28 | public Result info(MiaoshaUser user) { 29 | return Result.success(user); 30 | } 31 | 32 | @GetMapping("mqSender") 33 | public void mqSender(String msg) { 34 | sender.sender(msg); 35 | } 36 | 37 | @GetMapping("topicSender") 38 | public void topicSender(String msg) { 39 | sender.topicSender(msg); 40 | } 41 | 42 | @GetMapping("fanoutSender") 43 | public void fanoutSender(String msg) { 44 | sender.fanoutSender(msg); 45 | } 46 | 47 | @GetMapping("headersSender") 48 | public void headersSender(String msg) { 49 | sender.headersSender(msg); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/domain/Goods.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.domain; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Goods { 7 | private Long id; 8 | private String goodsName; 9 | private String goodsTitle; 10 | private String goodsImg; 11 | private String goodsDetail; 12 | private Double goodsPrice; 13 | private Integer goodsStock; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/domain/MiaoshaGoods.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.domain; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | @Data 8 | public class MiaoshaGoods { 9 | private Long id; 10 | private Long goodsId; 11 | private Integer stockCount; 12 | private Date startDate; 13 | private Date endDate; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/domain/MiaoshaOrder.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.domain; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class MiaoshaOrder { 7 | private Long id; 8 | private Long userId; 9 | private Long orderId; 10 | private Long goodsId; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/domain/MiaoshaUser.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.domain; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * @author imyzt 9 | * @date 2019/3/9 13:59 10 | * @description MiaoshaUser 11 | */ 12 | @Data 13 | public class MiaoshaUser { 14 | 15 | private Long id; 16 | private String nickname; 17 | private String password; 18 | private String salt; 19 | private String head; 20 | private Date registerDate; 21 | private Date lastLoginDate; 22 | private Integer loginCount; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/domain/OrderInfo.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.domain; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | @Data 7 | public class OrderInfo { 8 | private Long id; 9 | private Long userId; 10 | private Long goodsId; 11 | private Long deliveryAddrId; 12 | private String goodsName; 13 | private Integer goodsCount; 14 | private Double goodsPrice; 15 | private Integer orderChannel; 16 | private Integer status; 17 | private Date createDate; 18 | private Date payDate; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/domain/User.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.domain; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author imyzt 7 | * @date 2019/3/8 17:22 8 | * @description User 9 | */ 10 | @Data 11 | public class User { 12 | private Integer id; 13 | private String name; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/exception/GlobalException.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.exception; 2 | 3 | import lombok.Getter; 4 | import top.imyzt.study.miaosha.result.CodeMsg; 5 | 6 | /** 7 | * @author imyzt 8 | * @date 2019/3/9 15:31 9 | * @description GlobalException 10 | */ 11 | @Getter 12 | public class GlobalException extends RuntimeException { 13 | 14 | private CodeMsg cm; 15 | 16 | public GlobalException(CodeMsg cm) { 17 | super(cm.toString()); 18 | this.cm = cm; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.exception; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.validation.BindException; 5 | import org.springframework.validation.ObjectError; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.RestController; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | import top.imyzt.study.miaosha.result.CodeMsg; 10 | import top.imyzt.study.miaosha.result.Result; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.util.List; 14 | 15 | /** 16 | * @author imyzt 17 | * @date 2019/3/9 15:19 18 | * @description GlobalExceptionHandler 19 | */ 20 | @RestControllerAdvice 21 | @Slf4j 22 | public class GlobalExceptionHandler { 23 | 24 | @ExceptionHandler(value = Exception.class) 25 | public Result exception(HttpServletRequest request, Exception e) { 26 | 27 | e.printStackTrace(); 28 | 29 | if (e instanceof GlobalException) { 30 | GlobalException globalException = (GlobalException) e; 31 | CodeMsg codeMsg = globalException.getCm(); 32 | return Result.error(codeMsg); 33 | } 34 | if (e instanceof BindException) { 35 | BindException bindException = (BindException) e; 36 | List allErrors = bindException.getAllErrors(); 37 | ObjectError error = allErrors.get(0); 38 | String defaultMessage = error.getDefaultMessage(); 39 | return Result.error(CodeMsg.BIND_ERROR.fillArgs(defaultMessage)); 40 | } else { 41 | return Result.error(CodeMsg.SERVER_ERROR); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/mapper/GoodsMapper.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.mapper; 2 | 3 | import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select; 4 | import org.apache.ibatis.annotations.Update; 5 | import top.imyzt.study.miaosha.domain.MiaoshaOrder; 6 | import top.imyzt.study.miaosha.vo.GoodsVo; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author imyzt 12 | * @date 2019/3/13 15:59 13 | * @description GoodsMapper 14 | */ 15 | public interface GoodsMapper { 16 | 17 | /** 18 | * 查询秒杀商品列表 19 | * @return 秒杀商品列表 20 | */ 21 | @Select("SELECT mg.miaosha_price, mg.stock_count, mg.start_date, mg.end_date, g.* " + 22 | "FROM miaosha_goods mg " + 23 | "LEFT JOIN goods g " + 24 | "ON mg.goods_id = g.id") 25 | List listGoodsVo(); 26 | 27 | @Select("SELECT mg.miaosha_price, mg.stock_count, mg.start_date, mg.end_date, g.* " + 28 | "FROM miaosha_goods mg " + 29 | "LEFT JOIN goods g " + 30 | "ON mg.goods_id = g.id " + 31 | "WHERE g.id = #{goodsId}") 32 | GoodsVo getGoodsVoByGoodsId(@Param("goodsId")long goodsId); 33 | 34 | @Update("UPDATE miaosha_goods SET stock_count = stock_count - 1 WHERE goods_id = #{goodsId} ") 35 | int reduceStock(MiaoshaOrder miaoshaOrder); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/mapper/MiaoshaUserMapper.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.mapper; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.apache.ibatis.annotations.Select; 5 | import org.apache.ibatis.annotations.Update; 6 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 7 | 8 | /** 9 | * @author imyzt 10 | * @date 2019/3/9 14:10 11 | * @description MiaoshaUserMapper 12 | */ 13 | public interface MiaoshaUserMapper { 14 | 15 | @Select("SELECT * FROM miaosha.miaosha_user WHERE id = #{id} ") 16 | MiaoshaUser getById(@Param("id") long id); 17 | 18 | @Update("UPDATE miaosha_user SET password = #{password} WHERE id = #{id} ") 19 | void update(MiaoshaUser updateUser); 20 | 21 | @Select("INSERT INTO miaosha_user (id, nickname, password, salt, head, redister_date, login_count) " + 22 | "VALUES (#{id}, #{nickname}, #{password}, #{salt}, #{head}, #{registerDate}, #{loginCount} )") 23 | MiaoshaUser insert(MiaoshaUser user); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/mapper/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.mapper; 2 | 3 | import org.apache.ibatis.annotations.Insert; 4 | import org.apache.ibatis.annotations.Param; 5 | import org.apache.ibatis.annotations.Select; 6 | import org.apache.ibatis.annotations.SelectKey; 7 | import top.imyzt.study.miaosha.domain.MiaoshaOrder; 8 | import top.imyzt.study.miaosha.domain.OrderInfo; 9 | 10 | /** 11 | * @author imyzt 12 | * @date 2019/3/13 18:21 13 | * @description OrderMapper 14 | */ 15 | public interface OrderMapper { 16 | 17 | 18 | /** 19 | * 根据条件查询秒杀订单 20 | * @param userId 用户id 21 | * @param goodsId 商品id 22 | * @return 秒杀商品 23 | */ 24 | @Select("SELECT * FROM miaosha_order WHERE user_id = #{userId} AND goods_id = #{goodsId} ") 25 | MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(@Param("userId")Long userId, @Param("goodsId")Long goodsId); 26 | 27 | @Insert("INSERT INTO order_info(user_id, goods_id, goods_name, goods_price, goods_count, order_channel, status, create_date) " + 28 | "VALUES (#{userId}, #{goodsId}, #{goodsName}, #{goodsPrice}, #{goodsCount}, #{orderChannel}, #{status}, #{createDate} )") 29 | @SelectKey(keyColumn = "id", keyProperty = "id", resultType = long.class, before = false, statement = "select last_insert_id()") 30 | long insert(OrderInfo orderInfo); 31 | 32 | @Insert("INSERT INTO miaosha_order (user_id, order_id, goods_id) VALUES (#{userId}, #{orderId}, #{goodsId});") 33 | @SelectKey(keyColumn = "id", keyProperty = "id", resultType = long.class, before = false, statement = "select last_insert_id()") 34 | long insertMiaoshaOrder(MiaoshaOrder miaoshaOrder); 35 | 36 | @Select("SELECT * FROM miaosha_order WHERE id = #{orderId} ") 37 | OrderInfo getById(long orderId); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.mapper; 2 | 3 | import org.apache.ibatis.annotations.Select; 4 | import top.imyzt.study.miaosha.domain.User; 5 | 6 | /** 7 | * @author imyzt 8 | * @date 2019/3/8 17:22 9 | * @description UserMapper 10 | */ 11 | public interface UserMapper { 12 | 13 | @Select("SELECT * FROM user WHERE id = #{id}") 14 | User findById(Integer id); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/rabbitmq/MqReceiver.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.rabbitmq; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | import top.imyzt.study.miaosha.common.Constant; 8 | import top.imyzt.study.miaosha.domain.MiaoshaOrder; 9 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 10 | import top.imyzt.study.miaosha.domain.OrderInfo; 11 | import top.imyzt.study.miaosha.rabbitmq.dto.MiaoshaMessage; 12 | import top.imyzt.study.miaosha.result.CodeMsg; 13 | import top.imyzt.study.miaosha.result.Result; 14 | import top.imyzt.study.miaosha.service.GoodsService; 15 | import top.imyzt.study.miaosha.service.MiaoshaService; 16 | import top.imyzt.study.miaosha.service.OrderService; 17 | import top.imyzt.study.miaosha.utils.ConvertUtil; 18 | import top.imyzt.study.miaosha.vo.GoodsVo; 19 | 20 | /** 21 | * @author imyzt 22 | * @date 2019/3/20 11:26 23 | * @description mq 接收端 24 | */ 25 | @Service 26 | @Slf4j 27 | public class MqReceiver { 28 | 29 | @Autowired 30 | private GoodsService goodsService; 31 | 32 | @Autowired 33 | private OrderService orderService; 34 | 35 | @Autowired 36 | private MiaoshaService miaoshaService; 37 | 38 | @RabbitListener(queues = Constant.MIAOSHA_QUEUE) 39 | public void miaoshaQueueReceiver(String message) { 40 | 41 | log.debug("miaoshaQueueReceiver msg={}", message); 42 | 43 | MiaoshaMessage miaoshaMessage = ConvertUtil.strToBean(message, MiaoshaMessage.class); 44 | MiaoshaUser user = miaoshaMessage.getUser(); 45 | Long goodsId = miaoshaMessage.getGoodsId(); 46 | 47 | // 判断库存 48 | GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId); 49 | if (null == goodsVo || goodsVo.getStockCount() <= 0) { 50 | // log.error("miaoshaQueueReceiver, 库存不足, u={}, g={}", user.getId(), goodsId); 51 | return; 52 | } 53 | 54 | // 判断是否重复下单 55 | MiaoshaOrder miaoshaOrder = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId); 56 | if (null != miaoshaOrder) { 57 | // log.error("miaoshaQueueReceiver, 重复秒杀, u={}, g={}", user.getId(), goodsId); 58 | return; 59 | } 60 | 61 | // 减库存,下订单 写入订单 62 | miaoshaService.miaosha(user, goodsVo); 63 | log.info("miaoshaQueueReceiver. 秒杀成功. userId={}, goodsId={}", user.getId(), goodsVo.getId()); 64 | } 65 | 66 | @RabbitListener(queues = Constant.DEFAULT_QUEUE_NAME) 67 | public void listenerQueue(String message) { 68 | log.info("receiver DEFAULT_QUEUE_NAME msg={}", message); 69 | } 70 | 71 | @RabbitListener(queues = Constant.TOPIC_QUEUE_1) 72 | public void listenerTopicQueue1(String message) { 73 | log.info("receiver TOPIC_QUEUE_1 msg={}", message); 74 | } 75 | @RabbitListener(queues = Constant.TOPIC_QUEUE_2) 76 | public void listenerTopicQueue2(String message) { 77 | log.info("receiver TOPIC_QUEUE_2 msg={}", message); 78 | } 79 | 80 | @RabbitListener(queues = Constant.FANOUT_QUEUE_1) 81 | public void listenerFanoutQueue1(String message) { 82 | log.info("receiver FANOUT_QUEUE_1 msg={}", message); 83 | } 84 | @RabbitListener(queues = Constant.FANOUT_QUEUE_2) 85 | public void listenerFanoutQueue2(String message) { 86 | log.info("receiver FANOUT_QUEUE_2 msg={}", message); 87 | } 88 | 89 | @RabbitListener(queues = Constant.HEADERS_QUEUE) 90 | public void listenerHeadersQueue(byte[] message) { 91 | log.info("receiver HEADERS_QUEUE msg={}", message); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/rabbitmq/MqSender.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.rabbitmq; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.amqp.core.AmqpTemplate; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.messaging.Message; 7 | import org.springframework.messaging.MessageHeaders; 8 | import org.springframework.messaging.support.GenericMessage; 9 | import org.springframework.stereotype.Service; 10 | import top.imyzt.study.miaosha.common.Constant; 11 | import top.imyzt.study.miaosha.rabbitmq.dto.MiaoshaMessage; 12 | import top.imyzt.study.miaosha.utils.ConvertUtil; 13 | 14 | import java.util.HashMap; 15 | 16 | /** 17 | * @author imyzt 18 | * @date 2019/3/20 11:21 19 | * @description mq消息发送服务 20 | */ 21 | @Service 22 | @Slf4j 23 | public class MqSender { 24 | 25 | @Autowired 26 | private AmqpTemplate amqpTemplate; 27 | 28 | public void miaoshaSender(MiaoshaMessage message) { 29 | String msg = ConvertUtil.beanToStr(message); 30 | log.debug("miaosha sender={}", msg); 31 | amqpTemplate.convertAndSend(Constant.MIAOSHA_QUEUE, msg); 32 | } 33 | 34 | public void sender(Object message) { 35 | String msg = ConvertUtil.beanToStr(message); 36 | log.info("sender msg={}", msg); 37 | amqpTemplate.convertAndSend(Constant.DEFAULT_QUEUE_NAME, msg); 38 | } 39 | 40 | public void topicSender(Object message) { 41 | String msg = ConvertUtil.beanToStr(message); 42 | log.info("sender msg={}", msg); 43 | amqpTemplate.convertAndSend(Constant.TOPIC_EXCHANGE, Constant.ROUTING_KEY_1, msg); 44 | amqpTemplate.convertAndSend(Constant.TOPIC_EXCHANGE, Constant.ROUTING_KEY_2, msg); 45 | } 46 | 47 | public void fanoutSender(Object message) { 48 | String msg = ConvertUtil.beanToStr(message); 49 | log.info("sender msg={}", msg); 50 | amqpTemplate.convertAndSend(Constant.FANOUT_EXCHANGE,"", msg); 51 | amqpTemplate.convertAndSend(Constant.FANOUT_EXCHANGE, "", msg); 52 | } 53 | 54 | public void headersSender(Object message) { 55 | String msg = ConvertUtil.beanToStr(message); 56 | log.info("sender msg={}", msg); 57 | 58 | HashMap hashMap = new HashMap(2){{ 59 | put("header1", "value1"); 60 | put("header2", "value2"); 61 | }}; 62 | MessageHeaders messageHeaders = new MessageHeaders(hashMap); 63 | Message obj = new GenericMessage<>(msg.getBytes(), messageHeaders); 64 | 65 | amqpTemplate.convertAndSend(Constant.HEADERS_EXCHANGE,"", obj); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/rabbitmq/dto/MiaoshaMessage.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.rabbitmq.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 7 | 8 | /** 9 | * @author imyzt 10 | * @date 2019/3/20 18:02 11 | * @description MiaoshaMessage 12 | */ 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class MiaoshaMessage { 17 | private MiaoshaUser user; 18 | private Long goodsId; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/result/CodeMsg.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.result; 2 | 3 | import lombok.Getter; 4 | import lombok.ToString; 5 | 6 | /** 7 | * @author imyzt 8 | * @date 2019/3/8 17:30 9 | * @description CodeMsg 10 | */ 11 | @Getter 12 | @ToString 13 | public class CodeMsg { 14 | 15 | private Integer code; 16 | 17 | private String msg; 18 | 19 | public static final int SUCCESS_CODE = 0; 20 | 21 | // 通用错误码 22 | 23 | public static CodeMsg SUCCESS = new CodeMsg(0, "success"); 24 | public static CodeMsg SERVER_ERROR = new CodeMsg(500100, "服务端异常"); 25 | public static CodeMsg BIND_ERROR = new CodeMsg(500101, "参数校验异常: %s"); 26 | public static CodeMsg REQUEST_ILLEGAL = new CodeMsg(500102, "请求非法"); 27 | public static CodeMsg VERIFY_CODE_FAIL = new CodeMsg(500102, "验证码错误"); 28 | public static CodeMsg ACCESS_LIMIT_REACHED = new CodeMsg(500103, "请求太频繁了!"); 29 | 30 | 31 | // 登录模块错误码 32 | 33 | public static CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已经失效"); 34 | public static CodeMsg PASSWORD_EMPTY = new CodeMsg(500211, "登录密码不能为空"); 35 | public static CodeMsg MOBILE_EMPTY = new CodeMsg(500212, "手机号不能为空"); 36 | public static CodeMsg MOBILE_ERROR = new CodeMsg(500213, "手机号格式不正确"); 37 | public static CodeMsg MOBILE_NOT_EXIST = new CodeMsg(500214, "账号不存在"); 38 | public static CodeMsg PASSWORD_ERROR = new CodeMsg(500215, "密码错误"); 39 | 40 | 41 | // 秒杀模块 42 | 43 | public static CodeMsg MIAO_SHA_OVER = new CodeMsg(500500, "库存不足"); 44 | public static CodeMsg REPEATE_MIAOSHA = new CodeMsg(500501, "不能重复下单"); 45 | public static CodeMsg MIAOSHA_FAIL = new CodeMsg(500502, "秒杀失败"); 46 | 47 | 48 | // 订单模块 49 | 50 | public static CodeMsg ORDER_NOT_EXIST = new CodeMsg(500400, "订单不存在"); 51 | 52 | 53 | // 商品模块 54 | 55 | public static CodeMsg GOODS_NOT_EXIST = new CodeMsg(500600, "商品不存在"); 56 | 57 | 58 | 59 | private CodeMsg(int code, String msg) { 60 | this.code = code; 61 | this.msg = msg; 62 | } 63 | 64 | public CodeMsg fillArgs(Object... args) { 65 | return new CodeMsg(this.code, String.format(this.msg, args)); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/result/Result.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.result; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author imyzt 7 | * @date 2019/3/8 17:28 8 | * @description Result 9 | */ 10 | @Data 11 | public class Result { 12 | 13 | private Integer code; 14 | private String msg; 15 | private T data; 16 | 17 | private Result() { 18 | } 19 | 20 | private Result(T data) { 21 | this.code = 0; 22 | this.msg = "success"; 23 | this.data = data; 24 | } 25 | 26 | private Result(CodeMsg codeMsg) { 27 | if (null == codeMsg) { 28 | return; 29 | } 30 | this.code = codeMsg.getCode(); 31 | this.msg = codeMsg.getMsg(); 32 | } 33 | 34 | public static Result success(T data) { 35 | return new Result <>(data); 36 | } 37 | 38 | public static Result error(CodeMsg codeMsg) { 39 | return new Result <>(codeMsg); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/service/GoodsService.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import top.imyzt.study.miaosha.domain.MiaoshaOrder; 6 | import top.imyzt.study.miaosha.mapper.GoodsMapper; 7 | import top.imyzt.study.miaosha.vo.GoodsVo; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author imyzt 13 | * @date 2019/3/13 16:05 14 | * @description GoodsService 15 | */ 16 | @Service 17 | public class GoodsService { 18 | 19 | @Autowired 20 | private GoodsMapper goodsMapper; 21 | 22 | public List listGoodsVo() { 23 | return goodsMapper.listGoodsVo(); 24 | } 25 | 26 | public GoodsVo getGoodsVoByGoodsId(long goodsId) { 27 | return goodsMapper.getGoodsVoByGoodsId(goodsId); 28 | } 29 | 30 | /** 31 | * 判断库存 32 | * @param goodsId 商品id 33 | * @return 是否能够下单 34 | */ 35 | public boolean checkStockCount(Long goodsId) { 36 | GoodsVo goodsVo = this.getGoodsVoByGoodsId(goodsId); 37 | if (null != goodsVo) { 38 | Integer stockCount = goodsVo.getStockCount(); 39 | return stockCount > 0; 40 | } 41 | return false; 42 | } 43 | 44 | /** 45 | * 库存 -1 46 | * @param goodsVo 秒杀商品 47 | */ 48 | public boolean reduceStock(GoodsVo goodsVo) { 49 | MiaoshaOrder miaoshaOrder = new MiaoshaOrder(); 50 | miaoshaOrder.setGoodsId(goodsVo.getId()); 51 | int i = goodsMapper.reduceStock(miaoshaOrder); 52 | return i > 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/service/MiaoshaService.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.service; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | import cn.hutool.script.ScriptUtil; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | import top.imyzt.study.miaosha.common.redis.MiaoshaKey; 10 | import top.imyzt.study.miaosha.domain.MiaoshaOrder; 11 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 12 | import top.imyzt.study.miaosha.domain.OrderInfo; 13 | import top.imyzt.study.miaosha.utils.MD5Util; 14 | import top.imyzt.study.miaosha.vo.GoodsVo; 15 | 16 | import java.awt.*; 17 | import java.awt.image.BufferedImage; 18 | import java.util.Objects; 19 | import java.util.Random; 20 | import java.util.stream.IntStream; 21 | 22 | /** 23 | * @author imyzt 24 | * @date 2019/3/13 18:25 25 | * @description MiaoshaService 26 | */ 27 | @Service 28 | @Slf4j 29 | public class MiaoshaService { 30 | 31 | private static final char[] OPS = new char[]{'+','-','*'}; 32 | 33 | @Autowired 34 | private GoodsService goodsService; 35 | @Autowired 36 | private OrderService orderService; 37 | @Autowired 38 | private RedisService redisService; 39 | 40 | @Transactional(rollbackFor = Exception.class) 41 | public OrderInfo miaosha(MiaoshaUser user, GoodsVo goodsVo) { 42 | // 减库存 43 | boolean reduceStock = goodsService.reduceStock(goodsVo); 44 | if (reduceStock) { 45 | // 下订单 46 | return orderService.createOrder(user, goodsVo); 47 | } else { 48 | // 设置该商品状态为已售完 49 | setGoodsOver(goodsVo.getId()); 50 | return null; 51 | } 52 | } 53 | 54 | public long getMiaoshaResult(Long userId, Long goodsId) { 55 | 56 | MiaoshaOrder miaoshaOrder = orderService.getMiaoshaOrderByUserIdGoodsId(userId, goodsId); 57 | // 秒杀成功 58 | if (null != miaoshaOrder) { 59 | return miaoshaOrder.getId(); 60 | } else { 61 | // 判断该商品是否已售完 62 | boolean isOver = getGoodsOver(goodsId); 63 | if (isOver) { 64 | return -1; 65 | } else { 66 | return 0; 67 | } 68 | } 69 | } 70 | 71 | private void setGoodsOver(Long goodsId) { 72 | redisService.set(MiaoshaKey.IS_GOODS_OVER, ""+goodsId, true); 73 | } 74 | 75 | private boolean getGoodsOver(Long goodsId) { 76 | return redisService.exists(MiaoshaKey.IS_GOODS_OVER, ""+goodsId); 77 | } 78 | 79 | public String createMiaoshaPath(MiaoshaUser user, Long goodsId) { 80 | 81 | String path = MD5Util.md5(RandomUtil.randomString(32) + "&*!@:LJ:"); 82 | 83 | // 存入redis 84 | redisService.set(MiaoshaKey.GET_MIAOSHA_PATH, ""+user.getId()+"_"+goodsId, path); 85 | return path; 86 | } 87 | 88 | public boolean checkMiaoshaPath(MiaoshaUser user, Long goodsId, String path) { 89 | if (null == user || null == path) { 90 | return false; 91 | } 92 | String pathOld = redisService.get(MiaoshaKey.GET_MIAOSHA_PATH, "" + user.getId() + "_" + goodsId, String.class); 93 | return Objects.equals(pathOld, path); 94 | } 95 | 96 | public BufferedImage createMiaoshaVerifyCode(MiaoshaUser user, Long goodsId) { 97 | 98 | int width = 80, height = 32; 99 | 100 | // 创建图片 101 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 102 | Graphics graphics = image.getGraphics(); 103 | 104 | // 设置画笔颜色 105 | graphics.setColor(new Color(0xDCDCDC)); 106 | // 设置背景颜色 107 | graphics.fillRect(0, 0, width, height); 108 | // 重设画笔颜色 109 | graphics.setColor(new Color(0, 0, 0)); 110 | graphics.drawRect(0, 0, width -1, height -1); 111 | 112 | // 创建50个干扰点 113 | Random random = new Random(); 114 | IntStream.range(0, 50).forEach(i->{ 115 | int x = random.nextInt(); 116 | int y = random.nextInt(); 117 | graphics.drawOval(x, y, 0, 0); 118 | }); 119 | 120 | // 创建验证码 121 | String verifyCode = verifyCode(random); 122 | graphics.setColor(new Color(0, 100, 0)); 123 | graphics.setFont(new Font("Candara", Font.BOLD, 24)); 124 | graphics.drawString(verifyCode, 8, 24); 125 | graphics.dispose(); 126 | 127 | // 将验证码存放到redis中 128 | int rnd = calc(verifyCode); 129 | redisService.set(MiaoshaKey.GET_MIAOSHA_VERIFY_CODE, ""+user.getId()+"_"+goodsId, rnd); 130 | 131 | return image; 132 | } 133 | 134 | private int calc(String exp) { 135 | return (Integer) ScriptUtil.eval(exp); 136 | } 137 | 138 | private String verifyCode(Random random) { 139 | int num1 = random.nextInt(10); 140 | int num2 = random.nextInt(10); 141 | int num3 = random.nextInt(10); 142 | 143 | char op1 = OPS[random.nextInt(3)]; 144 | char op2 = OPS[random.nextInt(3)]; 145 | return String.format("%d%s%d%s%d", num1, op1, num2, op2, num3); 146 | } 147 | 148 | public boolean checkMiaoshaVerifyCode(MiaoshaUser user, Long goodsId, Integer verifyCode) { 149 | Integer oldCode = redisService.get(MiaoshaKey.GET_MIAOSHA_VERIFY_CODE, "" + user.getId() + "_" + goodsId, Integer.class); 150 | if (null == oldCode || oldCode - verifyCode != 0) { 151 | return false; 152 | } 153 | redisService.delete(MiaoshaKey.GET_MIAOSHA_VERIFY_CODE, "" + user.getId() + "_" + goodsId); 154 | return true; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/service/MiaoshaUserService.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.service; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.beans.BeanUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import top.imyzt.study.miaosha.common.redis.MiaoshaUserKey; 9 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 10 | import top.imyzt.study.miaosha.exception.GlobalException; 11 | import top.imyzt.study.miaosha.mapper.MiaoshaUserMapper; 12 | import top.imyzt.study.miaosha.result.CodeMsg; 13 | import top.imyzt.study.miaosha.result.Result; 14 | import top.imyzt.study.miaosha.utils.MD5Util; 15 | import top.imyzt.study.miaosha.utils.UUIDUtil; 16 | import top.imyzt.study.miaosha.vo.LoginVo; 17 | 18 | import javax.servlet.http.Cookie; 19 | import javax.servlet.http.HttpServletResponse; 20 | import java.util.Objects; 21 | import java.util.Random; 22 | 23 | /** 24 | * @author imyzt 25 | * @date 2019/3/9 14:12 26 | * @description MiaoshaUserService 27 | */ 28 | @Service 29 | public class MiaoshaUserService { 30 | 31 | private final MiaoshaUserMapper userMapper; 32 | private final RedisService redisService; 33 | 34 | public static final String COOKIE_NAME_TOKEN = "token"; 35 | 36 | @Autowired 37 | public MiaoshaUserService(MiaoshaUserMapper userMapper, RedisService redisService) { 38 | this.userMapper = userMapper; 39 | this.redisService = redisService; 40 | } 41 | 42 | public MiaoshaUser getById(long id) { 43 | 44 | MiaoshaUser miaoshaUser = redisService.get(MiaoshaUserKey.getById, "" + id, MiaoshaUser.class); 45 | if (null != miaoshaUser) { 46 | return miaoshaUser; 47 | } 48 | 49 | miaoshaUser = userMapper.getById(id); 50 | 51 | if (null != miaoshaUser) { 52 | redisService.set(MiaoshaUserKey.getById, "" + id, miaoshaUser); 53 | } 54 | 55 | return miaoshaUser; 56 | } 57 | 58 | public MiaoshaUser register (MiaoshaUser user) { 59 | 60 | if (null == user) { 61 | throw new GlobalException(CodeMsg.SERVER_ERROR); 62 | } 63 | 64 | MiaoshaUser registerUser = new MiaoshaUser(); 65 | BeanUtils.copyProperties(user, registerUser); 66 | 67 | String formPass = user.getPassword(); 68 | String salt = RandomUtil.randomString(10); 69 | String dbPass = MD5Util.formPassToDBPass(formPass, salt); 70 | registerUser.setPassword(dbPass); 71 | registerUser.setSalt(salt); 72 | 73 | return userMapper.insert(registerUser); 74 | } 75 | 76 | public MiaoshaUser updatePassword(String token, long id, String form) { 77 | 78 | MiaoshaUser miaoshaUser = this.getById(id); 79 | if (null == miaoshaUser) { 80 | throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST); 81 | } 82 | 83 | //更新密码 84 | MiaoshaUser updateUser = new MiaoshaUser(); 85 | updateUser.setId(id); 86 | updateUser.setPassword(MD5Util.formPassToDBPass(form, miaoshaUser.getSalt())); 87 | userMapper.update(updateUser); 88 | 89 | miaoshaUser.setPassword(updateUser.getPassword()); 90 | 91 | // 更新缓存 92 | redisService.delete(MiaoshaUserKey.getById, ""+id); 93 | redisService.set(MiaoshaUserKey.TOKEN, token, miaoshaUser); 94 | 95 | return miaoshaUser; 96 | } 97 | 98 | 99 | public boolean login(HttpServletResponse response, LoginVo loginVo) { 100 | 101 | if (null == loginVo) { 102 | throw new GlobalException(CodeMsg.SERVER_ERROR); 103 | } 104 | 105 | String mobile = loginVo.getMobile(); 106 | String formPass = loginVo.getPassword(); 107 | 108 | MiaoshaUser user = this.getById(Long.parseLong(mobile)); 109 | 110 | if (null == user) { 111 | throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST); 112 | } 113 | 114 | String dbSalt = user.getSalt(); 115 | String dbPass = user.getPassword(); 116 | 117 | String formPassToDBPass = MD5Util.formPassToDBPass(formPass, dbSalt); 118 | 119 | if (!Objects.equals(dbPass, formPassToDBPass)) { 120 | throw new GlobalException(CodeMsg.PASSWORD_ERROR); 121 | } 122 | 123 | String token = UUIDUtil.uuid(); 124 | writeCookie(response, token, user); 125 | 126 | return true; 127 | } 128 | 129 | private void writeCookie(HttpServletResponse response, String token, MiaoshaUser user) { 130 | 131 | // 存入redis 132 | redisService.set(MiaoshaUserKey.TOKEN, token, user); 133 | 134 | // 写入cookie 135 | Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token); 136 | cookie.setMaxAge(MiaoshaUserKey.TOKEN.getExpireSeconds()); 137 | cookie.setPath("/"); 138 | response.addCookie(cookie); 139 | } 140 | 141 | public MiaoshaUser getByToken(HttpServletResponse response, String token) { 142 | if (StringUtils.isEmpty(token)) { 143 | return null; 144 | } 145 | MiaoshaUser miaoshaUser = redisService.get(MiaoshaUserKey.TOKEN, token, MiaoshaUser.class); 146 | // 续签 147 | if (null != miaoshaUser) { 148 | writeCookie(response, token, miaoshaUser); 149 | } 150 | return miaoshaUser; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | import top.imyzt.study.miaosha.common.redis.OrderKey; 7 | import top.imyzt.study.miaosha.domain.MiaoshaOrder; 8 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 9 | import top.imyzt.study.miaosha.domain.OrderInfo; 10 | import top.imyzt.study.miaosha.mapper.OrderMapper; 11 | import top.imyzt.study.miaosha.vo.GoodsVo; 12 | 13 | import java.time.Instant; 14 | import java.util.Date; 15 | 16 | /** 17 | * @author imyzt 18 | * @date 2019/3/13 18:21 19 | * @description OrderService 20 | */ 21 | @Service 22 | @Slf4j 23 | public class OrderService { 24 | 25 | @Autowired 26 | private OrderMapper orderMapper; 27 | @Autowired 28 | private RedisService redisService; 29 | 30 | public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(Long userId, Long goodsId) { 31 | 32 | return redisService.get(OrderKey.getMiaoshaOrderByUidGid, ""+userId+"_"+goodsId, MiaoshaOrder.class); 33 | } 34 | 35 | public OrderInfo createOrder(MiaoshaUser user, GoodsVo goodsVo) { 36 | 37 | // 下订单 38 | OrderInfo orderInfo = new OrderInfo(); 39 | orderInfo.setCreateDate(Date.from(Instant.now())); 40 | orderInfo.setDeliveryAddrId(0L); 41 | orderInfo.setGoodsCount(1); 42 | orderInfo.setGoodsId(goodsVo.getId()); 43 | orderInfo.setGoodsName(goodsVo.getGoodsName()); 44 | orderInfo.setGoodsPrice(goodsVo.getMiaoshaPrice()); 45 | orderInfo.setOrderChannel(1); 46 | orderInfo.setStatus(0); 47 | orderInfo.setUserId(user.getId()); 48 | 49 | orderMapper.insert(orderInfo); 50 | 51 | // 下秒杀订单 52 | MiaoshaOrder miaoshaOrder = new MiaoshaOrder(); 53 | miaoshaOrder.setGoodsId(goodsVo.getId()); 54 | miaoshaOrder.setOrderId(orderInfo.getId()); 55 | miaoshaOrder.setUserId(user.getId()); 56 | 57 | orderMapper.insertMiaoshaOrder(miaoshaOrder); 58 | 59 | redisService.set(OrderKey.getMiaoshaOrderByUidGid, ""+user.getId()+"_"+goodsVo.getId(), miaoshaOrder); 60 | 61 | log.info("成功下单. userId={}, goodsId={}", user.getId(), goodsVo.getId()); 62 | return orderInfo; 63 | } 64 | 65 | public OrderInfo getById(long orderId) { 66 | return orderMapper.getById(orderId); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/service/RedisService.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | import redis.clients.jedis.Jedis; 7 | import redis.clients.jedis.JedisPool; 8 | import top.imyzt.study.miaosha.common.redis.KeyPrefix; 9 | 10 | import static top.imyzt.study.miaosha.utils.ConvertUtil.beanToStr; 11 | import static top.imyzt.study.miaosha.utils.ConvertUtil.strToBean; 12 | 13 | /** 14 | * @author imyzt 15 | * @date 2019/3/8 18:12 16 | * @description RedisService 17 | */ 18 | @Service 19 | public class RedisService { 20 | 21 | private final JedisPool jedisPool; 22 | 23 | @Autowired 24 | public RedisService(JedisPool jedisPool) { 25 | this.jedisPool = jedisPool; 26 | } 27 | 28 | /** 29 | * get 30 | * @param prefix 前缀 31 | * @param key key 32 | * @param clazz json转换类型 33 | * @param 泛型 34 | * @return 值 35 | */ 36 | public T get(KeyPrefix prefix, String key, Class clazz) { 37 | try(Jedis jedis = jedisPool.getResource()) { 38 | String realKey = prefix.getRealKey(key); 39 | String str = jedis.get(realKey); 40 | return strToBean(str, clazz); 41 | } 42 | } 43 | 44 | /** 45 | * set 46 | * @param prefix 前缀 47 | * @param key key 48 | * @param value value 49 | * @param 泛型 50 | * @return 是否成功 51 | */ 52 | public boolean set(KeyPrefix prefix, String key, T value) { 53 | try(Jedis jedis = jedisPool.getResource()) { 54 | String str = beanToStr(value); 55 | if (null != str && str.length() > 0) { 56 | String realKey = prefix.getRealKey(key); 57 | int seconds = prefix.getExpireSeconds(); 58 | if (seconds <= 0) { 59 | jedis.set(realKey, str); 60 | } else { 61 | jedis.setex(realKey, seconds, str); 62 | } 63 | return true; 64 | } 65 | } 66 | return false; 67 | } 68 | 69 | /** 70 | * 判断key是否存在 71 | * @param prefix 前缀 72 | * @param key key 73 | * @return 是否存在 74 | */ 75 | public boolean exists(KeyPrefix prefix, String key) { 76 | try(Jedis jedis = jedisPool.getResource()) { 77 | String realKey = prefix.getRealKey(key); 78 | return jedis.exists(realKey); 79 | } 80 | } 81 | 82 | /** 83 | * 自增 84 | * @param prefix 前缀 85 | * @param key key 86 | * @return 自增后的值 87 | */ 88 | public Long incr(KeyPrefix prefix, String key) { 89 | try(Jedis jedis = jedisPool.getResource()) { 90 | String realKey = prefix.getRealKey(key); 91 | return jedis.incr(realKey); 92 | } 93 | } 94 | 95 | /** 96 | * 删除key 97 | * @param prefix 前缀 98 | * @param key key 99 | * @return 是否成功 100 | */ 101 | public boolean delete(KeyPrefix prefix, String key) { 102 | try(Jedis jedis = jedisPool.getResource()) { 103 | String realKey = prefix.getRealKey(key); 104 | return jedis.del(realKey) > 0; 105 | } 106 | } 107 | 108 | /** 109 | * 自减 110 | * @param prefix 前缀 111 | * @param key key 112 | * @return 自减后的值 113 | */ 114 | public Long decr(KeyPrefix prefix, String key) { 115 | try(Jedis jedis = jedisPool.getResource()) { 116 | String realKey = prefix.getRealKey(key); 117 | return jedis.decr(realKey); 118 | } 119 | } 120 | 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/service/UserService.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import top.imyzt.study.miaosha.domain.User; 6 | import top.imyzt.study.miaosha.mapper.UserMapper; 7 | 8 | /** 9 | * @author imyzt 10 | * @date 2019/3/8 17:26 11 | * @description UserService 12 | */ 13 | @Service 14 | public class UserService { 15 | 16 | @Autowired 17 | private UserMapper userMapper; 18 | 19 | public User getById(Integer id) { 20 | return userMapper.findById(id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/utils/ConvertUtil.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | /** 6 | * @author imyzt 7 | * @date 2019/3/20 11:23 8 | * @description ConvertUtil 9 | */ 10 | public class ConvertUtil { 11 | 12 | public static String beanToStr(T value) { 13 | if (null == value) { 14 | return null; 15 | } 16 | 17 | Class clazz = value.getClass(); 18 | if (clazz == int.class || clazz == Integer.class) { 19 | return "" + value; 20 | } else if (clazz == String.class) { 21 | return (String) value; 22 | } else if (clazz == long.class || clazz == Long.class) { 23 | return "" + value; 24 | } else { 25 | return JSON.toJSONString(value); 26 | } 27 | } 28 | 29 | @SuppressWarnings("unchecked") 30 | public static T strToBean(String value, Class clazz) { 31 | if (null == value || value.length() <= 0 || null == clazz) { 32 | return null; 33 | } 34 | 35 | if (clazz == int.class || clazz == Integer.class) { 36 | return (T) Integer.valueOf(value); 37 | } else if (clazz == String.class) { 38 | return (T) value; 39 | } else if (clazz == long.class || clazz == Long.class) { 40 | return (T) Long.valueOf(value); 41 | } else { 42 | return JSON.toJavaObject(JSON.parseObject(value), clazz); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/utils/MD5Util.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.utils; 2 | 3 | import org.apache.commons.codec.digest.DigestUtils; 4 | 5 | /** 6 | * @author imyzt 7 | * @date 2019/3/9 10:36 8 | * @description MD5Util 9 | */ 10 | public class MD5Util { 11 | 12 | public static String md5(String src) { 13 | return DigestUtils.md5Hex(src); 14 | } 15 | 16 | private static final String SALT = "fas_12^*(%&"; 17 | 18 | public static String inputPassToFormPass(String src) { 19 | String str = SALT.charAt(4) + SALT.substring(4) + SALT.substring(0, 5) + src + SALT.charAt(9); 20 | return md5(str); 21 | } 22 | 23 | public static String formPassToDBPass(String src, String salt) { 24 | String str = salt.charAt(4) + salt.substring(4) + salt.substring(0, 5) + src + salt.charAt(9); 25 | return md5(str); 26 | } 27 | 28 | /*public static String inputPassToDBPass(String src, String saltDB) { 29 | String str = saltDB.charAt(4) + saltDB.substring(4) + saltDB.substring(0, 5) + src + saltDB.charAt(9); 30 | return md5(str); 31 | }*/ 32 | 33 | public static String inputPassToDBPass(String src, String saltDB) { 34 | return formPassToDBPass(inputPassToFormPass(src), saltDB); 35 | } 36 | 37 | public static void main(String[] args) { 38 | System.out.println(inputPassToFormPass("123456")); 39 | System.out.println(formPassToDBPass("b4accafee38035d4a1f0532785056d14", "y95834zn39")); 40 | System.out.println(inputPassToDBPass("123456", "y95834zn39")); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/utils/UUIDUtil.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.utils; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * @author imyzt 7 | * @date 2019/3/9 15:49 8 | * @description UUIDUtil 9 | */ 10 | public class UUIDUtil { 11 | 12 | public static String uuid() { 13 | return UUID.randomUUID().toString().replaceAll("-", ""); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/utils/ValidatorUtil.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.utils; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * @author imyzt 10 | * @date 2019/3/9 13:55 11 | * @description ValidatorUtil 12 | */ 13 | public class ValidatorUtil { 14 | 15 | private static final Pattern MOBILE_PATTERN = Pattern.compile("1\\d{10}"); 16 | 17 | public static boolean isMobile (String mobile) { 18 | if (StringUtils.isEmpty(mobile)) { 19 | return false; 20 | } 21 | Matcher matcher = MOBILE_PATTERN.matcher(mobile); 22 | return matcher.matches(); 23 | } 24 | 25 | public static void main(String[] args) { 26 | System.out.println(isMobile("12222222222")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/validator/IsMobile.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.validator; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import javax.validation.constraints.NotNull; 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Repeatable; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.Target; 11 | 12 | import static java.lang.annotation.ElementType.*; 13 | import static java.lang.annotation.ElementType.PARAMETER; 14 | import static java.lang.annotation.ElementType.TYPE_USE; 15 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 16 | 17 | /** 18 | * @author imyzt 19 | * @date 2019/3/9 15:03 20 | * @description IsMobile 21 | */ 22 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 23 | @Retention(RUNTIME) 24 | @Documented 25 | @Constraint(validatedBy = {IsMobileValidator.class}) 26 | public @interface IsMobile { 27 | 28 | boolean required() default true; 29 | 30 | String message() default "手机号码格式错误"; 31 | 32 | Class[] groups() default { }; 33 | 34 | Class[] payload() default { }; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/validator/IsMobileValidator.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.validator; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import top.imyzt.study.miaosha.utils.ValidatorUtil; 5 | 6 | import javax.validation.ConstraintValidator; 7 | import javax.validation.ConstraintValidatorContext; 8 | 9 | /** 10 | * @author imyzt 11 | * @date 2019/3/9 15:05 12 | * @description IsMobileValidator 13 | */ 14 | public class IsMobileValidator implements ConstraintValidator { 15 | 16 | private boolean required = false; 17 | 18 | @Override 19 | public void initialize(IsMobile constraintAnnotation) { 20 | required = constraintAnnotation.required(); 21 | } 22 | 23 | @Override 24 | public boolean isValid(String value, ConstraintValidatorContext context) { 25 | if (required) { 26 | return ValidatorUtil.isMobile(value); 27 | } else { 28 | return !StringUtils.isEmpty(value) && ValidatorUtil.isMobile(value); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/vo/GoodsDetailVo.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.vo; 2 | 3 | import lombok.Data; 4 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 5 | 6 | /** 7 | * @author imyzt 8 | * @date 2019/3/19 15:03 9 | * @description GoodsDetailVo 10 | */ 11 | @Data 12 | public class GoodsDetailVo { 13 | private int miaoshaStatus = 0; 14 | private int remainSeconds = 0; 15 | private GoodsVo goods; 16 | private MiaoshaUser user; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/vo/GoodsVo.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.vo; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import top.imyzt.study.miaosha.domain.Goods; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * @author imyzt 11 | * @date 2019/3/13 15:57 12 | * @description 秒杀商品信息 13 | */ 14 | @EqualsAndHashCode(callSuper = true) 15 | @Data 16 | public class GoodsVo extends Goods { 17 | 18 | private Double miaoshaPrice; 19 | private Integer stockCount; 20 | private Date startDate; 21 | private Date endDate; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/vo/LoginVo.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.vo; 2 | 3 | import lombok.Data; 4 | import org.hibernate.validator.constraints.Length; 5 | import top.imyzt.study.miaosha.validator.IsMobile; 6 | 7 | import javax.validation.constraints.NotNull; 8 | import java.io.Serializable; 9 | 10 | /** 11 | * @author imyzt 12 | * @date 2019/3/9 11:30 13 | * @description LoginVo 14 | */ 15 | @Data 16 | public class LoginVo implements Serializable{ 17 | 18 | @NotNull 19 | @IsMobile 20 | private String mobile; 21 | 22 | @NotNull 23 | @Length(min = 6) 24 | private String password; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/top/imyzt/study/miaosha/vo/OrderDetailVo.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.vo; 2 | 3 | import lombok.Data; 4 | import top.imyzt.study.miaosha.domain.OrderInfo; 5 | 6 | /** 7 | * @author imyzt 8 | * @date 2019/3/19 16:39 9 | * @description OrderDetailVo 10 | */ 11 | @Data 12 | public class OrderDetailVo { 13 | private OrderInfo order; 14 | private GoodsVo goods; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | thymeleaf: 3 | cache: false 4 | enabled: true 5 | encoding: UTF-8 6 | mode: HTML5 7 | prefix: classpath:/templates/ 8 | suffix: .html 9 | servlet: 10 | content-type: text/html 11 | datasource: 12 | url: jdbc:mysql://localhost:3306/miaosha?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true 13 | username: root 14 | password: ntech 15 | driver-class-name: com.mysql.cj.jdbc.Driver 16 | type: com.alibaba.druid.pool.DruidDataSource 17 | druid: 18 | filters: stat 19 | max-active: 1000 20 | initial-size: 100 21 | max-wait: 60000 22 | min-idle: 500 23 | time-between-eviction-runs-millis: 60000 24 | min-evictable-idle-time-millis: 30000 25 | validation-query: select 'x' 26 | test-while-idle: true 27 | test-on-return: false 28 | test-on-borrow: false 29 | resources: 30 | add-mappings: true 31 | cache: 32 | period: 3600s 33 | chain: 34 | cache: true 35 | enabled: true 36 | compressed: true 37 | html-application-cache: true 38 | static-locations: classpath:/static/ 39 | rabbitmq: 40 | host: localhost 41 | port: 5672 42 | username: guest 43 | password: guest 44 | virtual-host: / 45 | listener: 46 | simple: 47 | # 消费者数量 48 | concurrency: 10 49 | max-concurrency: 10 50 | # 消费者每次取的数量 51 | prefetch: 1 52 | auto-startup: true 53 | # 消费失败后重试 54 | default-requeue-rejected: true 55 | retry: 56 | # 启动重试 57 | enabled: true 58 | # 1s重试一次 59 | initial-interval: 1000s 60 | # 最大重试三次 61 | max-attempts: 3 62 | # 最大间隔10s 63 | max-interval: 10000s 64 | # 上一次等待时间 * multiplier = 下一次重试的时间. (0 < 下次重试时间 <= max-interval) 65 | multiplier: 1.0 66 | mybatis: 67 | mapper-locations: classpath:/mapper/*Mapper.xml 68 | type-aliases-package: top.imyzt.study.miaosha.domain 69 | configuration: 70 | map-underscore-to-camel-case: true 71 | default-fetch-size: 100 72 | default-statement-timeout: 3000 73 | redis: 74 | host: localhost 75 | port: 6379 76 | database: 0 77 | timeout: 10000 78 | password: 79 | pool-max-total: 1000 80 | pool-max-idle: 500 81 | pool-max-wait: 500 82 | -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /src/main/resources/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/favicon.png -------------------------------------------------------------------------------- /src/main/resources/static/goods_detail.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 商品详情 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 33 | 34 | 35 | 36 |
37 |
秒杀商品详情
38 |
39 | 您还没有登录,请登陆后再操作
40 | 没有收货地址的提示。。。 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
商品名称
商品图片
秒杀开始时间 55 | 56 | 57 | 59 | 64 |
65 |
66 | 67 | 68 | 69 |
70 |
71 | 72 |
商品原价
秒杀价
库存数量
87 |
88 | 89 | 246 | 247 | -------------------------------------------------------------------------------- /src/main/resources/static/img/iphonex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/img/iphonex.png -------------------------------------------------------------------------------- /src/main/resources/static/img/meta10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/img/meta10.png -------------------------------------------------------------------------------- /src/main/resources/static/jquery-validation/additional-methods.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Validation Plugin - v1.14.0 - 6/30/2015 2 | * http://jqueryvalidation.org/ 3 | * Copyright (c) 2015 Jörn Zaefferer; Licensed MIT */ 4 | !function(a){"function"==typeof define&&define.amd?define(["jquery","./jquery.validate.min"],a):a(jQuery)}(function(a){!function(){function b(a){return a.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'\"_+=\/\-“”’]*/g,"")}a.validator.addMethod("maxWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length<=d},a.validator.format("Please enter {0} words or less.")),a.validator.addMethod("minWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length>=d},a.validator.format("Please enter at least {0} words.")),a.validator.addMethod("rangeWords",function(a,c,d){var e=b(a),f=/\b\w+\b/g;return this.optional(c)||e.match(f).length>=d[0]&&e.match(f).length<=d[1]},a.validator.format("Please enter between {0} and {1} words."))}(),a.validator.addMethod("accept",function(b,c,d){var e,f,g="string"==typeof d?d.replace(/\s/g,"").replace(/,/g,"|"):"image/*",h=this.optional(c);if(h)return h;if("file"===a(c).attr("type")&&(g=g.replace(/\*/g,".*"),c.files&&c.files.length))for(e=0;ec;c++)d=h-c,e=f.substring(c,c+1),g+=d*e;return g%11===0},"Please specify a valid bank account number"),a.validator.addMethod("bankorgiroaccountNL",function(b,c){return this.optional(c)||a.validator.methods.bankaccountNL.call(this,b,c)||a.validator.methods.giroaccountNL.call(this,b,c)},"Please specify a valid bank or giro account number"),a.validator.addMethod("bic",function(a,b){return this.optional(b)||/^([A-Z]{6}[A-Z2-9][A-NP-Z1-2])(X{3}|[A-WY-Z0-9][A-Z0-9]{2})?$/.test(a)},"Please specify a valid BIC code"),a.validator.addMethod("cifES",function(a){"use strict";var b,c,d,e,f,g,h=[];if(a=a.toUpperCase(),!a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)"))return!1;for(d=0;9>d;d++)h[d]=parseInt(a.charAt(d),10);for(c=h[2]+h[4]+h[6],e=1;8>e;e+=2)f=(2*h[e]).toString(),g=f.charAt(1),c+=parseInt(f.charAt(0),10)+(""===g?0:parseInt(g,10));return/^[ABCDEFGHJNPQRSUVW]{1}/.test(a)?(c+="",b=10-parseInt(c.charAt(c.length-1),10),a+=b,h[8].toString()===String.fromCharCode(64+b)||h[8].toString()===a.charAt(a.length-1)):!1},"Please specify a valid CIF number."),a.validator.addMethod("cpfBR",function(a){if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var b,c,d,e,f=0;if(b=parseInt(a.substring(9,10),10),c=parseInt(a.substring(10,11),10),d=function(a,b){var c=10*a%11;return(10===c||11===c)&&(c=0),c===b},""===a||"00000000000"===a||"11111111111"===a||"22222222222"===a||"33333333333"===a||"44444444444"===a||"55555555555"===a||"66666666666"===a||"77777777777"===a||"88888888888"===a||"99999999999"===a)return!1;for(e=1;9>=e;e++)f+=parseInt(a.substring(e-1,e),10)*(11-e);if(d(f,b)){for(f=0,e=1;10>=e;e++)f+=parseInt(a.substring(e-1,e),10)*(12-e);return d(f,c)}return!1},"Please specify a valid CPF number"),a.validator.addMethod("creditcardtypes",function(a,b,c){if(/[^0-9\-]+/.test(a))return!1;a=a.replace(/\D/g,"");var d=0;return c.mastercard&&(d|=1),c.visa&&(d|=2),c.amex&&(d|=4),c.dinersclub&&(d|=8),c.enroute&&(d|=16),c.discover&&(d|=32),c.jcb&&(d|=64),c.unknown&&(d|=128),c.all&&(d=255),1&d&&/^(5[12345])/.test(a)?16===a.length:2&d&&/^(4)/.test(a)?16===a.length:4&d&&/^(3[47])/.test(a)?15===a.length:8&d&&/^(3(0[012345]|[68]))/.test(a)?14===a.length:16&d&&/^(2(014|149))/.test(a)?15===a.length:32&d&&/^(6011)/.test(a)?16===a.length:64&d&&/^(3)/.test(a)?16===a.length:64&d&&/^(2131|1800)/.test(a)?15===a.length:128&d?!0:!1},"Please enter a valid credit card number."),a.validator.addMethod("currency",function(a,b,c){var d,e="string"==typeof c,f=e?c:c[0],g=e?!0:c[1];return f=f.replace(/,/g,""),f=g?f+"]":f+"]?",d="^["+f+"([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$",d=new RegExp(d),this.optional(b)||d.test(a)},"Please specify a valid currency"),a.validator.addMethod("dateFA",function(a,b){return this.optional(b)||/^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test(a)},a.validator.messages.date),a.validator.addMethod("dateITA",function(a,b){var c,d,e,f,g,h=!1,i=/^\d{1,2}\/\d{1,2}\/\d{4}$/;return i.test(a)?(c=a.split("/"),d=parseInt(c[0],10),e=parseInt(c[1],10),f=parseInt(c[2],10),g=new Date(Date.UTC(f,e-1,d,12,0,0,0)),h=g.getUTCFullYear()===f&&g.getUTCMonth()===e-1&&g.getUTCDate()===d?!0:!1):h=!1,this.optional(b)||h},a.validator.messages.date),a.validator.addMethod("dateNL",function(a,b){return this.optional(b)||/^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test(a)},a.validator.messages.date),a.validator.addMethod("extension",function(a,b,c){return c="string"==typeof c?c.replace(/,/g,"|"):"png|jpe?g|gif",this.optional(b)||a.match(new RegExp("\\.("+c+")$","i"))},a.validator.format("Please enter a value with a valid extension.")),a.validator.addMethod("giroaccountNL",function(a,b){return this.optional(b)||/^[0-9]{1,7}$/.test(a)},"Please specify a valid giro account number"),a.validator.addMethod("iban",function(a,b){if(this.optional(b))return!0;var c,d,e,f,g,h,i,j,k,l=a.replace(/ /g,"").toUpperCase(),m="",n=!0,o="",p="";if(c=l.substring(0,2),h={AL:"\\d{8}[\\dA-Z]{16}",AD:"\\d{8}[\\dA-Z]{12}",AT:"\\d{16}",AZ:"[\\dA-Z]{4}\\d{20}",BE:"\\d{12}",BH:"[A-Z]{4}[\\dA-Z]{14}",BA:"\\d{16}",BR:"\\d{23}[A-Z][\\dA-Z]",BG:"[A-Z]{4}\\d{6}[\\dA-Z]{8}",CR:"\\d{17}",HR:"\\d{17}",CY:"\\d{8}[\\dA-Z]{16}",CZ:"\\d{20}",DK:"\\d{14}",DO:"[A-Z]{4}\\d{20}",EE:"\\d{16}",FO:"\\d{14}",FI:"\\d{14}",FR:"\\d{10}[\\dA-Z]{11}\\d{2}",GE:"[\\dA-Z]{2}\\d{16}",DE:"\\d{18}",GI:"[A-Z]{4}[\\dA-Z]{15}",GR:"\\d{7}[\\dA-Z]{16}",GL:"\\d{14}",GT:"[\\dA-Z]{4}[\\dA-Z]{20}",HU:"\\d{24}",IS:"\\d{22}",IE:"[\\dA-Z]{4}\\d{14}",IL:"\\d{19}",IT:"[A-Z]\\d{10}[\\dA-Z]{12}",KZ:"\\d{3}[\\dA-Z]{13}",KW:"[A-Z]{4}[\\dA-Z]{22}",LV:"[A-Z]{4}[\\dA-Z]{13}",LB:"\\d{4}[\\dA-Z]{20}",LI:"\\d{5}[\\dA-Z]{12}",LT:"\\d{16}",LU:"\\d{3}[\\dA-Z]{13}",MK:"\\d{3}[\\dA-Z]{10}\\d{2}",MT:"[A-Z]{4}\\d{5}[\\dA-Z]{18}",MR:"\\d{23}",MU:"[A-Z]{4}\\d{19}[A-Z]{3}",MC:"\\d{10}[\\dA-Z]{11}\\d{2}",MD:"[\\dA-Z]{2}\\d{18}",ME:"\\d{18}",NL:"[A-Z]{4}\\d{10}",NO:"\\d{11}",PK:"[\\dA-Z]{4}\\d{16}",PS:"[\\dA-Z]{4}\\d{21}",PL:"\\d{24}",PT:"\\d{21}",RO:"[A-Z]{4}[\\dA-Z]{16}",SM:"[A-Z]\\d{10}[\\dA-Z]{12}",SA:"\\d{2}[\\dA-Z]{18}",RS:"\\d{18}",SK:"\\d{20}",SI:"\\d{15}",ES:"\\d{20}",SE:"\\d{20}",CH:"\\d{5}[\\dA-Z]{12}",TN:"\\d{20}",TR:"\\d{5}[\\dA-Z]{17}",AE:"\\d{3}\\d{16}",GB:"[A-Z]{4}\\d{14}",VG:"[\\dA-Z]{4}\\d{16}"},g=h[c],"undefined"!=typeof g&&(i=new RegExp("^[A-Z]{2}\\d{2}"+g+"$",""),!i.test(l)))return!1;for(d=l.substring(4,l.length)+l.substring(0,4),j=0;j9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number"),a.validator.addMethod("nieES",function(a){"use strict";return a=a.toUpperCase(),a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")?/^[T]{1}/.test(a)?a[8]===/^[T]{1}[A-Z0-9]{8}$/.test(a):/^[XYZ]{1}/.test(a)?a[8]==="TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.replace("X","0").replace("Y","1").replace("Z","2").substring(0,8)%23):!1:!1},"Please specify a valid NIE number."),a.validator.addMethod("nifES",function(a){"use strict";return a=a.toUpperCase(),a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")?/^[0-9]{8}[A-Z]{1}$/.test(a)?"TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,0)%23)===a.charAt(8):/^[KLM]{1}/.test(a)?a[8]===String.fromCharCode(64):!1:!1},"Please specify a valid NIF number."),jQuery.validator.addMethod("notEqualTo",function(b,c,d){return this.optional(c)||!a.validator.methods.equalTo.call(this,b,c,d)},"Please enter a different value, values must not be the same."),a.validator.addMethod("nowhitespace",function(a,b){return this.optional(b)||/^\S+$/i.test(a)},"No white space please"),a.validator.addMethod("pattern",function(a,b,c){return this.optional(b)?!0:("string"==typeof c&&(c=new RegExp("^(?:"+c+")$")),c.test(a))},"Invalid format."),a.validator.addMethod("phoneNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phoneUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number"),a.validator.addMethod("phoneUS",function(a,b){return a=a.replace(/\s+/g,""),this.optional(b)||a.length>9&&a.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/)},"Please specify a valid phone number"),a.validator.addMethod("phonesUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number"),a.validator.addMethod("postalCodeCA",function(a,b){return this.optional(b)||/^[ABCEGHJKLMNPRSTVXY]\d[A-Z] \d[A-Z]\d$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeBR",function(a,b){return this.optional(b)||/^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test(a)},"Informe um CEP válido."),a.validator.addMethod("postalcodeIT",function(a,b){return this.optional(b)||/^\d{5}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeNL",function(a,b){return this.optional(b)||/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postcodeUK",function(a,b){return this.optional(b)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(a)},"Please specify a valid UK postcode"),a.validator.addMethod("require_from_group",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_req_grp")?f.data("valid_req_grp"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length>=d[0];return f.data("valid_req_grp",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),h},a.validator.format("Please fill at least {0} of these fields.")),a.validator.addMethod("skip_or_fill_minimum",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_skip")?f.data("valid_skip"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length,i=0===h||h>=d[0];return f.data("valid_skip",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),i},a.validator.format("Please either skip these fields or fill at least {0} of them.")),a.validator.addMethod("stateUS",function(a,b,c){var d,e="undefined"==typeof c,f=e||"undefined"==typeof c.caseSensitive?!1:c.caseSensitive,g=e||"undefined"==typeof c.includeTerritories?!1:c.includeTerritories,h=e||"undefined"==typeof c.includeMilitary?!1:c.includeMilitary;return d=g||h?g&&h?"^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":g?"^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":"^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$":"^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$",d=f?new RegExp(d):new RegExp(d,"i"),this.optional(b)||d.test(a)},"Please specify a valid state"),a.validator.addMethod("strippedminlength",function(b,c,d){return a(b).text().length>=d},a.validator.format("Please enter at least {0} characters")),a.validator.addMethod("time",function(a,b){return this.optional(b)||/^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test(a)},"Please enter a valid time, between 00:00 and 23:59"),a.validator.addMethod("time12h",function(a,b){return this.optional(b)||/^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(a)},"Please enter a valid time in 12-hour am/pm format"),a.validator.addMethod("url2",function(a,b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},a.validator.messages.url),a.validator.addMethod("vinUS",function(a){if(17!==a.length)return!1;var b,c,d,e,f,g,h=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"],i=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9],j=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2],k=0;for(b=0;17>b;b++){if(e=j[b],d=a.slice(b,b+1),8===b&&(g=d),isNaN(d)){for(c=0;c").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),e=c.settings.submitHandler.call(c,c.currentForm,b),c.submitButton&&d.remove(),void 0!==e?e:!1):!0}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,d=d.concat(c.errorList)}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(b,c){i[c]=f[c],delete f[c],"required"===c&&a(j).removeAttr("aria-required")}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g),a(j).attr("aria-required","true")),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}),a.extend(a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){return!!a.trim(""+a(b).val())},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||-1!==a.inArray(c.keyCode,d)||(b.name in this.submitted||b===this.lastElement)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date ( ISO ).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c=a.data(this.form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!a(this).is(e.ignore)&&e[d].call(c,this,b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler),a(this.currentForm).find("[required], [data-rule-required], .required").attr("aria-required","true")},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c=this.clean(b),d=this.validationTargetFor(c),e=!0;return this.lastElement=d,void 0===d?delete this.invalid[c.name]:(this.prepareElement(d),this.currentElements=a(d),e=this.check(d)!==!1,e?delete this.invalid[d.name]:this.invalid[d.name]=!0),a(b).attr("aria-invalid",!e),this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),e},showErrors:function(b){if(b){a.extend(this.errorMap,b),this.errorList=[];for(var c in b)this.errorList.push({message:b[c],element:this.findByName(c)[0]});this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.submitted={},this.lastElement=null,this.prepareForm(),this.hideErrors();var b,c=this.elements().removeData("previousValue").removeAttr("aria-invalid");if(this.settings.unhighlight)for(b=0;c[b];b++)this.settings.unhighlight.call(this,c[b],this.settings.errorClass,"");else c.removeClass(this.settings.errorClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){return!this.name&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.name in c||!b.objectLength(a(this).rules())?!1:(c[this.name]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},reset:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([]),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d=a(b),e=b.type;return"radio"===e||"checkbox"===e?this.findByName(b.name).filter(":checked").val():"number"===e&&"undefined"!=typeof b.validity?b.validity.badInput?!1:d.val():(c=d.val(),"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f=a(b).rules(),g=a.map(f,function(a,b){return b}).length,h=!1,i=this.elementValue(b);for(d in f){e={method:d,parameters:f[d]};try{if(c=a.validator.methods[d].call(this,i,b,e.parameters),"dependency-mismatch"===c&&1===g){h=!0;continue}if(h=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(j){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",j),j instanceof TypeError&&(j.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),j}}if(!h)return this.objectLength(f)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+"")},formatAndAdd:function(b,c){var d=this.defaultMessage(b,c.method),e=/\$?\{(\d+)\}/g;"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),this.errorList.push({message:d,element:b,method:c.method}),this.errorMap[b.name]=d,this.submitted[b.name]=d},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g=this.errorsFor(b),h=this.idOrName(b),i=a(b).attr("aria-describedby");g.length?(g.removeClass(this.settings.validClass).addClass(this.settings.errorClass),g.html(c)):(g=a("<"+this.settings.errorElement+">").attr("id",h+"-error").addClass(this.settings.errorClass).html(c||""),d=g,this.settings.wrapper&&(d=g.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement(d,a(b)):d.insertAfter(b),g.is("label")?g.attr("for",h):0===g.parents("label[for='"+h+"']").length&&(f=g.attr("id").replace(/(:|\.|\[|\]|\$)/g,"\\$1"),i?i.match(new RegExp("\\b"+f+"\\b"))||(i+=" "+f):i=f,a(b).attr("aria-describedby",i),e=this.groups[b.name],e&&a.each(this.groups,function(b,c){c===e&&a("[name='"+b+"']",this.currentForm).attr("aria-describedby",g.attr("id"))}))),!c&&this.settings.success&&(g.text(""),"string"==typeof this.settings.success?g.addClass(this.settings.success):this.settings.success(g,b)),this.toShow=this.toShow.add(g)},errorsFor:function(b){var c=this.idOrName(b),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+d.replace(/\s+/g,", #")),this.errors().filter(e)},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+b+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return this.dependTypes[typeof a]?this.dependTypes[typeof a](a,b):!0},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(a){this.pending[a.name]||(this.pendingRequest++,this.pending[a.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b){return a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,"remote")})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0!==e.param?e.param:!0:delete b[d]}}),a.each(b,function(d,e){b[d]=a.isFunction(e)?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},creditcard:function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||d>=e},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||c>=a},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.off(".validate-equalTo").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d){if(this.optional(c))return"dependency-mismatch";var e,f,g=this.previousValue(c);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),g.originalMessage=this.settings.messages[c.name].remote,this.settings.messages[c.name].remote=g.message,d="string"==typeof d&&{url:d}||d,g.old===b?g.valid:(g.old=b,e=this,this.startRequest(c),f={},f[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:f,context:e.currentForm,success:function(d){var f,h,i,j=d===!0||"true"===d;e.settings.messages[c.name].remote=g.originalMessage,j?(i=e.formSubmitted,e.prepareElement(c),e.formSubmitted=i,e.successList.push(c),delete e.invalid[c.name],e.showErrors()):(f={},h=d||e.defaultMessage(c,"remote"),f[c.name]=g.message=a.isFunction(h)?h(b):h,e.invalid[c.name]=!0,e.showErrors(f)),g.valid=j,e.stopRequest(c,j)}},d)),"pending")}}});var b,c={};a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)})}); -------------------------------------------------------------------------------- /src/main/resources/static/jquery-validation/localization/messages_zh.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Validation Plugin - v1.14.0 - 6/30/2015 2 | * http://jqueryvalidation.org/ 3 | * Copyright (c) 2015 Jörn Zaefferer; Licensed MIT */ 4 | !function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):a(jQuery)}(function(a){a.extend(a.validator.messages,{required:"这是必填字段",remote:"请修正此字段",email:"请输入有效的电子邮件地址",url:"请输入有效的网址",date:"请输入有效的日期",dateISO:"请输入有效的日期 (YYYY-MM-DD)",number:"请输入有效的数字",digits:"只能输入数字",creditcard:"请输入有效的信用卡号码",equalTo:"你的输入不相同",extension:"请输入有效的后缀",maxlength:a.validator.format("最多可以输入 {0} 个字符"),minlength:a.validator.format("最少要输入 {0} 个字符"),rangelength:a.validator.format("请输入长度在 {0} 到 {1} 之间的字符串"),range:a.validator.format("请输入范围在 {0} 到 {1} 之间的数值"),max:a.validator.format("请输入不大于 {0} 的数值"),min:a.validator.format("请输入不小于 {0} 的数值")})}); -------------------------------------------------------------------------------- /src/main/resources/static/js/common.js: -------------------------------------------------------------------------------- 1 | //展示loading 2 | function g_showLoading(){ 3 | var idx = layer.msg('处理中...', {icon: 16,shade: [0.5, '#f5f5f5'],scrollbar: false,offset: '0px', time:100000}) ; 4 | return idx; 5 | } 6 | //salt 7 | var g_passsword_salt="fas_12^*(%&" 8 | // 获取url参数 9 | function g_getQueryString(name) { 10 | var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); 11 | var r = window.location.search.substr(1).match(reg); 12 | if(r != null) return unescape(r[2]); 13 | return null; 14 | }; 15 | //设定时间格式化函数,使用new Date().format("yyyyMMddhhmmss"); 16 | Date.prototype.format = function (format) { 17 | var args = { 18 | "M+": this.getMonth() + 1, 19 | "d+": this.getDate(), 20 | "h+": this.getHours(), 21 | "m+": this.getMinutes(), 22 | "s+": this.getSeconds(), 23 | }; 24 | if (/(y+)/.test(format)) 25 | format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); 26 | for (var i in args) { 27 | var n = args[i]; 28 | if (new RegExp("(" + i + ")").test(format)) 29 | format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? n : ("00" + n).substr(("" + n).length)); 30 | } 31 | return format; 32 | }; 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/static/js/md5.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [js-md5]{@link https://github.com/emn178/js-md5} 3 | * 4 | * @namespace md5 5 | * @version 0.7.2 6 | * @author Chen, Yi-Cyuan [emn178@gmail.com] 7 | * @copyright Chen, Yi-Cyuan 2014-2017 8 | * @license MIT 9 | */ 10 | !function(){"use strict";function Md5(t){if(t)blocks[0]=blocks[16]=blocks[1]=blocks[2]=blocks[3]=blocks[4]=blocks[5]=blocks[6]=blocks[7]=blocks[8]=blocks[9]=blocks[10]=blocks[11]=blocks[12]=blocks[13]=blocks[14]=blocks[15]=0,this.blocks=blocks,this.buffer8=buffer8;else if(ARRAY_BUFFER){var r=new ArrayBuffer(68);this.buffer8=new Uint8Array(r),this.blocks=new Uint32Array(r)}else this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];this.h0=this.h1=this.h2=this.h3=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0}var ERROR="input is invalid type",WINDOW="object"==typeof window,root=WINDOW?window:{};root.JS_MD5_NO_WINDOW&&(WINDOW=!1);var WEB_WORKER=!WINDOW&&"object"==typeof self,NODE_JS=!root.JS_MD5_NO_NODE_JS&&"object"==typeof process&&process.versions&&process.versions.node;NODE_JS?root=global:WEB_WORKER&&(root=self);var COMMON_JS=!root.JS_MD5_NO_COMMON_JS&&"object"==typeof module&&module.exports,AMD="function"==typeof define&&define.amd,ARRAY_BUFFER=!root.JS_MD5_NO_ARRAY_BUFFER&&"undefined"!=typeof ArrayBuffer,HEX_CHARS="0123456789abcdef".split(""),EXTRA=[128,32768,8388608,-2147483648],SHIFT=[0,8,16,24],OUTPUT_TYPES=["hex","array","digest","buffer","arrayBuffer","base64"],BASE64_ENCODE_CHAR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""),blocks=[],buffer8;if(ARRAY_BUFFER){var buffer=new ArrayBuffer(68);buffer8=new Uint8Array(buffer),blocks=new Uint32Array(buffer)}(root.JS_MD5_NO_NODE_JS||!Array.isArray)&&(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),!ARRAY_BUFFER||!root.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW&&ArrayBuffer.isView||(ArrayBuffer.isView=function(t){return"object"==typeof t&&t.buffer&&t.buffer.constructor===ArrayBuffer});var createOutputMethod=function(t){return function(r){return new Md5(!0).update(r)[t]()}},createMethod=function(){var t=createOutputMethod("hex");NODE_JS&&(t=nodeWrap(t)),t.create=function(){return new Md5},t.update=function(r){return t.create().update(r)};for(var r=0;ro;){if(this.hashed&&(this.hashed=!1,f[0]=f[16],f[16]=f[1]=f[2]=f[3]=f[4]=f[5]=f[6]=f[7]=f[8]=f[9]=f[10]=f[11]=f[12]=f[13]=f[14]=f[15]=0),r)if(ARRAY_BUFFER)for(i=this.start;h>o&&64>i;++o)a[i++]=t[o];else for(i=this.start;h>o&&64>i;++o)f[i>>2]|=t[o]<o&&64>i;++o)s=t.charCodeAt(o),128>s?a[i++]=s:2048>s?(a[i++]=192|s>>6,a[i++]=128|63&s):55296>s||s>=57344?(a[i++]=224|s>>12,a[i++]=128|s>>6&63,a[i++]=128|63&s):(s=65536+((1023&s)<<10|1023&t.charCodeAt(++o)),a[i++]=240|s>>18,a[i++]=128|s>>12&63,a[i++]=128|s>>6&63,a[i++]=128|63&s);else for(i=this.start;h>o&&64>i;++o)s=t.charCodeAt(o),128>s?f[i>>2]|=s<s?(f[i>>2]|=(192|s>>6)<>2]|=(128|63&s)<s||s>=57344?(f[i>>2]|=(224|s>>12)<>2]|=(128|s>>6&63)<>2]|=(128|63&s)<>2]|=(240|s>>18)<>2]|=(128|s>>12&63)<>2]|=(128|s>>6&63)<>2]|=(128|63&s)<=64?(this.start=i-64,this.hash(),this.hashed=!0):this.start=i}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%4294967296),this}},Md5.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var t=this.blocks,r=this.lastByteIndex;t[r>>2]|=EXTRA[3&r],r>=56&&(this.hashed||this.hash(),t[0]=t[16],t[16]=t[1]=t[2]=t[3]=t[4]=t[5]=t[6]=t[7]=t[8]=t[9]=t[10]=t[11]=t[12]=t[13]=t[14]=t[15]=0),t[14]=this.bytes<<3,t[15]=this.hBytes<<3|this.bytes>>29,this.hash()}},Md5.prototype.hash=function(){var t,r,e,s,i,o,h=this.blocks;this.first?(t=h[0]-680876937,t=(t<<7|t>>>25)-271733879<<0,s=(-1732584194^2004318071&t)+h[1]-117830708,s=(s<<12|s>>>20)+t<<0,e=(-271733879^s&(-271733879^t))+h[2]-1126478375,e=(e<<17|e>>>15)+s<<0,r=(t^e&(s^t))+h[3]-1316259209,r=(r<<22|r>>>10)+e<<0):(t=this.h0,r=this.h1,e=this.h2,s=this.h3,t+=(s^r&(e^s))+h[0]-680876936,t=(t<<7|t>>>25)+r<<0,s+=(e^t&(r^e))+h[1]-389564586,s=(s<<12|s>>>20)+t<<0,e+=(r^s&(t^r))+h[2]+606105819,e=(e<<17|e>>>15)+s<<0,r+=(t^e&(s^t))+h[3]-1044525330,r=(r<<22|r>>>10)+e<<0),t+=(s^r&(e^s))+h[4]-176418897,t=(t<<7|t>>>25)+r<<0,s+=(e^t&(r^e))+h[5]+1200080426,s=(s<<12|s>>>20)+t<<0,e+=(r^s&(t^r))+h[6]-1473231341,e=(e<<17|e>>>15)+s<<0,r+=(t^e&(s^t))+h[7]-45705983,r=(r<<22|r>>>10)+e<<0,t+=(s^r&(e^s))+h[8]+1770035416,t=(t<<7|t>>>25)+r<<0,s+=(e^t&(r^e))+h[9]-1958414417,s=(s<<12|s>>>20)+t<<0,e+=(r^s&(t^r))+h[10]-42063,e=(e<<17|e>>>15)+s<<0,r+=(t^e&(s^t))+h[11]-1990404162,r=(r<<22|r>>>10)+e<<0,t+=(s^r&(e^s))+h[12]+1804603682,t=(t<<7|t>>>25)+r<<0,s+=(e^t&(r^e))+h[13]-40341101,s=(s<<12|s>>>20)+t<<0,e+=(r^s&(t^r))+h[14]-1502002290,e=(e<<17|e>>>15)+s<<0,r+=(t^e&(s^t))+h[15]+1236535329,r=(r<<22|r>>>10)+e<<0,t+=(e^s&(r^e))+h[1]-165796510,t=(t<<5|t>>>27)+r<<0,s+=(r^e&(t^r))+h[6]-1069501632,s=(s<<9|s>>>23)+t<<0,e+=(t^r&(s^t))+h[11]+643717713,e=(e<<14|e>>>18)+s<<0,r+=(s^t&(e^s))+h[0]-373897302,r=(r<<20|r>>>12)+e<<0,t+=(e^s&(r^e))+h[5]-701558691,t=(t<<5|t>>>27)+r<<0,s+=(r^e&(t^r))+h[10]+38016083,s=(s<<9|s>>>23)+t<<0,e+=(t^r&(s^t))+h[15]-660478335,e=(e<<14|e>>>18)+s<<0,r+=(s^t&(e^s))+h[4]-405537848,r=(r<<20|r>>>12)+e<<0,t+=(e^s&(r^e))+h[9]+568446438,t=(t<<5|t>>>27)+r<<0,s+=(r^e&(t^r))+h[14]-1019803690,s=(s<<9|s>>>23)+t<<0,e+=(t^r&(s^t))+h[3]-187363961,e=(e<<14|e>>>18)+s<<0,r+=(s^t&(e^s))+h[8]+1163531501,r=(r<<20|r>>>12)+e<<0,t+=(e^s&(r^e))+h[13]-1444681467,t=(t<<5|t>>>27)+r<<0,s+=(r^e&(t^r))+h[2]-51403784,s=(s<<9|s>>>23)+t<<0,e+=(t^r&(s^t))+h[7]+1735328473,e=(e<<14|e>>>18)+s<<0,r+=(s^t&(e^s))+h[12]-1926607734,r=(r<<20|r>>>12)+e<<0,i=r^e,t+=(i^s)+h[5]-378558,t=(t<<4|t>>>28)+r<<0,s+=(i^t)+h[8]-2022574463,s=(s<<11|s>>>21)+t<<0,o=s^t,e+=(o^r)+h[11]+1839030562,e=(e<<16|e>>>16)+s<<0,r+=(o^e)+h[14]-35309556,r=(r<<23|r>>>9)+e<<0,i=r^e,t+=(i^s)+h[1]-1530992060,t=(t<<4|t>>>28)+r<<0,s+=(i^t)+h[4]+1272893353,s=(s<<11|s>>>21)+t<<0,o=s^t,e+=(o^r)+h[7]-155497632,e=(e<<16|e>>>16)+s<<0,r+=(o^e)+h[10]-1094730640,r=(r<<23|r>>>9)+e<<0,i=r^e,t+=(i^s)+h[13]+681279174,t=(t<<4|t>>>28)+r<<0,s+=(i^t)+h[0]-358537222,s=(s<<11|s>>>21)+t<<0,o=s^t,e+=(o^r)+h[3]-722521979,e=(e<<16|e>>>16)+s<<0,r+=(o^e)+h[6]+76029189,r=(r<<23|r>>>9)+e<<0,i=r^e,t+=(i^s)+h[9]-640364487,t=(t<<4|t>>>28)+r<<0,s+=(i^t)+h[12]-421815835,s=(s<<11|s>>>21)+t<<0,o=s^t,e+=(o^r)+h[15]+530742520,e=(e<<16|e>>>16)+s<<0,r+=(o^e)+h[2]-995338651,r=(r<<23|r>>>9)+e<<0,t+=(e^(r|~s))+h[0]-198630844,t=(t<<6|t>>>26)+r<<0,s+=(r^(t|~e))+h[7]+1126891415,s=(s<<10|s>>>22)+t<<0,e+=(t^(s|~r))+h[14]-1416354905,e=(e<<15|e>>>17)+s<<0,r+=(s^(e|~t))+h[5]-57434055,r=(r<<21|r>>>11)+e<<0,t+=(e^(r|~s))+h[12]+1700485571,t=(t<<6|t>>>26)+r<<0,s+=(r^(t|~e))+h[3]-1894986606,s=(s<<10|s>>>22)+t<<0,e+=(t^(s|~r))+h[10]-1051523,e=(e<<15|e>>>17)+s<<0,r+=(s^(e|~t))+h[1]-2054922799,r=(r<<21|r>>>11)+e<<0,t+=(e^(r|~s))+h[8]+1873313359,t=(t<<6|t>>>26)+r<<0,s+=(r^(t|~e))+h[15]-30611744,s=(s<<10|s>>>22)+t<<0,e+=(t^(s|~r))+h[6]-1560198380,e=(e<<15|e>>>17)+s<<0,r+=(s^(e|~t))+h[13]+1309151649,r=(r<<21|r>>>11)+e<<0,t+=(e^(r|~s))+h[4]-145523070,t=(t<<6|t>>>26)+r<<0,s+=(r^(t|~e))+h[11]-1120210379,s=(s<<10|s>>>22)+t<<0,e+=(t^(s|~r))+h[2]+718787259,e=(e<<15|e>>>17)+s<<0,r+=(s^(e|~t))+h[9]-343485551,r=(r<<21|r>>>11)+e<<0,this.first?(this.h0=t+1732584193<<0,this.h1=r-271733879<<0,this.h2=e-1732584194<<0,this.h3=s+271733878<<0,this.first=!1):(this.h0=this.h0+t<<0,this.h1=this.h1+r<<0,this.h2=this.h2+e<<0,this.h3=this.h3+s<<0)},Md5.prototype.hex=function(){this.finalize();var t=this.h0,r=this.h1,e=this.h2,s=this.h3;return HEX_CHARS[t>>4&15]+HEX_CHARS[15&t]+HEX_CHARS[t>>12&15]+HEX_CHARS[t>>8&15]+HEX_CHARS[t>>20&15]+HEX_CHARS[t>>16&15]+HEX_CHARS[t>>28&15]+HEX_CHARS[t>>24&15]+HEX_CHARS[r>>4&15]+HEX_CHARS[15&r]+HEX_CHARS[r>>12&15]+HEX_CHARS[r>>8&15]+HEX_CHARS[r>>20&15]+HEX_CHARS[r>>16&15]+HEX_CHARS[r>>28&15]+HEX_CHARS[r>>24&15]+HEX_CHARS[e>>4&15]+HEX_CHARS[15&e]+HEX_CHARS[e>>12&15]+HEX_CHARS[e>>8&15]+HEX_CHARS[e>>20&15]+HEX_CHARS[e>>16&15]+HEX_CHARS[e>>28&15]+HEX_CHARS[e>>24&15]+HEX_CHARS[s>>4&15]+HEX_CHARS[15&s]+HEX_CHARS[s>>12&15]+HEX_CHARS[s>>8&15]+HEX_CHARS[s>>20&15]+HEX_CHARS[s>>16&15]+HEX_CHARS[s>>28&15]+HEX_CHARS[s>>24&15]},Md5.prototype.toString=Md5.prototype.hex,Md5.prototype.digest=function(){this.finalize();var t=this.h0,r=this.h1,e=this.h2,s=this.h3;return[255&t,t>>8&255,t>>16&255,t>>24&255,255&r,r>>8&255,r>>16&255,r>>24&255,255&e,e>>8&255,e>>16&255,e>>24&255,255&s,s>>8&255,s>>16&255,s>>24&255]},Md5.prototype.array=Md5.prototype.digest,Md5.prototype.arrayBuffer=function(){this.finalize();var t=new ArrayBuffer(16),r=new Uint32Array(t);return r[0]=this.h0,r[1]=this.h1,r[2]=this.h2,r[3]=this.h3,t},Md5.prototype.buffer=Md5.prototype.arrayBuffer,Md5.prototype.base64=function(){for(var t,r,e,s="",i=this.array(),o=0;15>o;)t=i[o++],r=i[o++],e=i[o++],s+=BASE64_ENCODE_CHAR[t>>>2]+BASE64_ENCODE_CHAR[63&(t<<4|r>>>4)]+BASE64_ENCODE_CHAR[63&(r<<2|e>>>6)]+BASE64_ENCODE_CHAR[63&e];return t=i[o],s+=BASE64_ENCODE_CHAR[t>>>2]+BASE64_ENCODE_CHAR[t<<4&63]+"=="};var exports=createMethod();COMMON_JS?module.exports=exports:(root.md5=exports,AMD&&define(function(){return exports}))}(); -------------------------------------------------------------------------------- /src/main/resources/static/layer/mobile/layer.js: -------------------------------------------------------------------------------- 1 | /*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */ 2 | ;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'

'+(e?n.title[0]:n.title)+"

":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e=''+n.btn[0]+"",2===t&&(e=''+n.btn[1]+""+e),'
'+e+"
"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='

'+(n.content||"")+"

"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"
':"")+'
"+l+'
'+n.content+"
"+c+"
",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;odiv{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} -------------------------------------------------------------------------------- /src/main/resources/static/layer/skin/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/layer/skin/default/icon-ext.png -------------------------------------------------------------------------------- /src/main/resources/static/layer/skin/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/layer/skin/default/icon.png -------------------------------------------------------------------------------- /src/main/resources/static/layer/skin/default/layer.css: -------------------------------------------------------------------------------- 1 | .layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}*html{background-image:url(about:blank);background-attachment:fixed}html #layuicss-skinlayercss{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layui-layer{border-radius:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim{-webkit-animation-name:layer-bounceIn;animation-name:layer-bounceIn}@-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown}@-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig}@-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft}@-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn}@keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn}@-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:1px -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 10px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:6px 6px 0;padding:0 15px;border:1px solid #dedede;background-color:#f1f1f1;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#4898d5;background-color:#2e8ded;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:5px 10px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:1px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:5px 10px 10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#BBB5B5;border:none}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:220px;height:30px;margin:0 auto;line-height:30px;padding:0 5px;border:1px solid #ccc;box-shadow:1px 1px 5px rgba(0,0,0,.1) inset;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;border-bottom:1px solid #ccc;background-color:#eee;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;cursor:default;overflow:hidden}.layui-layer-tab .layui-layer-title span.layui-layer-tabnow{height:43px;border-left:1px solid #ccc;border-right:1px solid #ccc;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.xubox_tab_layer{display:block}.xubox_tabclose{position:absolute;right:10px;top:5px;cursor:pointer}.layui-layer-photos{-webkit-animation-duration:.8s;animation-duration:.8s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@-webkit-keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:layer-bounceOut;animation-name:layer-bounceOut;-webkit-animation-duration:.2s;animation-duration:.2s}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}} -------------------------------------------------------------------------------- /src/main/resources/static/layer/skin/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/layer/skin/default/loading-0.gif -------------------------------------------------------------------------------- /src/main/resources/static/layer/skin/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/layer/skin/default/loading-1.gif -------------------------------------------------------------------------------- /src/main/resources/static/layer/skin/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imyzt/imooc_miaosha/630678201bc32effde86348b86ecd2bed3bba5f1/src/main/resources/static/layer/skin/default/loading-2.gif -------------------------------------------------------------------------------- /src/main/resources/static/order_detail.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 订单详情 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
秒杀订单详情
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
商品名称
商品图片
订单价格
下单时间
订单状态 44 | 46 | 47 |
收货人XXX 18812341234
收货地址北京市昌平区回龙观龙博一区
58 |
59 | 60 | 61 | 107 | -------------------------------------------------------------------------------- /src/main/resources/templates/goods_detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 商品详情 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
秒杀商品详情
25 |
26 | 您还没有登录,请登陆后再操作
27 | 没有收货地址的提示。。。 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
商品名称
商品图片
秒杀开始时间 42 | 43 | 秒杀倒计时: 44 | 秒杀进行中 45 | 秒杀已结束 46 | 48 |
49 | 50 | 51 |
52 |
商品原价
秒杀价
库存数量
67 |
68 | 69 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/resources/templates/goods_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 商品列表 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
秒杀商品列表
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
商品名称商品图片商品原价秒杀价库存数量详情
详情
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/templates/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hello 5 | 6 | 7 | 8 |

9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 登录 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |

用户登录

28 | 29 |
30 |
31 | 32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 | 43 |
44 | 45 |
46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 |
57 | 58 |
59 | 60 | 99 | -------------------------------------------------------------------------------- /src/main/resources/templates/miaosha_fail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 秒杀失败 5 | 6 | 7 | 8 | 秒杀失败:

9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/order_detail.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 订单详情 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
秒杀订单详情
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
商品名称
商品图片
订单价格
下单时间
订单状态 44 | 46 | 47 |
收货人XXX 18812341234
收货地址北京市昌平区回龙观龙博一区
58 |
59 | 60 | 61 | 107 | -------------------------------------------------------------------------------- /src/main/resources/templates/order_detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 订单详情 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
秒杀订单详情
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
商品名称
商品图片
订单价格
下单时间
订单状态 44 | 未支付 45 | 待发货 46 | 已发货 47 | 已收货 48 | 已退款 49 | 已完成 50 | 52 | 53 |
收货人XXX 18812341234
收货地址北京市昌平区回龙观龙博一区
64 |
65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/test/java/top/imyzt/study/miaosha/MiaoshaApplicationTests.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class MiaoshaApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() {} 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/top/imyzt/study/miaosha/service/MiaoshaUserServiceTest.java: -------------------------------------------------------------------------------- 1 | package top.imyzt.study.miaosha.service; 2 | 3 | import cn.hutool.core.io.FileUtil; 4 | import cn.hutool.core.util.RandomUtil; 5 | import cn.hutool.http.HttpRequest; 6 | import cn.hutool.http.HttpResponse; 7 | import cn.hutool.http.HttpStatus; 8 | import cn.hutool.http.HttpUtil; 9 | import com.alibaba.fastjson.JSON; 10 | import com.alibaba.fastjson.JSONObject; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import top.imyzt.study.miaosha.domain.MiaoshaUser; 18 | import top.imyzt.study.miaosha.exception.GlobalException; 19 | import top.imyzt.study.miaosha.utils.MD5Util; 20 | 21 | import java.io.*; 22 | import java.net.HttpCookie; 23 | import java.net.HttpURLConnection; 24 | import java.net.URL; 25 | import java.sql.Date; 26 | import java.time.Instant; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.concurrent.CopyOnWriteArrayList; 30 | import java.util.concurrent.atomic.AtomicInteger; 31 | import java.util.stream.IntStream; 32 | 33 | import static org.junit.Assert.*; 34 | 35 | @SpringBootTest 36 | @RunWith(SpringRunner.class) 37 | @Slf4j 38 | public class MiaoshaUserServiceTest { 39 | 40 | @Autowired 41 | private MiaoshaUserService userService; 42 | 43 | /** 44 | * 注册N个用户, 并处理登录.将token存入redis中. 45 | */ 46 | @Test 47 | public void register() { 48 | 49 | long epochSecond = Instant.now().getEpochSecond(); 50 | List miaoshaUsers = new CopyOnWriteArrayList<>(); 51 | 52 | IntStream.range(1, 5000) 53 | .parallel().forEach(i -> { 54 | 55 | // 生成用户 56 | MiaoshaUser user = new MiaoshaUser(); 57 | user.setId(13000000000L + i); 58 | user.setNickname("testUser_" + i); 59 | String formPass = MD5Util.inputPassToFormPass(RandomUtil.randomString(10)); 60 | user.setPassword(formPass); 61 | user.setHead("head"); 62 | user.setRegisterDate(Date.from(Instant.now())); 63 | user.setLoginCount(1); 64 | 65 | // 注册用户 66 | userService.register(user); 67 | 68 | // 保存用户 69 | miaoshaUsers.add(user); 70 | log.info("save user index ={}", user.getId()); 71 | }); 72 | 73 | CopyOnWriteArrayList tokens = new CopyOnWriteArrayList <>(); 74 | miaoshaUsers.forEach(user -> { 75 | 76 | HttpResponse execute = HttpRequest.post("http://localhost:8080/login/do_login") 77 | .form("mobile", user.getId()) 78 | .form("password", user.getPassword()) 79 | .timeout(2000) 80 | .execute(); 81 | 82 | if (execute.getStatus() != HttpStatus.HTTP_OK) { 83 | throw new RuntimeException("请求错误"); 84 | } 85 | String token = execute.getCookie("token").getValue(); 86 | 87 | tokens.add(user.getId()+","+token); 88 | log.info("save token index ={}", user.getId()); 89 | 90 | }); 91 | 92 | FileUtil.writeUtf8Lines(tokens, "D:/tmp/tokens.txt"); 93 | 94 | System.out.println(Instant.now().toEpochMilli() - epochSecond); 95 | } 96 | } 97 | --------------------------------------------------------------------------------