package com.java1234.miaosha.controller;
import com.java1234.miaosha.constant.Constant;
import com.java1234.miaosha.entity.Order;
import com.java1234.miaosha.entity.R;
import com.java1234.miaosha.entity.User;
import com.java1234.miaosha.service.IMiaoShaGoodsService;
import com.java1234.miaosha.service.IMiaoShaService;
import com.java1234.miaosha.service.IOrderService;
import com.java1234.miaosha.util.RedisUtil;
import com.java1234.miaosha.util.StringUtil;
import com.java1234.miaosha.vo.MiaoShaGoodsVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* 秒杀控制器
*/
@RestController
@RequestMapping("/miaoSha")
public class MiaoShaController {
@Autowired
private RedisUtil redisUtil;
@Autowired
private IMiaoShaGoodsService miaoShaGoodsService;
@Autowired
private IMiaoShaService miaoShaService;
@Autowired
private IOrderService orderService;
/**
* 执行秒杀
* @param request
* @param miaoShaGoodsId
* @return
*/
@RequestMapping("/exec")
public R exec(HttpServletRequest request, Integer miaoShaGoodsId,String verifyCode){
if(StringUtil.isEmpty(verifyCode)){
return R.error("验证码不能为空!");
}
//第一步:根据token得到用户user对象
String token = request.getHeader("token");
System.out.println("token:"+token);
User user = (User)redisUtil.get(Constant.REDIS_TOKEN_PREFIX, token);
System.out.println(user);
Object rnd=redisUtil.get(Constant.REDIS_VERIFYCODE_PREFIX, user.getId() + "," + miaoShaGoodsId);
if(rnd==null){
return R.error("验证码过期,请重新点击刷新验证码!");
}
if(!verifyCode.equals(String.valueOf(rnd))){
return R.error("验证码结果错误!");
}
//第二步:判断库存是否足够
MiaoShaGoodsVo miaoShaGoods = miaoShaGoodsService.findById(miaoShaGoodsId);
Integer stock = miaoShaGoods.getStock();
if(stock<=0){
return R.error("库存不足!");
}
//第三步:判断用户是否重复秒杀
HashMap map = new HashMap();
map.put("user_id",user.getId());
map.put("miaosha_goods_id",miaoShaGoodsId);
Order orderE = orderService.findByUserIdAndMiaoShaGoodsId(map);
if(orderE!=null){
return R.error("您已经秒杀过此商品,不能重复秒杀");
}
//第四步:减库存,下订单,必须同一个事务
String orderId = miaoShaService.miaoSha(user, miaoShaGoods);
if(orderId!=null){
Map<String,Object> resultMap=new HashMap<>();
resultMap.put("orderId",orderId);
return R.ok(resultMap);
}else{
return R.error("系统异常,请稍后重试!");
}
}
}
package com.java1234.miaosha.controller;
import com.java1234.miaosha.constant.Constant;
import com.java1234.miaosha.entity.R;
import com.java1234.miaosha.entity.User;
import com.java1234.miaosha.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.imageio.ImageIO;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* 订单控制器
* @author java1234_小锋
* @site www.java1234.com
* @company Java知识分享网
* @create 2020-12-13 11:20
*/
@Controller
@RequestMapping("/verifyCode")
public class VerifyCodeController {
@Autowired
private RedisUtil redisUtil;
/**
* 返回验证码图片
* @param miaoShaGoodsId
* @return
*/
@RequestMapping(value = "/get",method = RequestMethod.GET)
public R get(HttpServletResponse response,String token, Integer miaoShaGoodsId){
System.out.println("token:"+token);
System.out.println("miaoShaGoodsId:"+miaoShaGoodsId);
/* String token = request.getParameter("token");
System.out.println("token:"+token);*/
Object o=redisUtil.get(Constant.REDIS_TOKEN_PREFIX,token);
if(o==null){
return null;
}
if(miaoShaGoodsId==null || miaoShaGoodsId <=0){
return null;
}
try {
BufferedImage image = this.createVerifyCodeImage(((User) o).getId(), miaoShaGoodsId);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image,"JPEG",out);
out.flush();
out.close();
return null;
}catch(Exception e){
e.printStackTrace();
return R.error("服务端异常");
}
}
private BufferedImage createVerifyCodeImage(Integer userId,Integer miaoShaGoodsId) {
int width=80;
int height=32;
BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Graphics g=image.getGraphics();
g.setColor(new Color(0xDCDCDC));
g.fillRect(0,0,width,height);
g.setColor(Color.black);
g.drawRect(0,0,width-1,height-1);
Random rdm=new Random();
for(int i=0;i<50;i++){
int x=rdm.nextInt(width);
int y=rdm.nextInt(height);
g.drawOval(x,y,0,0);
}
String verifyCode=createVerifyCode();
System.out.println("验证:"+verifyCode);
g.setColor(new Color(0,100,0));
g.setFont(new Font("Candara",Font.BOLD,24));
g.drawString(verifyCode+"=",8,24);
g.dispose();
int rnd=calc(verifyCode);
System.out.println("rnd:"+rnd);
redisUtil.set(Constant.REDIS_VERIFYCODE_PREFIX,userId+","+miaoShaGoodsId,rnd,Constant.REDIS_VERIFYCODE_EXPIRE);
return image;
}
public static void main(String[] args) {
String verifyCode=createVerifyCode();
System.out.println(calc(verifyCode));
}
/**
* 计算表达式
* @param exp
* @return
*/
private static int calc(String exp) {
ScriptEngineManager manager=new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
return (int) engine.eval(exp);
} catch (ScriptException e) {
e.printStackTrace();
return 0;
}
}
private static char[] ops=new char[]{'+','-'};
/**
* + - 运算
* @return
*/
private static String createVerifyCode() {
Random rdm=new Random();
int num1=rdm.nextInt(10);
int num2=rdm.nextInt(10);
char op1=ops[rdm.nextInt(2)];
System.out.println("op1:"+op1);
String exp=num1+String.valueOf(op1)+num2;
System.out.println("exp:"+exp);
return exp;
}
}
<template>
<div>
<el-container>
<el-header height="120px">
<miao-sha-header></miao-sha-header>
</el-header>
<el-main>
<p class="goods_head">秒杀商品详情</p>
<el-form label-position="left" label-width="120px">
<el-form-item label="商品名称:">
{{this.miaoShaGoods.goods.name}}
</el-form-item>
<el-form-item label="商品图片:">
<img :src="getSrcUrl(this.miaoShaGoods.goods.image)"/>
</el-form-item>
<el-form-item label="商品原价:">
{{this.miaoShaGoods.goods.price+'元'}}
</el-form-item>
<el-form-item label="秒杀价:">
{{this.miaoShaGoods.price+'元'}}
</el-form-item>
<el-form-item label="库存数量:">
{{this.miaoShaGoods.stock}}
</el-form-item>
<el-form-item label="秒杀开始时间:">
{{this.miaoShaGoods.startTime}}
<span v-show="miaoShaGoods.miaoShaStatus==0">秒杀倒计时:{{miaoShaGoods.remainBeginSecond}}秒</span>
<span v-show="miaoShaGoods.miaoShaStatus==1">秒杀进行中</span>
<span v-show="miaoShaGoods.miaoShaStatus==2">秒杀结束</span>
</el-form-item>
<el-form-item label="秒杀结束时间:">
{{this.miaoShaGoods.endTime}}
</el-form-item>
</el-form>
<img id="verifyCodeImg" width="80" height="32" :src="verifyCodeImgSrc" v-show="miaoShaGoods.miaoShaStatus==1" @click="refreshVerifyCode">
<input id="verifyCode" type="text" style="width: 60px;height: 23px;padding: 4px;border: 1px solid gray;" v-model="verifyCode" v-show="miaoShaGoods.miaoShaStatus==1"/>
<el-button v-show="miaoShaGoods.miaoShaStatus==1" type="primary" size="small" @click="exec_miaosha">立即秒杀</el-button>
</el-main>
<el-footer>
<miao-sha-footer></miao-sha-footer>
</el-footer>
</el-container>
</div>
</template>
<script>
import MiaoShaHeader from './common/Header';
import MiaoShaFooter from './common/Footer';
import axios from 'axios';
import {getServerUrl} from '@/config/sys';
export default {
name: 'Detail',
data() {
return {
miaoShaGoods:{
goods:{
name:'',
image:'default.jpg',
price:0
}
},
verifyCodeImgSrc:"",
verifyCode:""
}
},
components: {
MiaoShaHeader,
MiaoShaFooter
},
methods: {
getSrcUrl(t) {
return getServerUrl('image/' + t);
},
exec_miaosha(){
if(this.verifyCode==""){
alert("请输入验证码结果!");
return;
}
let url = getServerUrl("miaoSha/exec");
let token = window.sessionStorage.getItem("token");
axios.defaults.headers.common['token'] = token;
axios.get(url,{
params:{
miaoShaGoodsId:this.$route.params.id,
verifyCode:this.verifyCode
}
}).then(response=>{
let data = response.data;
if(data.code!=0){
alert(data.msg);
}else{
alert(data.orderId);
}
}).catch(error=>{
alert(error+"-请联系管理员");
})
},
getInfo(){
let url = getServerUrl("miaoShaGoods/detail");
let token = window.sessionStorage.getItem("token");
axios.defaults.headers.common['token'] = token;
axios.get(url,{
params:{
id:this.$route.params.id
}
}).then(response=>{
console.log(response.data.data);
this.miaoShaGoods = response.data.data;
this.countDown();
if(this.miaoShaGoods.remainEndSecond>0){//秒杀还没结束
setTimeout(()=>{
this.miaoShaGoods.miaoShaStatus=2;
},this.miaoShaGoods.remainEndSecond*1000)
}
}).catch(error=>{
alert(error+"-请联系管理员");
})
},
countDown(){
let timeout;
let rs=this.miaoShaGoods.remainBeginSecond;
if(rs>0){ //秒杀还没开始,倒计时
timeout=setTimeout(()=>{
this.miaoShaGoods.remainBeginSecond=this.miaoShaGoods.remainBeginSecond-1;
this.countDown();
},1000);
}else if(rs==0){ //秒杀进行中
this.miaoShaGoods.miaoShaStatus=1;
let url = getServerUrl("verifyCode/get");
this.verifyCodeImgSrc=url+"?miaoShaGoodsId="+this.$route.params.id+"&token="+window.sessionStorage.getItem("token");
if(timeout){
clearTimeout(timeout);
}
}else{ //秒杀结束
this.miaoShaGoods.miaoShaStatus=2;
}
},
refreshVerifyCode(){
let url = getServerUrl("verifyCode/get");
this.verifyCodeImgSrc=url+"?miaoShaGoodsId="+this.$route.params.id+"&token="+window.sessionStorage.getItem("token")+"×tamp="+new Date().getTime();
}
},
mounted() {
this.getInfo();
}
}
</script>
<style scoped>
.goods_head{
font-weight: bold;
font-size: 20px;
}
</style>
@charset "utf-8";
.border,
.border-top,
.border-right,
.border-bottom,
.border-left,
.border-topbottom,
.border-rightleft,
.border-topleft,
.border-rightbottom,
.border-topright,
.border-bottomleft {
position: relative;
}
.border::before,
.border-top::before,
.border-right::before,
.border-bottom::before,
.border-left::before,
.border-topbottom::before,
.border-topbottom::after,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::before,
.border-topleft::after,
.border-rightbottom::before,
.border-rightbottom::after,
.border-topright::before,
.border-topright::after,
.border-bottomleft::before,
.border-bottomleft::after {
content: "\0020";
overflow: hidden;
position: absolute;
}
/* border
* 因,边框是由伪元素区域遮盖在父级
* 故,子级若有交互,需要对子级设置
* 定位 及 z轴
*/
.border::before {
box-sizing: border-box;
top: 0;
left: 0;
height: 100%;
width: 100%;
border: 1px solid
transform-origin: 0 0;
}
.border-top::before,
.border-bottom::before,
.border-topbottom::before,
.border-topbottom::after,
.border-topleft::before,
.border-rightbottom::after,
.border-topright::before,
.border-bottomleft::before {
left: 0;
width: 100%;
height: 1px;
}
.border-right::before,
.border-left::before,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::after,
.border-rightbottom::before,
.border-topright::after,
.border-bottomleft::after {
top: 0;
width: 1px;
height: 100%;
}
.border-top::before,
.border-topbottom::before,
.border-topleft::before,
.border-topright::before {
border-top: 1px solid
transform-origin: 0 0;
}
.border-right::before,
.border-rightbottom::before,
.border-rightleft::before,
.border-topright::after {
border-right: 1px solid
transform-origin: 100% 0;
}
.border-bottom::before,
.border-topbottom::after,
.border-rightbottom::after,
.border-bottomleft::before {
border-bottom: 1px solid
transform-origin: 0 100%;
}
.border-left::before,
.border-topleft::after,
.border-rightleft::after,
.border-bottomleft::after {
border-left: 1px solid
transform-origin: 0 0;
}
.border-top::before,
.border-topbottom::before,
.border-topleft::before,
.border-topright::before {
top: 0;
}
.border-right::before,
.border-rightleft::after,
.border-rightbottom::before,
.border-topright::after {
right: 0;
}
.border-bottom::before,
.border-topbottom::after,
.border-rightbottom::after,
.border-bottomleft::after {
bottom: 0;
}
.border-left::before,
.border-rightleft::before,
.border-topleft::after,
.border-bottomleft::before {
left: 0;
}
@media (max--moz-device-pixel-ratio: 1.49), (-webkit-max-device-pixel-ratio: 1.49), (max-device-pixel-ratio: 1.49), (max-resolution: 143dpi), (max-resolution: 1.49dppx) {
/* 默认值,无需重置 */
}
@media (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49), (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49), (min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49), (min-resolution: 144dpi) and (max-resolution: 239dpi), (min-resolution: 1.5dppx) and (max-resolution: 2.49dppx) {
.border::before {
width: 200%;
height: 200%;
transform: scale(.5);
}
.border-top::before,
.border-bottom::before,
.border-topbottom::before,
.border-topbottom::after,
.border-topleft::before,
.border-rightbottom::after,
.border-topright::before,
.border-bottomleft::before {
transform: scaleY(.5);
}
.border-right::before,
.border-left::before,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::after,
.border-rightbottom::before,
.border-topright::after,
.border-bottomleft::after {
transform: scaleX(.5);
}
}
@media (min--moz-device-pixel-ratio: 2.5), (-webkit-min-device-pixel-ratio: 2.5), (min-device-pixel-ratio: 2.5), (min-resolution: 240dpi), (min-resolution: 2.5dppx) {
.border::before {
width: 300%;
height: 300%;
transform: scale(.33333);
}
.border-top::before,
.border-bottom::before,
.border-topbottom::before,
.border-topbottom::after,
.border-topleft::before,
.border-rightbottom::after,
.border-topright::before,
.border-bottomleft::before {
transform: scaleY(.33333);
}
.border-right::before,
.border-left::before,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::after,
.border-rightbottom::before,
.border-topright::after,
.border-bottomleft::after {
transform: scaleX(.33333);
}
}
@charset "utf-8";html{background-color:
body,ul,ol,dl,dd,h1,h2,h3,h4,h5,h6,figure,form,fieldset,legend,input,textarea,button,p,blockquote,th,td,pre,xmp{margin:0;padding:0}
body,input,textarea,button,select,pre,xmp,tt,code,kbd,samp{line-height:1.5;font-family:tahoma,arial,"Hiragino Sans GB",simsun,sans-serif}
h1,h2,h3,h4,h5,h6,small,big,input,textarea,button,select{font-size:100%}
h1,h2,h3,h4,h5,h6{font-family:tahoma,arial,"Hiragino Sans GB","微软雅黑",simsun,sans-serif}
h1,h2,h3,h4,h5,h6,b,strong{font-weight:normal}
address,cite,dfn,em,i,optgroup,var{font-style:normal}
table{border-collapse:collapse;border-spacing:0;text-align:left}
caption,th{text-align:inherit}
ul,ol,menu{list-style:none}
fieldset,img{border:0}
img,object,input,textarea,button,select{vertical-align:middle}
article,aside,footer,header,section,nav,figure,figcaption,hgroup,details,menu{display:block}
audio,canvas,video{display:inline-block;*display:inline;*zoom:1}
blockquote:before,blockquote:after,q:before,q:after{content:"\0020"}
textarea{overflow:auto;resize:vertical}
input,textarea,button,select,a{outline:0 none;border: none;}
button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}
mark{background-color:transparent}
a,ins,s,u,del{text-decoration:none}
sup,sub{vertical-align:baseline}
html {overflow-x: hidden;height: 100%;font-size: 50px;-webkit-tap-highlight-color: transparent;}
body {font-family: Arial, "Microsoft Yahei", "Helvetica Neue", Helvetica, sans-serif;color:
hr {height: .02rem;margin: .1rem 0;border: medium none;border-top: .02rem solid
a {color: