▶【SecKill】U5 页面优化技术
▶【SecKill】U5 页面优化技术
一、页面缓存+URL缓存+对象缓存
① com.kirin.miaosha.controller / GoodsController.java:
package com.kirin.miaosha.controller;
@Controller
@RequestMapping("/goods")
public class GoodsController {
@Autowired
MiaoshaUserService userService;
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@Autowired
ThymeleafViewResolver thymeleafViewResolver;
@Autowired
ApplicationContext applicationContext;
//1.用户登录后,跳转到商品列表页
@RequestMapping(value="/to_list", produces="text/html")
@ResponseBody
public String list(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user) {
model.addAttribute("user", user);
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
if(!StringUtils.isEmpty(html)) {
return html;
}
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList", goodsList);
// return "goods_list";
SpringWebContext ctx = new SpringWebContext(request,response,
request.getServletContext(),request.getLocale(), model.asMap(), applicationContext);
html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
if(!StringUtils.isEmpty(html)) {
redisService.set(GoodsKey.getGoodsList, "", html);
}
return html;
}
//2.查看商品详情
@RequestMapping("/to_detail/{goodsId}") //根据id获取商品
public String detail(Model model,MiaoshaUser user,@PathVariable("goodsId")long goodsId) {
model.addAttribute("user", user);
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
model.addAttribute("goods", goods);
long startAt = goods.getStartDate().getTime();
long endAt = goods.getEndDate().getTime();
long now = System.currentTimeMillis();
int miaoshaStatus = 0;
int remainSeconds = 0;
if(now < startAt ) {
miaoshaStatus = 0;
remainSeconds = (int)((startAt - now )/1000);
}else if(now > endAt){
miaoshaStatus = 2;
remainSeconds = -1;
}else {
miaoshaStatus = 1;
remainSeconds = 0;
}
model.addAttribute("miaoshaStatus", miaoshaStatus);
model.addAttribute("remainSeconds", remainSeconds);
return "goods_detail";
}
}
* @RequestMapping(value = “/to_list”,produces = "text/html"):produces标注了返回值的类型,必须与@ResponseBody搭配使用
手动渲染过程中,我们要注入ThymeleafViewResolver,这个是框架给我们准备好的Bean,利用它来渲染页面,其中第二个参数,需要注入IContext。
* 在Spring5版本中,SpringWebContext已经没有了,我们需要使用WebContext来代替。它剔除了之前对ApplicationContext 过多的依赖,现在thymeleaf渲染不再过多依赖spring容器。
* 我们对Redis缓存的时间设置了60秒的限制,超过60秒过期,这个时间不宜过长,在60秒内看到的网页一直一样是可以接受的。
② 新建 com.kirin.miaosha.redis / GoodsKey.java:
package com.kirin.miaosha.redis;
public class GoodsKey extends BasePrefix{
private GoodsKey(int expireSeconds, String prefix) {
super(expireSeconds, prefix);
}
public static GoodsKey getGoodsList = new GoodsKey(60, "gl"); //有效期60s
public static GoodsKey getGoodsDetail = new GoodsKey(60, "gd");
}
① com.kirin.miaosha.controller / GoodsController.java:
package com.kirin.miaosha.controller;
@Controller
@RequestMapping("/goods")
public class GoodsController {
@Autowired
MiaoshaUserService userService;
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@Autowired
ThymeleafViewResolver thymeleafViewResolver;
@Autowired
ApplicationContext applicationContext;
//1.用户登录后,跳转到商品列表页(缓存)
@RequestMapping(value="/to_list", produces="text/html")
@ResponseBody
public String list(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user) {
model.addAttribute("user", user);
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
if(!StringUtils.isEmpty(html)) {
return html;
}
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList", goodsList);
SpringWebContext ctx = new SpringWebContext(request,response,
request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
if(!StringUtils.isEmpty(html)) {
redisService.set(GoodsKey.getGoodsList, "", html);
}
return html;
}
//2.查看商品详情(缓存)
@RequestMapping(value="/to_detail/{goodsId}",produces="text/html") //根据id获取商品
@ResponseBody
public String detail2(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user,
@PathVariable("goodsId")long goodsId) {
model.addAttribute("user", user);
String html = redisService.get(GoodsKey.getGoodsDetail, ""+goodsId, String.class);
if(!StringUtils.isEmpty(html)) {
return html;
}
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
model.addAttribute("goods", goods);
long startAt = goods.getStartDate().getTime();
long endAt = goods.getEndDate().getTime();
long now = System.currentTimeMillis();
int miaoshaStatus = 0;
int remainSeconds = 0;
if(now < startAt ) {
miaoshaStatus = 0;
remainSeconds = (int)((startAt - now )/1000);
}else if(now > endAt)
miaoshaStatus = 2;
remainSeconds = -1;
}else { //秒杀进行中
miaoshaStatus = 1;
remainSeconds = 0;
}
model.addAttribute("miaoshaStatus", miaoshaStatus);
model.addAttribute("remainSeconds", remainSeconds);
// return "goods_detail";
SpringWebContext ctx = new SpringWebContext(request,response,
request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
html = thymeleafViewResolver.getTemplateEngine().process("goods_detail", ctx);
if(!StringUtils.isEmpty(html)) {
redisService.set(GoodsKey.getGoodsDetail, ""+goodsId, html);
}
return html;
}
}
② 新建 com.kirin.miaosha.redis / GoodsKey.java:
package com.kirin.miaosha.redis;
public class GoodsKey extends BasePrefix{
private GoodsKey(int expireSeconds, String prefix) {
super(expireSeconds, prefix);
}
public static GoodsKey getGoodsList = new GoodsKey(60, "gl"); //有效期60s
public static GoodsKey getGoodsDetail = new GoodsKey(60, "gd");
}
(1)对象缓存:com.kirin.miaosha.service / MiaoshaUserService.java / getByToken()
① com.kirin.miaosha.service / MiaoshaUserService.java:
② 新建 com.kirin.miaosha.redis / MiaoshaUserKey.java:
package com.kirin.miaosha.redis;
//3.实现类
public class MiaoshaUserKey extends BasePrefix{
public static final int TOKEN_EXPIRE = 3600*24*2;
private MiaoshaUserKey(int expireSeconds, String prefix) {
super(expireSeconds, prefix);
}
public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE, "tk");
public static MiaoshaUserKey getById = new MiaoshaUserKey(0, "id");
}
(2)更新缓存:com.kirin.miaosha.service / MiaoshaUserService.java / updatePassword()
① com.kirin.miaosha.service / MiaoshaUserService.java:
package com.kirin.miaosha.service;
@Service
public class MiaoshaUserService {
public static final String COOKI_NAME_TOKEN = "token";
@Autowired
MiaoshaUserDao miaoshaUserDao;
@Autowired
RedisService redisService;
//(1.对象缓存)通过id获取对象token
public MiaoshaUser getById(long id) {
MiaoshaUser user = redisService.get(MiaoshaUserKey.getById, ""+id, MiaoshaUser.class);
if(user != null) {
return user;
}
user = miaoshaUserDao.getById(id);
if(user != null) {
redisService.set(MiaoshaUserKey.getById, ""+id, user);
}
return user;
}
//(2.缓存更新)修改密码
public boolean updatePassword(String token, long id, String formPass) {
//取user
MiaoshaUser user = getById(id);
if(user == null) {
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
MiaoshaUser toBeUpdate = new MiaoshaUser();
toBeUpdate.setId(id);
toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, user.getSalt()));
miaoshaUserDao.update(toBeUpdate);
redisService.delete(MiaoshaUserKey.getById, ""+id);
user.setPassword(toBeUpdate.getPassword());
redisService.set(MiaoshaUserKey.token, token, user);
return true;
}
public String login(HttpServletResponse response, LoginVo loginVo) {
if(loginVo == null) {
throw new GlobalException(CodeMsg.SERVER_ERROR);
}
String mobile = loginVo.getMobile();
String formPass = loginVo.getPassword();
MiaoshaUser user = getById(Long.parseLong(mobile));
if(user == null) {
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
//验证密码
String dbPass = user.getPassword();
String saltDB = user.getSalt();
String calcPass = MD5Util.formPassToDBPass(formPass, saltDB);
if(!calcPass.equals(dbPass)) {
throw new GlobalException(CodeMsg.PASSWORD_ERROR);
}
//登录成功后,生成cookie
String token = UUIDUtil.uuid();
addCookie(response, token, user);
return token;
}
private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
redisService.set(MiaoshaUserKey.token, token, user);
Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token); //name:value = COOKI_NAME_TOKEN:token
cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
cookie.setPath("/");
response.addCookie(cookie);
}
public MiaoshaUser getByToken(HttpServletResponse response, String token) {
//参数校验
if(StringUtils.isEmpty(token)) {
return null;
}
MiaoshaUser user = redisService.get(MiaoshaUserKey.token, token, MiaoshaUser.class);
//延长有效期
if(user != null) {
addCookie(response, token, user);
}
return user;
}
}
② 新建 com.kirin.miaosha.redis / RedisService.java:
添加删除方法(com.kirin.miaosha.service / MiaoshaUserService.java / updatePassword()修改密码:删除之前的redis)
package com.kirin.miaosha.redis;
@Service
public class RedisService {
@Autowired
JedisPool jedisPool;
//获取当前对象
public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
String str = jedis.get(realKey);
T t = stringToBean(str, clazz); //将获取的值,从String类型转换成bean类型
return t;
}finally {
returnToPool(jedis);
}
}
//设置对象
public <T> boolean set(KeyPrefix prefix, String key, T value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String str = beanToString(value);
if(str == null || str.length() <= 0) {
return false;
}
//生成真正的key
String realKey = prefix.getPrefix() + key;
int seconds = prefix.expireSeconds(); //过期时间
if(seconds <= 0) {
jedis.set(realKey, str);
}else {
jedis.setex(realKey, seconds, str);
}
return true;
}finally {
returnToPool(jedis);
}
}
//判断key是否存在
public <T> boolean exists(KeyPrefix prefix, String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.exists(realKey);
}finally {
returnToPool(jedis);
}
}
//增加值:+1
public <T> Long incr(KeyPrefix prefix, String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.incr(realKey);
}finally {
returnToPool(jedis);
}
}
//减少值:-1
public <T> Long decr(KeyPrefix prefix, String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
return jedis.decr(realKey);
}finally {
returnToPool(jedis);
}
}
//删除(com.kirin.miaosha.service / MiaoshaUserService.java / updatePassword()修改密码:删除之前的redis)
public boolean delete(KeyPrefix prefix, String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
long ret = jedis.del(key);
return ret > 0;
}finally {
returnToPool(jedis);
}
}
//将设置的值,从bean类型转换成String类型
private <T> String beanToString(T value) {
if(value == null) { //若空,直接返回
return null;
}
Class<?> clazz = value.getClass();
if(clazz == int.class || clazz == Integer.class) { //若是int类型,直接写入
return ""+value;
}else if(clazz == String.class) { //若是String类型,直接写入
return (String)value;
}else if(clazz == long.class || clazz == Long.class) { //若是long类型,直接写入
return ""+value;
}else {
return JSON.toJSONString(value);
}
}
//将获取的值,从String类型转换成bean类型
@SuppressWarnings("unchecked")
private <T> T stringToBean(String str, Class<T> clazz) {
if(str == null || str.length() <= 0 || clazz == null) { //若空,直接返回
return null;
}
if(clazz == int.class || clazz == Integer.class) { //若是int类型,强转为int类型
return (T)Integer.valueOf(str);
}else if(clazz == String.class) { //若是String类型,直接输出
return (T)str;
}else if(clazz == long.class || clazz == Long.class) { //若是long类型,强转为long类型
return (T)Long.valueOf(str);
}else {
return JSON.toJavaObject(JSON.parseObject(str), clazz);
}
}
private void returnToPool(Jedis jedis) {
if(jedis != null) {
jedis.close();
}
}
}
③ 新建 com.kirin.miaosha.dao / MiaoshaUserDao.java:
package com.kirin.miaosha.dao;
@Mapper
public interface MiaoshaUserDao {
@Select("select * from miaosha_user where id = #{id}")
public MiaoshaUser getById(@Param("id")long id);
//修改密码:MiaoshaUserService.java / updatePassword()
@Update("update miaosha_user set password = #{password} where id = #{id}")
public void update(MiaoshaUser toBeUpdate);
}
(1)打jar包,放进Linux服务器
(2)启动miaosha_3.jar包,将输出放进nohup中
nohup java -jar miaosha_3.jar &
(3)启动
tail -f nohup.out
(4)压测
goods_list.jmx
并发10000 = 1000个线程 * 10次循环
二、页面静态化,前后端分离
① com.kirin.miaosha.controller / GoodsController.java:
package com.kirin.miaosha.controller;
@Controller
@RequestMapping("/goods")
public class GoodsController {
@Autowired
MiaoshaUserService userService;
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@Autowired
ThymeleafViewResolver thymeleafViewResolver;
@Autowired
ApplicationContext applicationContext;
//2.3 查看商品详情(页面静态化:页面存为HTML,动态数据通过接口从服务端获取)
@RequestMapping(value="/detail/{goodsId}")
@ResponseBody
public Result<GoodsDetailVo> detail(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user,
@PathVariable("goodsId")long goodsId) {
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
long startAt = goods.getStartDate().getTime();
long endAt = goods.getEndDate().getTime();
long now = System.currentTimeMillis();
int miaoshaStatus = 0;
int remainSeconds = 0;
if(now < startAt ) {
miaoshaStatus = 0;
remainSeconds = (int)((startAt - now )/1000);
}else if(now > endAt){
miaoshaStatus = 2;
remainSeconds = -1;
}else {
miaoshaStatus = 1;
remainSeconds = 0;
}
GoodsDetailVo vo = new GoodsDetailVo();
vo.setGoods(goods);
vo.setUser(user);
vo.setRemainSeconds(remainSeconds);
vo.setMiaoshaStatus(miaoshaStatus);
return Result.success(vo);
}
}
② 新建 com.kirin.miaosha.vo / GoodsDetailVo.java:
package com.kirin.miaosha.vo;
import com.kirin.miaosha.domain.MiaoshaUser;
public class GoodsDetailVo {
private int miaoshaStatus = 0;
private int remainSeconds = 0;
private GoodsVo goods ;
private MiaoshaUser user;
public int getMiaoshaStatus() {
return miaoshaStatus;
}
public void setMiaoshaStatus(int miaoshaStatus) {
this.miaoshaStatus = miaoshaStatus;
}
public int getRemainSeconds() {
return remainSeconds;
}
public void setRemainSeconds(int remainSeconds) {
this.remainSeconds = remainSeconds;
}
public GoodsVo getGoods() {
return goods;
}
public void setGoods(GoodsVo goods) {
this.goods = goods;
}
public MiaoshaUser getUser() {
return user;
}
public void setUser(MiaoshaUser user) {
this.user = user;
}
}
③ static / goods_detail.htm:
④ goods_list.html:
<a th:href="'/goods_detail.htm?goodsId='+${goods.id}">详情</a>
① static / goods_detail.htm:
<!DOCTYPE HTML>
<html>
<head>
<title>商品详情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- 1.商品详情静态化 -->
<!-- jquery -->
<script type="text/javascript" src="/js/jquery.min.js"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" />
<script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>
<!-- jquery-validator -->
<script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script>
<script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script>
<!-- layer -->
<script type="text/javascript" src="/layer/layer.js"></script>
<!-- md5.js -->
<script type="text/javascript" src="/js/md5.min.js"></script>
<!-- common.js -->
<script type="text/javascript" src="/js/common.js"></script>
</head>
<body>
<div class="panel panel-default">
<div class="panel-heading">秒杀商品详情</div>
<div class="panel-body">
<span id="userTip"> 您还没有登录,请登陆后再操作<br/></span>
<span>没有收货地址的提示。。。</span>
</div>
<table class="table" id="goodslist">
<tr>
<td>商品名称</td>
<td colspan="3" id="goodsName"></td>
</tr>
<tr>
<td>商品图片</td>
<td colspan="3"><img id="goodsImg" width="200" height="200" /></td>
</tr>
<tr>
<td>秒杀开始时间</td>
<td id="startTime"></td>
<!-- 设置秒杀状态 -->
<td>
<input type="hidden" id="remainSeconds" />
<span id="miaoshaTip"></span>
</td>
<td>
<!--<form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">
<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button>
<input type="hidden" name="goodsId" id="goodsId" />
</form>-->
<!-- 2.秒杀静态化 -->
<button class="btn btn-primary btn-block" type="button" id="buyButton"onclick="doMiaosha()">立即秒杀</button>
<input type="hidden" name="goodsId" id="goodsId" />
</td>
</tr>
<tr>
<td>商品原价</td>
<td colspan="3" id="goodsPrice"></td>
</tr>
<tr>
<td>秒杀价</td>
<td colspan="3" id="miaoshaPrice"></td>
</tr>
<tr>
<td>库存数量</td>
<td colspan="3" id="stockCount"></td>
</tr>
</table>
</div>
</body>
<script>
//2.秒杀静态化
function doMiaosha(){
$.ajax({
url:"/miaosha/do_miaosha",
type:"POST",
data:{
goodsId:$("#goodsId").val(), //获取goodsId
},
success:function(data){
if(data.code == 0){
window.location.href="/order_detail.htm?orderId="+data.data.id;
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
//1.商品详情静态化:渲染页面
function render(detail){
var miaoshaStatus = detail.miaoshaStatus;
var remainSeconds = detail.remainSeconds;
var goods = detail.goods;
var user = detail.user;
if(user){ //若已登录,能获取到用户,就把提示信息隐藏
$("#userTip").hide();
}
$("#goodsName").text(goods.goodsName);
$("#goodsImg").attr("src", goods.goodsImg);
$("#startTime").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"));
$("#remainSeconds").val(remainSeconds);
$("#goodsId").val(goods.id);
$("#goodsPrice").text(goods.goodsPrice);
$("#miaoshaPrice").text(goods.miaoshaPrice);
$("#stockCount").text(goods.stockCount);
countDown();
}
//页面初始化
$(function(){
//countDown();
getDetail();
});
//1.商品详情静态化
function getDetail(){
var goodsId = g_getQueryString("goodsId"); //获取url参数(common.js)
$.ajax({
url:"/goods/detail/"+goodsId,
type:"GET",
success:function(data){
if(data.code == 0){ //成功
render(data.data); //根据服务端的数据,把页面渲染出了
}else{ //失败
layer.msg(data.msg); //把错误消息打印出来
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
//秒杀倒计时
function countDown(){
var remainSeconds = $("#remainSeconds").val();
var timeout;
if(remainSeconds > 0){//秒杀还没开始,倒计时
$("#buyButton").attr("disabled", true);
$("#miaoshaTip").html("秒杀倒计时:"+remainSeconds+"秒");
timeout = setTimeout(function(){
$("#countDown").text(remainSeconds - 1);
$("#remainSeconds").val(remainSeconds - 1);
countDown();
},1000);
}else if(remainSeconds == 0){//秒杀进行中
$("#buyButton").attr("disabled", false);
if(timeout){
clearTimeout(timeout);
}
$("#miaoshaTip").html("秒杀进行中");
}else{//秒杀已经结束
$("#buyButton").attr("disabled", true);
$("#miaoshaTip").html("秒杀已经结束");
}
}
</script>
</html>
② com.kirin.miaosha.controller / MiaoshaController.java:
package com.kirin.miaosha.controller;
@Controller
@RequestMapping("/miaosha")
public class MiaoshaController {
@Autowired
MiaoshaUserService userService;
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Autowired
MiaoshaService miaoshaService;
//秒杀表单提交
@RequestMapping(value="/do_miaosha", method=RequestMethod.POST)
@ResponseBody
public Result<OrderInfo> miaosha(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId) {
model.addAttribute("user", user);
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
//判断库存
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
int stock = goods.getStockCount();
if(stock <= 0) {
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判断是否已经秒杀到了
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
return Result.error(CodeMsg.REPEATE_MIAOSHA);
}
//减库存 下订单 写入秒杀订单
OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
return Result.success(orderInfo);
}
}
① static / order_detail.htm:
<!DOCTYPE HTML>
<html>
<head>
<title>订单详情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- jquery -->
<script type="text/javascript" src="/js/jquery.min.js"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" />
<script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>
<!-- jquery-validator -->
<script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script>
<script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script>
<!-- layer -->
<script type="text/javascript" src="/layer/layer.js"></script>
<!-- md5.js -->
<script type="text/javascript" src="/js/md5.min.js"></script>
<!-- common.js -->
<script type="text/javascript" src="/js/common.js"></script>
</head>
<body>
<div class="panel panel-default">
<div class="panel-heading">秒杀订单详情</div>
<table class="table" id="goodslist">
<tr>
<td>商品名称</td>
<td colspan="3" id="goodsName"></td>
</tr>
<tr>
<td>商品图片</td>
<td colspan="2"><img id="goodsImg" width="200" height="200" /></td>
</tr>
<tr>
<td>订单价格</td>
<td colspan="2" id="orderPrice"></td>
</tr>
<tr>
<td>下单时间</td>
<td id="createDate" colspan="2"></td>
</tr>
<tr>
<td>订单状态</td>
<td id="orderStatus"></td>
<td>
<button class="btn btn-primary btn-block" type="submit" id="payButton">立即支付</button>
</td>
</tr>
<tr>
<td>收货人</td>
<td colspan="2">XXX 18812341234</td>
</tr>
<tr>
<td>收货地址</td>
<td colspan="2">北京市昌平区回龙观龙博一区</td>
</tr>
</table>
</div>
</body>
<script>
//渲染页面
function render(detail){
var goods = detail.goods;
var order = detail.order;
$("#goodsName").text(goods.goodsName);
$("#goodsImg").attr("src", goods.goodsImg);
$("#orderPrice").text(order.goodsPrice);
$("#createDate").text(new Date(order.createDate).format("yyyy-MM-dd hh:mm:ss"));
//订单状态
var status = "";
if(order.status == 0){
status = "未支付"
}else if(order.status == 1){
status = "待发货";
}
$("#orderStatus").text(status);
}
$(function(){
getOrderDetail();
})
//获取订单详情
function getOrderDetail(){
var orderId = g_getQueryString("orderId");
$.ajax({
url:"/order/detail",
type:"GET",
data:{
orderId:orderId
},
success:function(data){
if(data.code == 0){
render(data.data);
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
</script>
</html>
② 新建 com.kirin.miaosha.controller / OrderController.java:
package com.kirin.miaosha.controller;
@Controller
@RequestMapping("/order")
public class OrderController {
@Autowired
MiaoshaUserService userService;
@Autowired
RedisService redisService;
@Autowired
OrderService orderService;
@Autowired
GoodsService goodsService;
@RequestMapping("/detail")
@ResponseBody
public Result<OrderDetailVo> info(Model model,MiaoshaUser user,
@RequestParam("orderId") long orderId) {
if(user == null) { //用户为空,提示重新登录
return Result.error(CodeMsg.SESSION_ERROR);
}
OrderInfo order = orderService.getOrderById(orderId);
if(order == null) { //订单为空,提示错误
return Result.error(CodeMsg.ORDER_NOT_EXIST);
}
long goodsId = order.getGoodsId(); //获取订单中的商品id
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId); //通过商品id获取商品
OrderDetailVo vo = new OrderDetailVo();
vo.setOrder(order);
vo.setGoods(goods);
return Result.success(vo);
}
}
③ 新建 com.kirin.miaosha.vo / OrderDetailVo.java:
package com.kirin.miaosha.vo;
import com.kirin.miaosha.domain.OrderInfo;
public class OrderDetailVo {
private GoodsVo goods;
private OrderInfo order;
public GoodsVo getGoods() {
return goods;
}
public void setGoods(GoodsVo goods) {
this.goods = goods;
}
public OrderInfo getOrder() {
return order;
}
public void setOrder(OrderInfo order) {
this.order = order;
}
}
④ com.kirin.miaosha.service / OrderService.java:
package com.kirin.miaosha.service;
@Service
public class OrderService {
@Autowired
OrderDao orderDao;
@Autowired
RedisService redisService;
public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) {
return orderDao.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);
}
public OrderInfo getOrderById(long orderId) {
return orderDao.getOrderById(orderId);
}
//生成订单
@Transactional
public OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCreateDate(new Date());
orderInfo.setDeliveryAddrId(0L);
orderInfo.setGoodsCount(1);
orderInfo.setGoodsId(goods.getId());
orderInfo.setGoodsName(goods.getGoodsName());
orderInfo.setGoodsPrice(goods.getMiaoshaPrice());
orderInfo.setOrderChannel(1);
orderInfo.setStatus(0);
orderInfo.setUserId(user.getId());
long orderId = orderDao.insert(orderInfo);
MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
miaoshaOrder.setGoodsId(goods.getId());
miaoshaOrder.setOrderId(orderId);
miaoshaOrder.setUserId(user.getId());
orderDao.insertMiaoshaOrder(miaoshaOrder);
return orderInfo;
}
}
⑤ com.kirin.miaosha.dao / OrderDao.java:
package com.kirin.miaosha.dao;
@Mapper
public interface OrderDao {
@Select("select * from miaosha_order where user_id=#{userId} and goods_id=#{goodsId}")
public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(@Param("userId")long userId, @Param("goodsId")long goodsId);
@Insert("insert into order_info(user_id, goods_id, goods_name, goods_count, goods_price, order_channel, status, create_date)values("
+ "#{userId}, #{goodsId}, #{goodsName}, #{goodsCount}, #{goodsPrice}, #{orderChannel},#{status},#{createDate} )")
@SelectKey(keyColumn="id", keyProperty="id", resultType=long.class, before=false, statement="select last_insert_id()")
public long insert(OrderInfo orderInfo);
@Insert("insert into miaosha_order (user_id, goods_id, order_id)values(#{userId}, #{goodsId}, #{orderId})")
public int insertMiaoshaOrder(MiaoshaOrder miaoshaOrder);
@Select("select * from order_info where id = #{orderId}")
public OrderInfo getOrderById(@Param("orderId")long orderId);
}
⑥ com.kirin.miaosha.result / CodeMsg.java:
//订单模块5004XX public static CodeMsg ORDER_NOT_EXIST = new CodeMsg(500400, "订单不存在");
4、★解决超卖问题
超卖问题:压测时,出现秒杀商品数量为 - 的情况
(1)修改SQL语句
com.kirin.miaosha.dao / GoodsDao.java:
(2)添加唯一索引
(3)压测,查看是否解决超卖问题
① 压测前,小优化:查缓存
com.kirin.miaosha.service / OrderService.java:
com.kirin.miaosha.redis / OrderKey.java:
package com.kirin.miaosha.redis;
public class OrderKey extends BasePrefix {
public OrderKey(String prefix) {
super(prefix);
}
public static OrderKey getMiaoshaOrderByUidGid = new OrderKey("moug");
}
② 压测