缓存
页面缓存+URL缓存+对象缓存,页面缓存适用于变换不大的,实际中分页是值缓存前几页,不会全都缓存。
页面缓存
主要步骤:
第一步:取缓存
第二步:手动渲染模板
第三步:结果输出
具体实现:
- 新建GoodKey用于redis中货物模块key的前缀。
public class GoodsKey extends BasePrefix {
public GoodsKey(int expireSeconds, String prefix) {
super(prefix);
}
public static GoodsKey getGoodsList = new GoodsKey("gl");
}
- 将GoodController类中的用于返回商品列表的方法进行修改。为方法添加@RsponseBody注解,和为注解@RequestMapping添加属性produces = “text/html”。进入list方法先使用redisService的get方法从缓存中进行取值。若得到的值不为空,将得到的值直接进行返回。若为空,使用thymeleafViewResolver进行手动渲染。使用thymeleafViewResolver的getTrmplateEngine().process(模版名称,context)方法,其中的模版名称就是这个类返回的html页面的名称。context包含业务数据。这里的context是IContext,IContext与springboot结合的对象为SpringWebContext。若是模版不为空,就将其保存到缓存中。最终返回html页面对象。
@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); //放到Model中,供前端展示使用。
// 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;
}
- 因为页面缓存的有效期较短,所以在GoodKey中设置有效期,设为60s。
public GoodsKey(int expireSeconds, String prefix) {
super(expireSeconds, prefix);
}
public static GoodsKey getGoodsList = new GoodsKey(60, "gl");
- 运行测试,进入redis根据key值进行查找,看页面是否正确的缓存到的redis中。
URL缓存
- 进入代码详情页的方法,依旧是修改和添加注解。进入方法后先取缓存。若不为空,就继续进行手动渲染,渲染后将结果进行输出。因为 redisService.set(GoodsKey.getGoodsDetail, “”+goodsId, html),使不同的商品有不同的详情页,这个被称为url的缓存。
@RequestMapping(value = "/to_detail/{goodsId}", produces = "text/html")
@ResponseBody
public String detail(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;
}
- 为GoodKey中添加getGoodsDetail
public static GoodsKey getGoodsDetail = new GoodsKey(60, "gd");
遇到的问题
- 没有SpringWebContext对象
错误原因:
springboot的版本太高,在这个版本中是thymeleaf.spring5。没有这个对象spring4中有这个对象。
解决方案:
修改springboot的版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
- Maven中org.apache.commons.commons-lang3显示unknown
错误原因:
导入了多个不同版本的相同jar包
解决方案:
删除多余的jar包,只保留一个导入信息 - idea控制台输出的中文不能识别
解决方案:
进入settings->editor->file encodings,将其中的GlobalEncoding、ProjectEcoding和Properties Files中的Default encoding for properties files都选为utf-8,点击apply。重启运行,控制台的中文可以正常阅读。
- 页面th参下有红色波浪线
解决方案:在下添加<!--suppress All-->
或进入Editor > Inspections >Expression variables validation的对勾取消。
- 从登录页面进入商品列表页面出500错误,并返回了商品列表的html代码和template might not exist or might not be accessible by any of the configured Template Resolvers(模板可能不存在,或者任何已配置的模板解析器都无法访问)
解决方案:
为list方法添加@RsponseBody注解,和为注解@RequestMapping添加属性produces = “text/html”。
对象缓存
更细的粒度缓存
- 进入MiaoshaUserService,找到getById方法,先从redis中取缓存的用户信息,若是没有从缓存中取到数据,就向数据库中进行取值。
public MiaoshaUser getById(long id){
//取缓存
MiaoshaUser miaoshaUser =redisService.get(MiaoshaUserKey.getById, ""+id, MiaoshaUser.class);
if (miaoshaUser != null){
return miaoshaUser;
}
//取数据库
miaoshaUser = miaoshaUserMapper.getById(id);
if (miaoshaUser != null){
redisService.set(MiaoshaUserKey.getById, ""+id, miaoshaUser);
}
return miaoshaUser;
}
- 在MiaoshaUserKey中添加getById,希望对象缓存是永久有效的,所以不舍有效期。
public static MiaoshaUserKey getById = new MiaoshaUserKey(0, "id");
- 创建一个用于更新密码的方法updatePassword,先判断用户是否存在,若是存在就进行对数据库中数据的更新,在将数据库中的数据更新后,再对缓存中的用户数据进行更新。更新的缓存token和getByI的两个,通过对相应数据的删除,再添加实现数据的更新。为了求效率修改什么字段就更新什么字段。
public boolean updatePassword(String token, long id, String formPass){
//取user
MiaoshaUser miaoshaUser = getById(id);
if (miaoshaUser == null){
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
//更新数据库
MiaoshaUser toBeUpdate = new MiaoshaUser();
toBeUpdate.setId(id);
toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, miaoshaUser.getSalt()));
miaoshaUserMapper.update(toBeUpdate);
//处理缓存
redisService.delete(MiaoshaUserKey.getById, ""+id);
miaoshaUser.setPassword(toBeUpdate.getPassword());
redisService.set(MiaoshaUserKey.token, token, miaoshaUser);
return true;
}
- 在RedisService中新建delete方法,用于删除数据。
public Boolean delete(KeyPrefix prefix, String key){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String realPrefix = prefix.getPrefix() + key;
long ret = jedis.del(realPrefix);
return ret > 0;
}finally {
returnToPool(jedis);
}
}
注:在service中使用其他的Mapper对象,只能是通过其service进行使用。因为如果在service中有缓存,直接调用mapper会将缓存略过。
- 在MiaoshaUserMapper中添加update方法。
@Update("update miaosha_user set password = #{password} where id = #{id}")
public void update(MiaoshaUser toBeUpdate);
压测
将代码达成jar包,传到linux系统中,输入命令进行对代码的压测。详细步骤见上一次笔记。通过将结果下载载入到JMeter中可以看到,在使用了缓存后,负载下降了。
静态化
实现了前后端的分离,将页面缓存到了浏览器中。在用户访问页面时,不需要与服务端进行交互,极大的节省了网络的流量。
常用的技术AngularJS,Vue.js。
前后端分离的实质是htm+ajax。
商品详情静态化
- 进入GoodsController类,复制一份detail方法,将其中一份更名为detail2,路径中的detail中改为detail2.将复制出的detail,将取缓存的部分删除和手动渲染的部分删除。将方法的返回对象改为Result,将返回给前台的信息存入Result的data属性中。
@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 goodsDetailVo = new GoodsDetailVo();
goodsDetailVo.setGoodsVo(goods);
goodsDetailVo.setMiaoshaUser(user);
goodsDetailVo.setMiaoshaStatus(miaoshaStatus);
goodsDetailVo.setRemainSeconds(remainSeconds);
return Result.success(goodsDetailVo);
}
- 新建GoodDetailVo类用于封装返回的信息,其中有秒杀的状态,秒杀剩余的时间,商品的信息和秒杀对象的信息。
public class GoodsDetailVo {
private int miaoshaStatus = 0;
private int remainSeconds = 0;
private GoodsVo goodsVo;
private MiaoshaUser miaoshaUser;
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 getGoodsVo() {
return goodsVo;
}
public void setGoodsVo(GoodsVo goodsVo) {
this.goodsVo = goodsVo;
}
public MiaoshaUser getMiaoshaUser() {
return miaoshaUser;
}
public void setMiaoshaUser(MiaoshaUser miaoshaUser) {
this.miaoshaUser = miaoshaUser;
}
}
- 进入goods_list.html页面,见其中通过服务端跳转到detail页面的部分进行修改。因为静态页面一般应该在static下,所以将要进行静态化的页面goods_detail.html复制到static下,又因为在application中配置了.html后缀的页面在templates下进行查找,所以将移到static下的detail页面的后缀修改为.htm。
<td>
<a th:href="'/goods_detail.htm?goodsId=' + ${goods.id}">详情</a>
</td>
- 进入goods_detail.htm中进行修改,删除关于thymeleaf的内容。将页面获取后端传输过来的数据${数据名称}删除,为标签添加id,id的值为所需数据的名称。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>商品详情</title>
<!-- 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" value="${goods.id}"/>
</form>
</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> //页面初始化就执行```
- function中第一步为去服务端取出所需数据。在getDetail方法中通过使用g_getQueryString函数获取从url中传入的goodsId。
$(function () {
// countDown();
getDetail();
});
function getDetail() {
var goodsId = g_getQueryString("goodsId");
$.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 render(detail){
var miaoshaStatus = detail.miaoshaStatus;
var remainSeconds = detail.remainSeconds;
var goods = detail.goodsVo;
var user = detail.miaoshaUser;
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();
}
</script>
</html>
- 修改实现页面中秒杀功能的方法。
function countDown() {
// var remainSeconds = $("#countDown").text(); <!-- 这样写的话,在秒杀进行中和秒杀已结束时就没有值 -->
var remainSeconds = $("#remainSeconds").val(); <!-- 在隐藏域中取 -->
var timeout;
if (remainSeconds > 0){ //秒杀还没开始,倒计时
$("#buyButton").attr("disabled", true); //按钮不能点
$("#miaoshaTip").html("秒杀倒计时:" + remainSeconds + "秒");
timeout = setTimeout(function () {
$("#countDown").text(remainSeconds - 1); //input标签用的是value 文案随着改
$("#remainSeconds").val(remainSeconds - 1); //span标签用的是text
countDown(); //不断回调countDown方法
}, 1000); //过1秒之后,setTimeout就会执行
}else if(remainSeconds == 0){ //秒杀进行中
$("#buyButton").attr("disabled", false);
if (timeout){
clearTimeout(timeout); //自带的清除函数吧
}
$("#miaoshaTip").html("秒杀进行中"); //等到remainSeconds减到0时,改文案
}else { //秒杀已经结束
$("#buyButton").attr("disabled", true);
$("#miaoshaTip").html("秒杀已结束");
}
}
- 在static中的common.js页面中添加获取url中的参数的函数和设定时间格式化的函数。
// 获取url参数
function g_getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if(r != null) return unescape(r[2]);
return null;
};
//设定时间格式化函数,使用new Date().format("yyyyMMddhhmmss");
Date.prototype.format = function (format) {
var args = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
};
if (/(y+)/.test(format))
format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var i in args) {
var n = args[i];
if (new RegExp("(" + i + ")").test(format))
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? n : ("00" + n).substr(("" + n).length));
}
return format;
};
秒杀静态化
- 修改goods_detail中的秒杀功能的逻辑实现部分不需要那么麻烦,只需要button就可以。将form的部分忽略掉。
<button class="btn btn-primary btn-block" type="button" id="buyButton" οnclick="doMiaosha()">立即秒杀</button>
<input type="hidden" name="goodsId" id="goodsId"/>
- 新建秒杀方法,先用ajax调用数据。
function doMiaosha() {
$.ajax({
url:"miaosha/do_miaosha",
type:"POST",
data:{
goodsId:$("#goodsId").val(),
},
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("客户端请求有误");
}
});
}
- 将MiaoshaController类中的list方法,将返回值类型换为Result类型,将OrderInfo数据存入data传送到前端页面。限制这个方法只能通过POST方法提交。判断用户是否处于登录状态和库存,若是用户信息和库存为空,返回error。判断是否秒杀到,若是秒杀到就将订单信息进行返回。
@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 goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
int stock = goodsVo.getStockCount();
if (stock <= 0){
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判断是否已经秒杀到
MiaoshaOrder miaoshaOrder = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if (miaoshaOrder != null){
return Result.error(CodeMsg.REPEATE_MIAOSHA);
}
//进行秒杀步骤:减库存 创建普通订单 创建秒杀订单 注意这是个事务操作
OrderInfo orderInfo = miaoshaService.miaosha(user, goodsVo);
return Result.success(orderInfo);
}
补充:
GET,POST有什么区别?
GET幂等的代表从服务端获取数据,无论调用多少次,最终产生的结果都是一样的,不会对服务端数据产生影响。
POST不是幂等的, 向服务端提交数据,会对服务端的数据发生变化。
- 进入CodeMsg添加session不存在的错误信息。
public static final CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已经失效");
- 将order_detail.html页面复制到static下,并将后缀改为.htm。同样删除关于thymeleaf的内容。将页面获取后端传输过来的数据${数据名称}删除。为标签添加id,id的值为所需数据的名称。将其中订单状态的td标签注释。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>订单详情</title>
<!-- 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 id="goodsName" colspan="3"></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>-->
<!--<span th:if="${orderInfo.status eq 0}">未支付</span>-->
<!--<span th:if="${orderInfo.status eq 1}">待发货</span>-->
<!--<span th:if="${orderInfo.status eq 2}">已发货</span>-->
<!--<span th:if="${orderInfo.status eq 3}">已收货</span>-->
<!--<span th:if="${orderInfo.status eq 4}">已退款</span>-->
<!--<span th:if="${orderInfo.status eq 5}">已完成</span>-->
<!--</td>-->
<td>
<button class="btn btn-primary btn-block" type="submit" id="payButton">立即支付</button>
</td>
</tr>
<tr>
<td>收货人</td>
<td colspan="2">yanguobin 15842674359</td>
</tr>
<tr>
<td>收获地址</td>
<td colspan="2">中国 北京</td>
</tr>
</table>
</div>
</body>
</html>
<script>
function render(detail){
var goods = detail.goodsVo;
var order = detail.orderInfo;
$("#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>
- 如何实现客户端直接从浏览器取数据,不需要询问服务端。进入到application.properties,写入静态配置。
spring.resources. | 意思 |
---|---|
add-mappings | 启用默认的资源处理 |
cache-period | 资源处理程序提供的资源的缓存周期,单位为秒 |
chain.cache | 在资源链中启用缓存 |
chain.enabled | 启用Spring资源处理链。默认情况下禁用,除非至少启用了一个策略 |
chain.gzipped | 启用解析已经经过gzip压缩的资源 |
html-application-cache | 启用HTMLS应用程序缓存清单重写 |
chain.strategy.content.enabled | 启用内容版本策略 |
chain.strategy.content.paths | 逗号分隔的用于版本策略的模式列表 |
chain.strategy.fixed.enabled | 启用固定版本策略 |
chain.strategy.fixed.paths | 逗号分隔的用于版本策略的模式列表 |
chain.strategy.fixed.version | 用于版本策略的版本字符串 |
static-locations | static的路径 |
订单静态化
- 与商品详情页的静态化步骤相似,根据参数获取服务端数据,将页面渲染出来。
<script>
function render(detail){
var goods = detail.goodsVo;
var order = detail.orderInfo;
$("#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 = "待发货";
} else if(order.status = 2){
status = "已发货";
} else if(order.status = 3){
status = "已收货";
} else if(order.status = 4){
status = "已退款";
} else if(order.status = 5){
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>
- 新建OrderController类。创建detail方法,判断用户是否为空,获取oder订单信息,将商品的信息通getById方法获取出来。将获取的的值存入OrderDetailVo中。
@Controller
@RequestMapping("/order")
public class OrderController {
@Autowired
OrderService orderService;
@Autowired
GoodsService goodsService;
@RequestMapping("/detail")
@ResponseBody
public Result<OrderDetailVo> detail(MiaoshaUser miaoshaUser, @RequestParam("orderId") long orderId){
if (miaoshaUser == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
OrderInfo orderInfo = orderService.getOrderById(orderId);
if (orderInfo == null) {
return Result.error(CodeMsg.ORDER_NOT_EXIST);
}
long goodsId = orderInfo.getGoodsId();
GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
OrderDetailVo orderDetailVo = new OrderDetailVo();
orderDetailVo.setOrderInfo(orderInfo);
orderDetailVo.setGoodsVo(goodsVo);
return Result.success(orderDetailVo);
}
}
- 新建OrderDetailVo类
public class OrderDetailVo {
private GoodsVo goodsVo;
private OrderInfo orderInfo;
public GoodsVo getGoodsVo() {
return goodsVo;
}
public void setGoodsVo(GoodsVo goodsVo) {
this.goodsVo = goodsVo;
}
public OrderInfo getOrderInfo() {
return orderInfo;
}
public void setOrderInfo(OrderInfo orderInfo) {
this.orderInfo = orderInfo;
}
}
- 向 OrderService中添加getOrderById方法。
public OrderInfo getOrderById(long orderId) {
return orderInfoMapper.getOrderById(orderId);
}
- 向OrderInfoMapper添加getOrderById方法。
@Select("select * from oder_info where id = #{orderId}")
public OrderInfo getOrderById(@Param("orderId") long orderId);
- 在 CodeMsg中添加ORDER_NOT_EXIST表示订单不存在。
//订单模块 5004xx
public static final CodeMsg ORDER_NOT_EXIST = new CodeMsg(500400, "订单不存在");
解决卖超
- 数据库加唯一索引:防止用户重复购买。
- SQL加库存数量判断:防止库存变成负数。
将GoodsMapper中的reduceStock方法的update注解中的sql语句添加一个对库存的判断。
@Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count>0")
在用户发送了两个请求时,有可能会将同一件商品秒杀两次。向表中创建唯一的索引。
优化:
在判断是否已经秒杀到时,不查数据库,查缓存。进入OrderService,将 getMiaoshaOrderByUserIdGoodsId的返回值进行替换。为使用redis,添加可自动注入的对象RedisService。
return redisService.get(OrderKey.getMiaoshaOrderByUidGid, ""+userId+"_"+goodsId, MiaoshaOrder.class);
当订单生成后将其写入redis缓存中。在createOrder中添加redisService.set()。
myRedisUtil.set(OrderKey.getMiaoshaOrderByUidGid, ""+user.getId()+"_"+goodsVo.getId(), miaoshaOrder);
在OderKey中添加getMiaoshaOrderByUidGid。
public static OrderKey getMiaoshaOrderByUidGid = new OrderKey("moug");
静态资源优化
主要包括:
- JS/CSS压缩,减少流量
- 多个JS/CSS组合,减少 连接数
- CDN就近访问
并发大的问题的瓶颈在数据库,最有效的解决方法是添加缓存。用户的角度来说,从用户发起请求的时候,从浏览器开始,通过做页面的静态化,直接将页面缓存到用户的浏览器端。请求在到达网站之前,可以部署一些CDN节点,使请求首先访问CDN。