5 b0 h. I$ p6 ~" |, |0 q3 \7 b Q@RequestMapping(value="/to_list") 3 M/ e- _8 z+ J+ ?: g public String list(Model model,MiaoshaUser user) { $ w& H% A$ f l. Q& Y model.addAttribute("user", user);7 @- I) L5 v1 n
List<GoodsVo> goodsList = goodsService.listGoodsVo(); ! T" v- u) C1 e model.addAttribute("goodsList", goodsList);# |7 B4 Y( R4 y5 z3 s& t [/ A
return "goods_list"; / o) S4 {5 z" s } . j1 l9 q1 J0 v! x1/ o% Y3 ^0 ?* c
2 # w8 M& `+ @$ R8 w4 \3 . q. Q$ g! a- D+ Y) x, z/ _40 h1 p! }6 H o* m8 x! S
5% l7 O, W- Y( Z' H
6 $ S' U& i) j3 R& M X. o) V77 h ~! e# P7 {
/** 3 v" [8 q( p* A/ L$ m: A. u2 r * 页面缓存:取缓存、手动渲染模板和结果输出8 p8 |6 V$ ^) I' K8 U
* @param model9 h' C/ D) H% a# h; D7 ]
* @param user% i6 o) [# e$ Z/ e% u
* @return; Q$ b. _5 M& [( [) M( F
*/ H+ a' T/ N) w" _ @RequestMapping(value = "/to_list",produces = "text/html") , e2 V5 i% T. o @ResponseBody* U9 J+ w$ x9 t+ a2 G+ w
public String toList(HttpServletRequest request, HttpServletResponse response,9 I: w5 \) X! G. j7 v' n
Model model, KillsUser user){ ) b2 I/ _, m6 g2 T% i. Z //无cookie返回登录界面. \" b! o, n1 R6 d5 h( e. s$ D
model.addAttribute("user",user);% g# K0 L. ^. V) Q6 @
//取缓存,如果redis中有相应的缓存,直接取出使用 & O5 e/ K3 r) g String html = redisService.get(GoodsKey.getGoodsList, "", String.class); ( D% Y) Y5 I" M3 y# f1 E if(!StringUtils.isEmpty(html)){! ]8 ?. L; K* w& _ e
return html; 1 k: G3 m; \' ?5 b } 2 H( Y- Z8 j) D; N% U$ N; u5 j //查询商品列表 9 O# y3 r: \- i5 n4 `& B2 a* c5 v List<GoodsVo> goodsList = goodsService.listGoodsVo(); / P8 C8 t$ `, |& [, `! L6 U model.addAttribute("goodsList",goodsList); / N3 }4 P4 x- w3 ~ //return "goods_list"; ! L J# e1 X7 t3 ^' s) y. y& t( y) g7 {2 ~* D( I1 G% f
SpringWebContext ctx=new SpringWebContext(request,response,request.getServletContext(), h- Z7 [9 t4 J& A
request.getLocale(),model.asMap(),applicationContext);" g; W% P/ I+ X/ M2 a# {- u
//手动渲染模板并且存入redis,框架渲染的具体方式也是如此* H( F4 W5 Y- O
html=thymeleafViewResolver.getTemplateEngine().process("goods_list",ctx); / Y! ~; A9 X z2 C3 s- l2 P. ~ if(!StringUtils.isEmpty(html)){0 c8 r: F! _& }; T( H+ ]9 p* R
redisService.set(GoodsKey.getGoodsList,"",html); * L, K) X( n9 F( J- e5 A8 L- R }4 \ v: _, R9 d
//结果返回 % I8 b8 @7 H4 @) U2 o2 c7 \ return html;4 u8 p, J' n$ f
}: }' h3 ^8 A% O5 c7 v6 g) N# J
1+ y0 R7 @$ e. h Z3 v! t3 U) n
2 7 G# l+ r5 l8 q+ }* U3: b3 @( i- f7 c% Y
4+ ~8 e6 W3 ~, W0 C& P6 j
5; J7 Z6 E7 \5 S% G' ?
6+ U2 G. P2 y/ H2 b2 H8 Z; {
75 A3 h3 {2 J/ f+ X" F/ M
8 - M* F) i- {2 U6 W" p q93 N8 [+ r& c% p1 V* ~
10 $ U7 a7 u; |& `; f9 n9 k11 ! r! U; G9 J# v6 G12 , M' L! O3 m7 f! T13 % m8 D: @; D# r$ D9 V14 - U% V+ ]% s& t15 9 A- B+ P: o a' d1 p161 }1 B8 `7 x1 t- Q S
17- [. H$ S+ e# u/ ]. d0 p4 K7 U
18 & t" Y, r' B' A' }) t, t4 \19 6 m/ x, D7 N0 k$ V20* b0 e( j$ `6 a+ {
21) {3 l! T- y; ~! c: ?
223 v! x1 i# O% _3 H# \* O8 i
23 + b3 A" F1 t( c1 E4 _, m243 ^7 B1 C% B. V
25 3 A# w' y+ ]* V$ p26 $ r1 f7 O0 f9 N5 m1 T4 W27$ o i* V5 f* }4 L' h& H7 u
28+ D. i. ^$ j& R/ Z2 a ?
29 $ m) T- i* o7 i* m30# e6 r9 y4 [; A4 i
31 4 N9 h' W: j0 w32, ]+ o$ w+ K" e8 ]0 u
8 K" _* _. Z( H9 k. s
0 t; I }2 \: ]9 O/ I0 s, L
2、对象缓存 ~# E) H7 d' O# E , s( Q" P% v& L$ X0 y根据token对秒杀对象进行缓存,如果缓存中还有秒杀对象的信息,则不去数据库进行取出。 + D" d% v# b4 g3 Y / Q; g* B b w) r# g & k4 J5 g& H6 C! X5 M3、页面静态化 8 o* I; H( z: m! W7 X7 N# g- @) ` $ |7 N* ]' V* j# W3 m7 Z页面静态化常用的技术为AngularJS、Vue.js等等,此处使用js简单模拟该过程,实现前后端分离。页面静态化,其实就是把用户经常访问的页面做成一个静态的页面,避免每次都要从数据库取出数据并且渲染到对应的模板文件中,从而减少服务器的压力。 9 t0 L8 H, ~2 A. |' L页面静态化是将动态渲染生成的画面保存为html文件,放到静态文件服务器中。用户访问的是处理好的静态文件,而需要展示不同数据内容时,则可以在请求完静态化的页面后,在页面中向后端发送请求,获取属于用户的特殊数据。 - u2 ]/ x) p, ^, [页面静态化是一个以空间换时间的操作,虽然页面增多时会占用更多的空间,但提高了网站的访问速度,提升了用户的体验,还是非常值得的。 ) _4 I9 l) P* D$ d* j2 R关于原来的页面,正常的前端访问后端接口,获取html页面,展示,而页面静态化,是利用ajax异步传递数据,在前台进行数据的填充,实现了前后端分离。 3 o$ r0 n4 z' n6 O" _9 t0 r. G6 Q% L9 }) c$ Q
@RequestMapping(value = "/to_detail/{goodsId}",produces = "text/html") 1 n, G+ T0 k! T5 ^6 F/ [* I* D @ResponseBody/ \4 e/ @) B0 `8 C( @
public String detail(HttpServletRequest request, HttpServletResponse response, % J0 b* H0 n: w Model model,KillsUser user, 6 {1 {' O" D7 i: d! h8 b @PathVariable("goodsId")long goodsId){ . R& S; N# ]1 o6 J model.addAttribute("user",user); |) v5 C) y' o' P8 Z! X& {- L/ H9 o) h/ a& t% S% }4 j
//取缓存,如果redis中有相应的缓存,直接取出使用 # E" C' ]6 U: h8 Q) Z( ^ String html = redisService.get(GoodsKey.getGoodsDetail, ""+goodsId, String.class);6 @- z+ a, j4 r
if(!StringUtils.isEmpty(html)){+ u7 H" ~. B" W5 b' p& }% `
return html;6 j2 O" l1 F0 Q1 a0 ?/ j
} 7 X L H$ T$ I' ~1 Z8 V" t+ w8 P- g8 ^$ l, c
GoodsVo goods=goodsService.getGoodsVoByGoodsId(goodsId);/ I$ s3 ~: Y) }$ \$ N7 j- s
model.addAttribute("goods",goods);# J7 H& K6 V2 f2 ?1 p* E/ Q
% L R3 K# f- E# c! E0 z
long startAt=goods.getStartDate().getTime(); " g6 V, y& z" _0 a% ]" y9 |" R long endAt=goods.getEndDate().getTime();! A% D! s4 b2 @6 D0 i
long now=System.currentTimeMillis();" S4 O4 _8 s. f) N' I }
$ p9 j) N# r" w5 C1 A. }2 C
int KillStatus=0;//表示还未秒杀 ! D# a/ j) g8 d/ i2 l" @6 ^ int remainSeconds=0;7 |( ]) ~) B- K0 `9 m
0 q/ M9 K# Q+ U
if(now < startAt){//秒杀未开始 / `) C" {) E' j; I( e KillStatus=0; b4 @" _3 a' x0 j% o9 a
remainSeconds=(int)((startAt-now)/1000); 3 Z# M* }# }! W$ z }else if(now > endAt){//秒杀已结束, Q, l$ M' z" P/ p2 J9 ~7 A% m' ~
KillStatus=2;. ^! r5 a( S" z" x# a
remainSeconds=-1; + J# i$ l+ A' u4 M' y5 n }else{//秒杀进行中 3 S5 D5 J& A( Z9 \" A KillStatus=1;" j4 x/ [& N/ n9 _( D
remainSeconds=0;0 A" H4 B2 Z# q. O4 _ O) l2 S
}3 C" [: F6 L: ~, z2 l
model.addAttribute("KillStatus",KillStatus); 4 N4 P8 n2 J* f8 A+ g% ]! C model.addAttribute("remainSeconds",remainSeconds);( ]* W6 b+ }1 U5 x
9 @) c! ?0 k: `( z9 b" }: @
1 `) h7 { z. C* {6 `* J. c
SpringWebContext ctx=new SpringWebContext(request,response,request.getServletContext(), 1 z/ g- d2 i( _' O request.getLocale(),model.asMap(),applicationContext); ) @# i5 g' W. ], z$ @: I //手动渲染模板并且存入redis,框架渲染的具体方式也是如此% u- ~' |6 F/ |
html=thymeleafViewResolver.getTemplateEngine().process("goods_detail",ctx); ( Q2 K2 I8 {; J9 c1 N" w2 X$ a/ ~ if(!StringUtils.isEmpty(html)){ ( H1 Y; L& F1 r+ A8 T; X redisService.set(GoodsKey.getGoodsDetail,""+goodsId,html); 8 \1 N' }, A2 l$ U }4 t7 t5 w2 _8 @" l. _
//结果返回' x, K" ]8 U O; S, ?
return html;/ @" O) n# v# u5 H
//return "goods_detail"; ! V/ c! `9 M( {* G } ) i5 e+ @+ X. {/ i# M1" k: m- i& g+ x# E( n3 d
2- k9 `' h* d5 V+ v( O* R
3 ; N4 {9 t. g8 }9 D+ l5 z4 H4 9 K; a8 A' U! }" b5; e( C0 r3 W6 M* y) n" L
63 I. e, t0 |* ~4 {
7 - d/ C( g. M# g" a: r2 V d ]8 & J5 e: m9 r+ Q# }, e9( A# ?5 Y3 Z; R* |* f
106 T# J8 Q9 W7 Z2 |: o- `
115 U8 i$ p4 ], u1 i: }5 L& y
12- w, p9 p( m! y/ y( N
13 3 W4 e7 `* W# k L0 K. R- l14 7 f l5 K( p2 z6 L. T, `15 7 M* J6 y! |. G4 e+ {! p' B16 p! D. {) g+ }+ C. j17 # x7 Y) H$ O" s( W: C18 5 P3 O1 m! Q- P+ g4 f0 ^19 , K- A" e) V7 o$ b203 c* F' o: o2 A v+ {. b6 E# z; @
21' n' {2 x: E, @ z8 z
223 q- l; `* X U* [* _ z( Z u% F
23 * E* J+ b0 p) V# _' ^1 u2 W0 q24 ; T. K, W; E$ N25& l: M& J, Z: R! }* @
26 ) M, { k' B( {5 e27 - N7 m4 P& `0 t7 |- ?* W& N) Q- C/ o28 ! a7 b2 B; R. h29 / h7 ~+ }% l/ e0 q# f4 s30 # ~* c: q- @$ X31) q0 m6 }6 w; R+ _9 v6 [
328 `$ y) K5 D+ t
33 _5 q) h7 V# u5 _5 L! U. t
34 8 w& [" v) [0 x9 l1 D N35 ' C8 y- h$ j/ q0 g5 @- ?363 [& l; v' F3 w/ ^5 o. `
37) F7 R9 s3 v" J: j
38 ' ]- k/ p. C% e3 e7 A39 ' X6 U4 M( S+ S9 G; W5 q2 q5 R* O40 5 d& ~4 M. Y* W2 e4 o( w41 : {8 z: t& k" i4 \42 ! Z' e6 R4 G. g! O3 u43( Y! c& @5 R8 }" `# R7 k2 i" F
44$ `6 Q4 {! T$ ~1 N3 W8 v2 T
457 T2 \9 U' t X, Z, A
46 , Q$ i. L6 o) b! e; R( O. v47$ |8 s+ c/ \: Z' U. }; T* P4 a
48 & d9 O; p7 U6 `4 f6 F. U& l前端代码通过ajax异步请求后端的接口返回数据,并在前台取出数据对页面进行填充。 - P0 ?: t6 Y, V* b \5 P: C$ ?' B1 h L3 Q* N
//前端7 [' I5 O2 i( w) l4 k& X
$(function () { 5 |" i! e/ v% K! E. K //countDown();' C" o5 Q( k% o. v" W$ Z- V. k
getDetail(); 6 U" D6 s$ ~5 S. a" Z- l8 Q});+ {, z# k* M q# j) O* w. q
function getDetail(){! {% Z- g: b I- h. y2 v: @
var goodsId=g_getQueryString("goodsId");6 D. U- m& V# E; k, n# Y
$.ajax({2 B* j [2 k. O; m$ J% T
url:"/goods/detail2/"+goodsId, . z- c: }2 V) x2 H5 N4 T type:"GET", * |; |3 E' x0 e9 T; g z success:function(data){ . T; ~+ k* |& L0 V( A if(data.code==0){ - D, p: y% B- ~. J& c* {" i! j render(data.data); " D ]8 |+ Q9 ~ d3 S3 f+ u; G; P }else{* E( [8 V! c8 `! g" \9 ]+ x
layer.msg(data.msg); + a" _- ]6 C6 a3 f% e. b; p- o) e. p }- u/ c+ Q$ P* |% f; s! Z- M2 J) s( n8 ?
},4 _+ s1 B M: z5 ]4 v0 `1 u
error:function(){ " o. W8 f; p- I1 e! S1 H layer.msg("客户端请求有误")* i% ~+ O5 |9 Z1 g. J
}/ ?- u( F! z. [( B* q7 r
8 b* a7 }/ L5 ?" s% T, `0 V }); ; _5 e W4 q: o1 v4 K7 C4 W, p}& U+ O) c$ [, u/ \7 U- w
/*text() :设置或返回匹配元素的内容 ' E1 z) b4 T( L0 V% g5 b) o) x* val() :设置或返回匹配元素的值$ _, l v" g! Y
* */% j( j2 F+ S- c+ Q3 {2 V3 F& w
function render(detail){, O4 E6 ~1 `/ I6 e, O7 |- E
var goods=detail.goods; + f, L* H% u/ a0 M2 A% ] var KillStatus=detail.KillStatus;8 w/ S; u; X) h9 W8 n
var remainSeconds=detail.remainSeconds;( Z4 A, ]# ?: r7 i
var user=detail.user;9 R9 h5 }) v! k# ?3 Z
if(user){. {0 ~& ?' ?; `' g' [( A
$("#userTip").hide(); * y* N- u4 [9 ^) e5 L" x } 0 Z- Y: a0 g+ O; S $("#goodsName").text(goods.goodsName);# P, i4 M0 V; B. _- k& ?4 ?
$("#goodsImg").attr("src",goods.goodsImg);7 p. I+ q1 N: {* _+ H( y8 \
$("#startDate").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"))% i0 p' G- n" O5 ]
$("#remainSeconds").val(remainSeconds);$ w1 ^& @8 M% {; \ K
$("#goodsId").val(goods.id);) v- I. J5 O* I5 \
$("#goodsPrice").text(goods.goodsPrice);7 Y4 _- o% R8 D& L6 e6 Y; w
$("#miaoshaPrice").text(goods.miaoshaPrice);4 C& j% u$ l4 o# F) \+ J0 s
$("#stockCount").text(goods.stockCount); 9 S( Q$ H9 V" p. x7 R) L) b6 x countDown(); " K- C& ^( {0 I* A$ L6 J) q}& M, m1 Z/ _) V: ]; `
//后台 - V" i! h5 J0 {1 P z1 t! r@RequestMapping(value = "/detail2/{goodsId}")& n! l1 q5 S7 [( Y, B
@ResponseBody 3 S" t* }9 y7 r" Z) l# o public Result<GoodsdetailVo> detail2(HttpServletRequest request, HttpServletResponse response, 3 Z. L3 D2 V8 s% W8 d Model model, KillsUser user, & s, k/ |. ?- N/ T @PathVariable("goodsId")long goodsId){& L$ i4 P w* g& A E! t4 x
GoodsVo goods=goodsService.getGoodsVoByGoodsId(goodsId);# o$ S8 J k+ ~, q" n# ]! l% T
+ ^3 N, [! ?& v: {8 [3 |3 S- j long startAt=goods.getStartDate().getTime();# ~5 j0 D- ~* s- k2 H. Z$ c# c
long endAt=goods.getEndDate().getTime(); $ w% l9 i+ x0 R+ t$ T/ m long now=System.currentTimeMillis();8 w" M# O8 o v
* F8 `/ i2 }( c; S int KillStatus=0;//表示还未秒杀 " U" X8 R6 K7 d9 ~9 ~/ t9 Q0 }( ]" r int remainSeconds=0;7 Z$ {! H8 P1 U# s6 v
* M" F# e) K( D; D if(now < startAt){//秒杀未开始) P* y, ~2 { G3 T1 J
KillStatus=0; + S4 d: R0 n! n0 l4 X remainSeconds=(int)((startAt-now)/1000);+ v3 o7 d& _" ^' Y7 \
}else if(now > endAt){//秒杀已结束 ' N# q$ i+ W; h8 `/ E; ]4 _ KillStatus=2;( b5 W, x$ p/ S
remainSeconds=-1;, p/ P8 n" h- b4 C
}else{//秒杀进行中% z1 I7 E3 X9 S$ m
KillStatus=1; 6 C8 r8 Q y8 A4 B remainSeconds=0; % T; U, U; X' n6 @% W } . e& P7 n/ i, J: ]6 [& \8 k3 m! l+ P. `0 _; F2 q* o+ r1 N
GoodsdetailVo vo=new GoodsdetailVo();+ ~8 M$ `" s- ?7 }3 A
vo.setGoods(goods); 2 g* R3 y: n5 ?% g3 j. B vo.setKillStatus(KillStatus);. L0 B+ H0 T( j0 A, {
vo.setRemainSeconds(remainSeconds);' a9 |4 r7 ~9 d/ a
vo.setUser(user);: }3 B4 v; k# }% }$ x
return Result.success(vo); / ^7 e' [# Z2 M: E0 [5 s }0 z+ j8 n* B( l3 S" T
1 5 _* R9 ]3 O6 \. R, E$ f2 ; ^9 z, W+ ?2 ]0 L9 j6 ?( s3 6 m' O8 G8 c% G. |$ v; C4 ) @9 [8 l: X; m3 v1 l) |! b5 0 Y. `9 y6 Y6 e& l ~# s6/ w& h+ T! r0 o# ]. H# p
7, r4 F6 b. A7 f4 M
8 + U1 V* x% S, R- w9) i1 b/ L- Z4 ^* _) j' @
10 - [+ s7 x% i2 p- l& a; @/ [115 b) W0 T7 Z5 Z; v2 h4 x# c
123 l1 ?$ B: p! X2 J0 f6 M
135 o; x7 m2 [- F
14$ y5 F- \) n6 A2 ?( A' o9 F4 ^
15& E6 U# X1 r5 y
169 J9 |9 I, ^+ C4 Y- g2 n$ y
17 6 u2 v" s% {& _1 |4 S& O5 s18* D' ^) C! C- L1 B0 G; q$ s0 I
197 N* Y t8 w7 B3 B4 M% N7 \4 |
20 # t6 n5 n$ t F3 P0 L& w21 6 A; w& {2 X$ q- @228 T: i& c) R7 [" t/ \" ^$ i8 W
23 $ u" M) q# H+ ^7 Y; d24 , A6 T7 i) m& j+ E25 1 [ A! r1 f/ h9 K$ @26 1 [, C- `" ~ k2 x. |" u27 / u$ z3 [5 U8 g t5 t7 x0 N2 X282 W) E% k6 D/ J! T2 c8 [+ N/ T
29 ! x4 Q* `4 F4 r! W4 K: b! v30 9 @4 Z- w& S+ f0 p0 o# z31' @. } r ~# m% z* m4 a
325 h$ ~- j0 P# T
33 , D5 }) v! j7 J9 x+ u34, T" W5 {+ g( y( \2 x
35 5 o: W0 u0 ?) w0 Z% W36: Q" D, ]2 b# y8 h/ o
37; U. M5 R3 q# f5 c- |' P; A
38 8 O7 G' o4 A2 R# ^4 ?3 b n5 m }39 9 \8 R7 Z2 O. S' O) |2 i40 " R9 `3 A2 e0 Q41 , [" z/ e3 j/ V42% J! O) H/ c; [0 B6 x
43' j5 Z8 H. P4 X. I% g1 h
44) t) U3 S5 n+ T2 i- `
451 t5 Q7 F$ J- q) @
46 % B q- ~) Y) e: i' s3 i0 h* S9 B47) O7 N+ \1 H3 r% s; m2 j
48: R5 q& l! h! q0 \: B3 ~
49 / s" r) Q. P; @6 ~6 r50 - J% J( z. d X' O51 # X; k3 d' Z& a( K52 }, q& |8 O0 X: \1 j% H% X
534 G5 U3 ~. ^# D+ C: B9 U
54 . H& g; h: K% m1 `/ ?5 F55 ]4 y8 D3 f& W% M& K Z+ s( ^56 4 I" p- I8 ]/ t57 : C2 Q7 Y0 A9 x* p; s# i/ ~58$ }3 e: v0 B5 m6 h3 m
59 ' r6 O! ], P9 A8 N$ }3 s60- l4 j6 I5 m6 z
615 |% ~, o4 @/ y+ _, n
62 ; s3 ^) h0 M& V+ K" o0 X. G635 E7 n% d* |1 C0 F
64! k% D0 g ]3 B1 u; r
65 1 r( o( L: e- R( k) ^3 C/ m' ]3 K66 3 k" v# u$ h: x. D! B' U6 ?67$ X) i/ ]0 M. I% B* O
68 5 p5 a) a' {& O' p% `69 / K) H+ A1 P- b6 h+ U702 d( K! Z" V; E" ~6 D% b! N; M. ?
71 2 L% I8 ~" a! L0 G& k6 U72 1 e" G% z7 h; s1 [( z9 P1 b3 s/ U+ V73 + I! c. r2 _ I. [4 J74 3 L5 M b1 e2 z6 V! s9 T758 O6 L; \$ y I% }0 k( H
76# L+ r5 A, w+ ]+ [" _( K6 r& v
772 F) O) c4 h% \- v N/ A- ?
四、接口优化 4 @* Q: j: Z- i5 i- }: `! a; u8 t \
接下来是对于秒杀接口的优化,这是最初始的秒杀接口:! p9 S) i( y7 R) ^6 a1 @- c. t8 Z2 s
刚开始的秒杀接口的业务流程是这样的:判断用户状态->获取商品库存并判断->获取秒杀订单并判断->减库存。这个过程需要调用数据库共3次。 * ^, c) s* n7 W秒杀需要面对一个很严峻的问题:如何防止超卖或者少卖?此项目中总共用了两种方式,第一种是在减库存的sql语句中加上对库存的判断,来防止库存变成负数;第二种是对数据库中的秒杀订单表中的用户id列和商品id列加上唯一索引,防止用户重复购买。: L& N& k7 i( y9 J. N
( m8 f A" `5 F@RequestMapping("/do_miaosha")$ c& F$ n G7 t1 B
public String miaosha(Model model, KillsUser user, & q" p" n/ S: B" }% a @RequestParam("goodsId")long goodsId){7 E+ l% d" D+ M+ C7 |7 A f
model.addAttribute("user",user); 7 |0 D1 o" _. J) a# v- @: {: F if(user==null){8 C9 j' h: X }
return "login";: D) |( d: }7 {: c, K9 |
} $ ]- e0 ~6 k/ g1 L! Y GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId); 9 M; l# s8 h. h2 r int stockCount = goodsVo.getStockCount();0 S9 |# ~2 m' h1 k! e/ E$ x2 T h
//库存不足 , }- {/ o. }- g$ o5 h if(stockCount<=0){ , n1 `5 Z [, z' Q: N model.addAttribute("errormsg", CodeMsg.MIAO_SHA_OVER.getMsg()); $ x/ P- L- p: h1 u return "miaosha_fail";1 R, [6 q6 V0 H9 U9 R. o. R
} ) I0 D* B! ^, o2 P //判断是否已经秒杀到了; v* f' p7 `' Y$ c
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId); 0 {1 W# ]: c( ]) W. l! ~# N if(order!=null){% i, s( ^8 s# R
model.addAttribute("errormsg",CodeMsg.REPEATE_MIAOSHA.getMsg()); , A6 Y( L% Q- B, A4 F& \8 D return "miaosha_fail"; 5 b+ M R4 d0 S# P4 W }% D" H7 e) K: b N5 v1 B
//减库存,下单,写入秒杀订单 & w* I% ]* Q- l& r6 P$ A: W OrderInfo orderInfo = miaoshaService.miaosha(user, goodsVo);7 h3 T) n6 v; V! j( R
model.addAttribute("orderInfo",orderInfo);, Y3 _- g9 y: X1 _- G: g3 {
model.addAttribute("goods",goodsVo);" g3 j! [7 E- \% f3 W! v' O# S
return "order_detail";- a/ Q$ p: X( V( Y' V+ m! B8 E
}" k: q( K& R# [! [/ F
14 h* `5 K$ g8 m
2 ( o( U4 W: `$ u* c5 P3, a, @' j) W4 j6 L2 V
4- }/ P) g- |: j
5- k: i' h5 a; m: p% a& `/ B/ M
63 O* a! }+ B& g$ C; V
7 ( N3 _7 o, Z% g6 m1 A# |8 Q) S. c/ s! v6 j* ` T8 u- |
93 L3 u9 C" R2 d; r6 T3 a
10 ( Y. u' \: r1 H7 O' A# f; @5 n- A11) A8 o; k/ b8 z$ O
12 + L4 w" I! c* F# Y9 D13& s0 g: r+ |" X" z7 {! w
14 3 \; _( I% @1 Y159 E5 X3 k- b: n% _. Z
16 6 {1 R, K' l. ~, J) v7 q; R17 - s/ m7 p& o {* j0 a+ d- n- Y0 q$ J3 w18) m T2 ?' O: M/ X$ J3 o
19 3 M# N& ]2 }7 j208 D) [' {; {6 r" }. ~- N+ B1 m
21 & Z7 f8 Q1 Y; ~2 S22# \' R* E, s c" a, @" c! Z
235 ?; N9 x# ^! @0 z2 c, }$ d
24 ) B5 Y* _# X$ f5 _$ M25+ W% E' B* g9 W" G5 y+ Q2 x" O
26 3 X" Y3 e1 c1 s4 ] Q4 j; W优化:秒杀静态化,通过ajax异步请求调用秒杀接口并进行轮询查看是否成功,redis预减库存减少对数据库的访问,内存标记减少redis访问,RabbitMQ队列缓冲,异步下单,增强用户体验,同时减少服务器压力。首先是使用redis预减库存,我们使用一个HashMap来存储某商品是否售空,这里用到一个方法afterPropertiesSet(),该方法会在Bean的所有属性初始化完成后调用,在这个方法中,我们从数据库中查出商品列表,将每个商品的库存存入redis中,并将库存不为0的商品在HashMap中设置为false,意为未售空。(此处应加上对商品库存是否大于0的判断) % X' j4 D: x4 y2 |6 j! V在秒杀方法中,我们首先获取mao中的布尔值来判断是否售空决定是否进行下一步操作。使用redis的decr()来预减库存,减完判断是否小于0,如果小于0就设置map中的值为true,并且返回,否则进行下一步操作。1 D9 R1 G$ q0 l+ l, a( ~2 S1 N
关于RabbitMQ的操作,我个人感觉还是有点迷的,毕竟真正的秒杀不会让你一直等待吧?(没事,咱也没秒杀到过东西,问题应该不是很大,也可能等待的时间很短人们感受不出来?这里请懂的朋友评论区指导指导呗) # E# A+ Q' o% W; J此项目中使用RabbitMQ将秒杀请求入队,前端进行轮询确认是否下单成功来实现异步下单的操作。关于RabbitMQ,RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,或者简单的将作业排队以便让分布式服务器进行处理。 & e$ C! S9 `, D6 C; YRabbitMQ有四种交换机模式,分别是Direct模式、Topic模式、Fanout模式和Headers模式。Direct模式需要将一个队列绑定到交换机上,要求该信息与一个特定的路由键完全匹配;Topic模式是将路由键和某模式进行匹配,队列需要绑定到一个模式上;Fanout模式则是不处理路由键,只需要将队列绑定到交换机上,一个发送到该交换机的信息都会被转发到与该交换机绑定的所有队列上,类似于子网传播;Headers则是以key-value形式来匹配。 , t, P) a& S3 M5 ~队列和交换机绑定使用的是BindingBuilder.bind(队列).to(交换机) . e. N6 }$ p2 P! a5 {- ~ 8 [( G7 {& n% [8 \3 M3 k0 e注意此时秒杀操作并不在秒杀功能中处理,而是在RabbitMQ中的receive()方法中处理(见第三段代码)& p8 R( e O5 J# P9 `" Z+ T ~* E
3 q: H; f# r. s7 A2 V0 r; C6 N9 r2 H //用于保存商品的id和是否售空. Q# R, {4 b2 a& f
private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>(); 3 h+ D0 N' l2 \, r- K % G3 C; G0 s) H4 I6 `, L; h/ I
/**: [: v0 r- I+ [" ]
* 系统初始化6 u0 r- a2 E$ Y% g& J; C7 u
* */( \; P6 ]* V3 J" ? q. E3 z2 C# N
public void afterPropertiesSet() throws Exception { 9 Z2 \3 i/ @; F+ A& j$ O9 ~/ M List<GoodsVo> goodsList = goodsService.listGoodsVo();5 Z P/ x! D. L8 x3 x6 c
if(goodsList == null) {# ]( A5 v4 G7 g4 T* ~
return;+ L3 |) l3 s( I3 D6 }$ b
}% d6 R2 Z, A, c) i0 F
for(GoodsVo goods : goodsList) {* n: K3 k. n T+ W( s9 i
redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());8 ^6 K/ @9 z, {: ~* k
localOverMap.put(goods.getId(), false); \4 }3 V; e$ }. } } 3 B3 x; d; M/ h8 N/ \ } - y/ J# T( P5 U& I) ]1 E: O5 A' E" s
7 G( A3 l0 Y4 @7 h @RequestMapping(value="/do_miaosha", method=RequestMethod.POST) ) n1 ~5 k) z H- t) s @ResponseBody 5 N7 T/ u6 n2 B, p: v( N3 H2 w public Result<Integer> miaosha(Model model,MiaoshaUser user,* \- `2 J- [" X- c3 B
@RequestParam("goodsId")long goodsId) { 2 c0 u" h! B' z# [1 G model.addAttribute("user", user);' D# |1 ~$ C( }# T5 A
if(user == null) {4 d" N8 o w8 e; q& a F' Q
return Result.error(CodeMsg.SESSION_ERROR); 3 L4 B+ U9 d7 n }; D( g0 E. K- s) n# i$ ^# y
//内存标记,减少redis访问9 B7 J& r! ^6 i5 Z& o# d
boolean over = localOverMap.get(goodsId);" T6 Y+ E8 w4 B0 C ~# O/ A
if(over) {1 @' h( U' F1 D7 D
return Result.error(CodeMsg.MIAO_SHA_OVER);* h3 E1 q6 ^/ j4 R, p; r1 u% U$ F
}5 z" u( D4 ^7 v) U# E/ Y
//预减库存 # s; V# U* \8 k4 K4 A9 [" x0 r long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//10 - M. ~0 h1 `( i1 q if(stock < 0) {, D2 Z3 a. Y' P* j; ? C! u
localOverMap.put(goodsId, true);3 _+ L% k# U( o$ O6 ^ C) ^
return Result.error(CodeMsg.MIAO_SHA_OVER);- j7 r# l; u! X/ F9 c
} 9 e1 H7 D( o. J+ U6 {$ S) m //判断是否已经秒杀到了 - c* ^! \( H; V6 A2 u MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId); : j# E$ y! J0 M* z4 l if(order != null) { 4 a# [4 u8 C) S1 W return Result.error(CodeMsg.REPEATE_MIAOSHA);# m! p( u, P$ v% h
} 4 F# S _# c$ l: d4 M+ v% Y% r7 y3 K //入队 : E2 O( S1 o+ H W MiaoshaMessage mm = new MiaoshaMessage();8 b! @' r. s' w7 U
mm.setUser(user); / Z( O1 K% T( r7 R% u z6 O& } L mm.setGoodsId(goodsId);$ Y) |7 S4 g/ _( i2 E! c
sender.sendMiaoshaMessage(mm); - R7 b& y# p1 U( I. V& b return Result.success(0);//排队中 * L$ D( i0 o" s" ~ }3 k4 d; V9 g" a( [3 n& C
# b2 V! d( F+ B/ k0 L) Y2 [7 k5 w
/**MQReceiver类中的方法5 f1 ~! M. b5 V4 e% ^% Y
* 此时到达这里的请求大大减少, ?% S4 K/ A- O) I
* 因为有redis库存的拦截$ S1 P, Q' f- ^# M8 X% F9 h* T
* @param message / o1 |( R: R' o$ y( V, f% L */7 ^# w/ G* J, T0 E
@RabbitListener(queues = MQConfig.MIAOSHA_QUEUE) ( F% c; g0 i' w* L public void receive(String message){ 8 M: ]$ n! M7 O; }5 X; V log.info("receive message:"+message);! ?+ W" ]9 M2 C$ U3 o
MiaoshaMessage mm = redisService.stringToBean(message, MiaoshaMessage.class); / X9 n( Z+ i. R( K KillsUser user = mm.getUser();- _/ K8 e7 V5 M" P9 R- H
long goodsId = mm.getGoodsId();: N. D0 B5 z' K% D" ~* B
//判断库存8 V; ~5 b5 e- ]8 O( _! L3 M
GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId); + ]+ @* V4 F) Z% W$ [ int stockCount = goodsVo.getStockCount();) B1 a, i3 `- K8 ~9 t r/ U
//库存不足 7 n* H3 I+ r* @* h1 @5 c: B1 T if(stockCount<=0){5 Z6 U3 L- I' o
return; ! t* b: K3 J4 Y5 g1 x& v }" S' h1 u. P+ f
//判断是否已经秒杀到了,这里可以不去数据库查找是否有订单) b3 I0 h$ @# D7 Q
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId); % Y+ {( B# _. n& m if(order!=null){ . u# N$ }8 P! N! E; b/ v return;4 V: B! m/ R' q& L J# w2 q
} 7 g! F5 G; S l; ^+ V* \ //减库存,下单,写入秒杀订单 # l1 q2 q M) z9 K3 X OrderInfo orderInfo = miaoshaService.miaosha(user, goodsVo);: r) F# K _2 Z( E/ |
} % t8 Q- t2 S2 H. T* L8 Z0 ?1 ! d& h& s4 S2 V1 A2 ! x0 I. t0 S3 ~, ?32 Y, \: k+ l$ B/ A& H; ]
4 - x& Y, w% B! W1 C5 p5% d+ l% a' w% M3 c2 [
6+ {; O$ z m+ e$ _2 Q$ F3 H
7 + X J# p9 I* J2 K' z, q; q8 # \' `/ q6 g3 N5 H9 ?98 _$ t; ~. K% ]7 H% @
10# a1 e# s1 Q& q% B
11$ _' l9 f a( P: l+ [+ N! j
12 8 @- ~& C3 W$ {5 ?% y0 O8 G133 z: D {3 I) l2 @$ a
14 8 P3 y; Z+ B) C( E p: d7 D5 W; [15( @1 ]* f/ {& _! E+ I$ T
16* C) N7 V& X2 r* ?7 O3 s1 U- S
17( ]: {; H! f* {# P
18: C0 x" Y7 D' z
19% @, D7 ^* K" c
20& W9 q/ i& l7 Q9 I. L; J- m
21 0 D9 |! E; m4 e' y1 i# J$ m! x8 h. g& o22: w$ ?: \5 ]) O: {( k
230 T1 ^7 T' p& h% C
24 - m, v3 h" z. U0 p0 o( {254 i; G) r( f; p6 o7 h; b8 M
26 4 ]8 E) i) y- D3 \8 C2 P) [271 h: l1 d4 `" J, Q* e
287 r, P1 D1 L n
29 0 Y5 A ~9 p4 }0 _+ u( R" ^30/ h \% I, v" v7 n$ c1 }6 e' X: `
31 7 s9 E+ F& H. w3 @. Z4 g) F320 |9 d, p+ H0 L
33 # G; [+ }% o; f! `8 R34 . S+ l6 V6 l. S8 V7 D35 " z- y. h R" a( K36 & P; s3 K& f, m) _* C37 : s1 T$ Q4 k1 ~, {% a j7 A38! E- A! r5 O5 d+ b; p# ?
39 , H! O* P l- M- z, ^40, U9 ^- I1 h3 P8 n
418 A" F; Q% q$ q |9 E$ b- S
428 g9 { @2 |! `( |, o2 v5 H
43+ @$ [" K, w- I' V, X
444 B/ ?) `6 t7 X) R4 `; N0 A
45! @$ K+ T6 {7 j4 \6 C/ F. G
46 ' Y, U* c! M. o! K/ k471 Z. k) R8 b0 K' U e
48 . `' i h- e+ d" y49 1 C; ]- X4 Z0 i) \' N, U50 : w1 n% S0 C% ^* M2 B% o% F: E51: S, ]# {. M- o
52$ R- g4 H2 D. j) ~2 i3 f; }3 z6 N& Q
53/ Q' b2 q" p* k7 J4 G+ D
54$ J" X6 m' e1 `1 s. e( `* M0 [9 H1 V
55 " Z+ G& p: W6 K2 p+ G3 E [0 c56& B* c+ k2 s" R, Y$ Z
57/ T, l- u+ `0 ~/ F6 i9 U9 q
58 1 d' o' Y% n' B1 B9 ~7 r. Y$ ~590 d* h/ D2 q$ z5 g5 E9 B
60 8 S' q3 s/ _4 D; j) [61 : [! T H: ~$ {# F, P5 I62 O+ c+ c3 d+ q( v; r
63( A0 M3 g: f7 P
648 W) k5 p A% H- @5 m5 |
657 ?6 n1 O& I3 y- I
66 0 t6 p6 Q" p; Y0 o5 X' I. |3 e1 G$ d674 g! R% @% _, G2 G# L6 o
686 ~" m- p4 |- _# o8 C q
69 + n3 T2 s( z1 m, O6 g7 D2 e70 L! g& h4 T+ b/ R0 z! O71- l0 O4 ~* b! ]! {% G2 b
72 - D4 S# m7 M- w73 2 V' Q; p2 n5 }0 S, c( V! ?74 1 }% M/ l1 m8 L: n/ U5 f+ J6 X75, w& l0 |1 [% P' ]) B+ P
76% o) ?6 B. c+ b! s
五、安全优化 ; z* d7 v( M, v9 f6 P $ g4 Y& j; F! H* d E1、秒杀接口地址隐藏- q3 s$ I; H2 x! _" h2 g5 S2 y. k