' K3 q& n8 t* Wpublic class Result<T>{, H1 s. M; B2 x1 ]3 L1 B+ X
private int code;7 _1 J: l @8 x2 E' f! ?) e: l
private String msg; " C1 ]6 m7 _, d9 }+ V private T data;$ ^, o6 u( ~( g) M2 z5 v7 F( f6 O
5 B ?$ G3 @$ K" w( @) ~ /**; f$ f* ?# I- }# v6 R
* 成功时候的调用0 g6 ]1 V; P2 @: q1 u* B( n
*/ ) i1 d9 F: H7 _: L+ T public static <T> Result<T> success(T data){ : i6 A4 p4 P0 _' d, G4 H0 c5 R return new Result<T>(data);( a h* P+ h3 I
}* W% ?7 J; a& t$ _8 }# J
/**5 j; u% i) A' w9 d* L6 S) R4 R# ~3 I/ S
* 失败时候的调用" L0 E5 Z& H( H/ h' i
*/ * P8 `6 i$ |6 q public static <T> Result<T> error(CodeMsg codeMsg){ 4 ^' n' J$ T5 G return new Result<T>(codeMsg);2 H8 k' U# L) P! {! c
}. N' H) s; p y9 H
1 E7 W5 B/ ]0 i- _& R, R$ D 1 J) f' q+ z2 C; |' R: f& r1 l$ P; ? private Result(T data){ 1 f! v X9 t0 K3 r( m this.data=data; - ~3 s* `5 B' n% e } ; U* g- z5 P: F* P7 Q: A1 @ private Result(int code,String msg){ z/ P- u6 d, N7 s7 k4 Y3 ], i this.code=code; . d& H/ i7 _# B/ M* i this.msg=msg; . R, K: I2 b5 h } 8 k0 n5 ]' ^- M4 Q, ` private Result(CodeMsg codeMsg){+ \9 W, x9 [" W) Y& U3 t* m( X
if(codeMsg!=null){ 1 t! L9 G2 x- n8 r& y- v* ~ this.code=codeMsg.getCode();) c# E9 V" D/ A) j+ c X! T' ~
this.msg=codeMsg.getMsg(); _- p2 X& J. Z! q$ |' ]
}& ^! a" i- b4 T2 e+ I0 Y" n
}( E/ ]. n1 |7 j
2 l- ]7 }: z5 i8 R
public int getCode() { b% X2 X6 l$ Z$ j) a6 N return code;$ P% H; X+ A' x; ^3 R, i3 V
}. Y6 O3 v; \ @4 U1 J- c3 ?
public void setCode(int code) {& @8 z7 j4 I( P/ b `
this.code = code; . E& o B4 @) V- v4 \2 R' g5 q0 R }* Q9 H! _1 A4 Y }1 q. x
public String getMsg() { 1 N0 Y4 {+ y9 {4 B! N return msg; 1 a" M, s+ i" u) o# c3 b }+ x/ R6 J- }/ K" E+ l l# b# ?% |
public void setMsg(String msg) {# W( ^! _. \5 \( }7 L) k+ {
this.msg = msg; / ]$ @! }* _7 A/ H2 @- A7 N$ V } 4 M9 g. H! Q* s7 ? { public T getData() { : A/ p( C3 V) n- s return data;1 o) s- I3 d, b+ S1 `
}! d6 }1 Q& }1 u; ?
public void setData(T data) { 3 ~0 D" O3 p. u2 _' W5 B this.data = data;7 y% l( k' l4 ^* k' l* x, c' T
} . T. h+ k& q! y& Z, s} 2 _+ B p6 c& E6 T6 u1 - W. v' T: h0 S( X3 ]' I7 j z2 / r1 A0 z9 k1 O z- W3 - P& [' ?* y d. E# S4. h+ U! [3 ]2 Q6 b7 F
5; N B$ H8 v3 l) G
64 Q9 ?3 L6 t$ W+ O
71 S! d; M$ E W
8! t! T4 j' X' V" b0 `
9 " |3 D) v) F- t- g) @/ H, v& O10 + T" S% o# p* n1 z& R1 F11, l0 r) C3 |6 U( N- W U- e: w: B
12 9 E9 `( {# h% {2 h% A+ `3 y- [13+ i3 o% p4 b. O& m- Z" ?, H
14, p8 i) `4 }8 V/ t! r- C W
15* M5 E3 C; K0 Z! I
16 + j. x& W) n$ c) Q% |( T% N! r17 6 W. l' B g' H( N$ z7 i* }180 x' e9 B8 }$ E
19 : ], j8 t; }9 w% \, M3 D2 p" ?20 o7 L; O& N0 P, q. H) M
21 " L1 U# j) a, y! }: R224 V2 C; }7 E" q
23* P( J, C2 [& a# ?6 Y2 [
24! V0 u6 \) F6 n4 R
259 f* ?0 G6 }1 M* O+ b& {+ U8 X
26 # z; L. J+ L( b2 p27 4 Z: K3 O- g9 C7 D; O6 e28 . Y4 D: j2 @8 ?, B29 , g' @% J1 `5 f% Y# o* u30 ' z/ U p& a: w3 B$ u: k: o31/ |# n$ S( O& ]
329 u4 E' h5 v9 ]& O
33 5 I/ J1 ?4 G; Y/ R# F! e34 " s- J4 k) h/ [3 @; Y7 V35$ F1 ~8 y" S# ^' N" \: k: y i
36& _0 E u2 {; O3 Z; U
37 2 r: W D) ~$ R0 ^# k1 O38 1 M* g6 o* Z# X$ J39 3 }& t A& q" ^6 h! k40& K5 ]- T# y& \% B/ l
415 z/ I: y7 b9 k# Q" p/ l* `4 O
42 7 \+ z3 h' b* i( n) h1 N43 # W4 L9 p7 Y: X( y; h1 ?44* l. S& P" {# d' `9 v5 r
45" s6 R0 {6 F. | S y3 l" Y3 d
46/ h5 L3 A4 [/ {$ F5 L
47 + ^5 O5 |! O! k4 @( ?48 0 Q' D, {. X( p2 s1 g: x49 3 B# I) q9 O/ Y6 k) Q50 ) w! m" [- ^% `/ L I0 p/ }6 z51 3 {! ] f1 B, `2 z) c$ k52 5 i) t3 }% ]+ ~# I二、功能实现 & ^/ R- c/ G! V. F$ j8 p) a. q 1 L+ h( i6 F* Y# d* N功能实现这一块算是比较常规的实现,毕竟这个项目的重心在于秒杀的优化而不在于具体的实现,只是实现了常规的登录,商品列表,商品秒杀等功能。 9 ~% f- G. f: J/ O( C, g. i) O) m) O: \2 H- U
1、全局异常控制器 9 m7 N) |* p! A4 N3 V4 x6 @ ( L7 M- M. f+ G% I4 g8 J: {为了使得代码更清爽而不十分复杂,我们实现了一个全局异常控制器。在这个类中,@ControllerAdvice是一个增强的Controller注解,使用这个Controller,可以实现以下功能:全局异常处理、全局数据绑定、全局数据预处理(SpringMVC提供,在SpringBoot中可以直接使用)@ExceptionHandler:如果只使用此注解,只能在当前Controller中处理异常,当配合ControllerAdvice一起使用的时候,就可以摆脱这种限制了。 1 ?" v6 ]6 s/ A; o4 }# F J 6 m, r: i0 Y; Ximport com.yuan.SecondsKill.result.CodeMsg; 5 I B2 ]5 |( F* Dimport com.yuan.SecondsKill.result.Result; ! w* a9 ?* A& N4 | _( himport org.springframework.validation.BindException; + F {* I# {/ C& y! Jimport org.springframework.validation.ObjectError;6 n' j& I" I/ O5 T) H
import org.springframework.web.bind.annotation.ControllerAdvice;; o9 }; `2 n" h. Z, T
import org.springframework.web.bind.annotation.ExceptionHandler;8 E* m: D# R( I Q1 s" p" p# f
import org.springframework.web.bind.annotation.ResponseBody;+ v9 ]) n' a+ Q- S/ }" j# }: w
import javax.servlet.http.HttpServletRequest; & y# x+ [2 ], y' H4 X# R1 Iimport java.util.List; 1 P: j- v( p% l) |% y' ]) S' f5 j* W; Y4 m {+ d7 M
/**) [+ ]# E- \% ~$ d z# U
* 全局异常控制器 4 d: M- |5 @, L/ f * 拦截异常,在页面上显示错误信息# n) v9 Z& }0 D9 A
*/1 q& b$ s# E$ H
@ControllerAdvice# q2 ~8 a" H3 T1 R
@ResponseBody ' c! G; L4 t$ ~7 K# D: cpublic class GlobalExceptionHandler { : K, {! v( o5 A @ExceptionHandler(value = Exception.class)% ]( i+ W4 b: V$ k
public Result<String> exceptionHandler(HttpServletRequest request,Exception e){ - R' H; S2 S4 H1 @5 O e.printStackTrace();" L& _1 D) ^% n" {
if(e instanceof GlobalException){1 z, s( O. l" r$ Y
GlobalException ge=(GlobalException)e; 1 ]. S+ h" j. x+ P0 @ return Result.error(ge.getCm()); ) t+ x3 I+ a8 c+ }3 `$ Z% n2 M }) A- ~6 d) j5 s9 T9 N x. Q
if(e instanceof BindException){: ^/ K" c( ~2 W8 c" W; [
BindException ex=(BindException)e; 8 t& _( w* u5 G9 \ List<ObjectError> errors = ex.getAllErrors(); 4 R0 s7 k) b9 v/ w- D ObjectError error = errors.get(0);- z/ V: b# J0 d0 K
String msg=error.getDefaultMessage(); / I$ ^. R+ }0 i9 x9 O return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));. X4 Z9 K7 o+ O& C0 R" K# c
}else{ ^% C3 }8 L% Z: ?# y9 C return Result.error(CodeMsg.SERVER_ERROR); ?- s: N3 o% L2 W7 o' N } ) a. V5 F) B/ f }! e' h( p. r$ e8 S1 N4 T
}. W+ Z: o y- ]) ?
1+ S. S9 d% J$ D3 j* e; {" g
2 2 V9 T, Z) n1 q5 y! R; Y3, g; g, H% {; c6 R& |$ U
49 S4 U0 E) {6 T0 B" G& z) r
54 e2 s4 A1 |3 G; w
60 N6 Z6 S6 r) P
7& ^% R1 J' b8 G) j# a
8! z+ r2 h+ S* l7 }! y- L+ x
9/ W$ I1 _$ g' [8 |9 G0 H
10, T& K2 u0 ]9 H3 v6 ?, D3 O! T
11- u V2 E5 l$ q* v6 [6 ?. S! P: [
12 9 [4 n; v' j3 L1 Q" H13 * X- H, R0 A' Q A14 8 R9 T; H& \* H) L3 P8 ?6 N158 z" b: F6 q6 Q$ ^/ U
16& r' E, m: T# [. y
17 + W2 r, O5 D d9 q6 W18 1 g+ \! X5 C9 |* L a19 $ C8 B4 g, O% w4 P20/ d# _& y5 }- ]
21$ S' V$ G( j' R
22 - q/ d2 R- r1 j7 a239 Q7 D; r7 W& P0 P4 H
24* w" Q, U ^+ C: Y& ]
258 ]3 j, H4 z2 \& c
26 6 p+ d( U l. y+ L27" I( V; a8 T2 @$ {6 R7 v
28 . ?9 s" P/ Z5 L291 K3 Z. r! d ~# e* W. i
30 7 P. ~" I" ?5 J& T4 k+ ^# ~31 3 e0 B, E: \* ~$ g! D; M9 n1 D5 H32 0 \0 K2 c! p& t1 D336 G5 j" q% Y- Y/ y
348 m7 d( x: R7 k4 x
35 L/ k2 o' M1 f: \7 D在登陆方法中,使用@Valid注解配合全局处理器达到对手机号和密码进行格式校验的目的 ; e, n1 M2 b% ^8 u" m $ r: [$ S, \" j6 a! v& U8 @7 k2 z! \ @RequestMapping("/do_login")2 b! e, c9 h5 q
@ResponseBody3 s2 ^# `8 x4 d! g( A3 G+ d
public Result<String> doLogin(HttpServletResponse response, @Valid LoginVo loginVo){ / v! x7 R2 z$ \: Z log.info(loginVo.toString()); ' ^3 ~+ k7 _* w8 { return null; 0 d+ a& @7 s! k, U1 a7 z' } K }' k9 E0 l* p7 G/ L7 p! z5 b+ H c
1 U; V$ i! I, B& N% Q! v24 s8 M* u0 [: {% B4 i& Y
3- O1 q2 d% n/ z' y) @& I
4 4 `; @2 _ d/ F" f/ ?3 }5 7 Y7 e& D$ q6 J6 P6 ) M/ `% N5 d3 Q5 H& c9 {; R, |4 y实体类中的注解来校验是否为空等等,其中的isMobile是自己定义的。4 Z2 k2 m9 ]' f$ u' {, \. ^
& q* a- i1 l* t
public class LoginVo { * P% G. e! W! z0 z @NotNull ( d! e5 g, b7 E, z @isMobile H' T& ?6 K) T0 H. `4 j& Y+ @ private String mobile;% [ Q$ m! C& U' `2 o" k- Q
$ w& i, u0 Y( A6 X+ g" z3 _! i @NotNull # w# t5 i; T1 _' @; B* M @Length(min=6); A( i' Z) @2 _2 O
private String password; % P$ y6 w3 Q. [7 W( V( z! ~+ @} * ?1 P# {$ X8 d* B1- y5 I0 |7 `4 p6 M# D. }0 J0 E
2" U& G4 D# _7 S4 p- ?* ]+ ~ c8 A
3 5 r% ^) a0 l% _" Z! j9 b4 . G) H3 K2 G) o; R0 M# A( m$ h( ^4 {5* i+ o4 G5 x8 T) a" R/ @
6: g; h. @3 W* D0 U8 Z6 A
7 * |7 X& d$ p, p. L8% a: X/ T* C2 {3 x* v. |) j
9 : C- T3 f* J4 S/ f0 ?/ t: u9 T@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) 9 Q4 b& d" Z8 n* K7 c# A@Retention(RetentionPolicy.RUNTIME): a& f) ^3 Q4 r. N# y
@Documented7 L: D; ?: ~) s' h) J) j" o
@Constraint(validatedBy = {isMobileValidator.class})1 H( n$ _4 t$ }3 G5 j, W7 G. S* x
public @interface isMobile { , T* x4 X5 `, b/ z boolean required() default true;4 Y9 G( |% I+ d# @) m/ n
! o: J9 B; ^& {* u% \ ?
String message() default "手机号格式错误"; 0 k, n" ~% d3 n" O- s8 K: G0 T+ _# N, k7 g0 q
Class<?>[] groups() default {}; " i; S7 F ?/ {& U0 w# j l1 o4 b& Y; W: X# e* M Class<? extends Payload>[] payload() default {}; + _9 f8 r$ m$ |7 N6 Y9 D} 6 E' @8 |# |: p& r: x; W3 P4 }/ q1 $ z1 T+ C& B( N6 n$ P- v9 u2! k$ {7 ^1 h! t% x6 X- y3 |
3 5 W) c) @8 `; G9 d! e* |40 ^. u3 m0 t3 |6 H4 {8 j; }- d' ~
5: `4 i+ [# |- V1 o/ K0 X4 ?- b1 U9 Y
6 " M) |% d6 r5 A$ @! P. G7# Q, n3 {! p, n! g# Q* S7 A+ _4 \- G2 N
8) L" t" g% f/ M, g1 X
9 2 }8 n6 T1 O1 `$ g* x; f6 i105 H& ]3 b3 s1 c
11! E; Z. ^" T1 i3 l# }: x
12 - m. y, y- {( o136 Z7 ]# C& e' A/ [: O; T+ R
2、拦截器的使用 - u1 G& _/ }) t4 G; F# t3 r7 Z( f& \5 H1 b8 F
使用拦截器与实现了HandlerMethodArgumentResolver接口的类从session中获取user对象,并作为参数注入Controller中。) `$ K4 g) M; F& O9 i P [# g+ s
( R) U5 @3 X$ g
3、分布式Session的使用 2 S! t2 X0 F& K3 e2 \( u: ` l- b4 o- m: M9 y! {4 k9 d
此处分布式session的实现主要是在登录的过程中,为用户随机生成一个token,并保存到数据库中,随后封装成cookie返回前端,并且,获取用户也由这个cookie中的token来实现。关于分布式Session一般有4种解决方案,详细可参考这篇文章:分布式Session的4种解决方案。项目中所使用的就是其中的利用cookie记录Session。 Z, S0 q& ~3 v& m& X" v
+ e4 G3 e+ W' y5 I三、页面缓存优化 3 P0 z1 {/ R$ q k9 V* o$ h( J3 P1 j1 A4 S& K4 S! u9 ]+ s
页面缓存你优化主要使用redis完成了页面缓存、URL缓存和对象缓存,并且使得频繁访问的商品详情页面和订单详情页面静态化,成为纯html页面,使用ajax异步的方式与后端交互,实现前后端分离。另外还有静态资源优化和CDN优化没有实现。$ a: N/ k# d6 \; L/ s9 t
) L, I; N$ b: M( W; V
1、页面缓存 - {0 B7 D5 L0 X4 C+ Z + ^$ H0 g! l% j- y$ E这里是一个商品列表的展示,可以看到前后代码的区别,刚开始只是直接去数据库中获取商品列表并返回到前台的页面,前台页面获取其中的信息进行填充。而下面的代码则是对页面做了一个缓存,流程大概是这样的:从redis中取缓存->从数据库中取数据->手动渲染模板->返回html。 - N k3 o; \3 v% h: ~/ b3 g. U首先从redis中取相应的缓存,如果有直接返回,没有则到数据库中取商品列表的数据,并且进行手动渲染,渲染后将html页面存进redis中,并且返回。在redis的保存方法中设置了过期时间,默认是60秒。 $ |* j) M( i5 j/ o% X f% m0 a0 p+ _4 n; f
@RequestMapping(value="/to_list") / q2 \7 {6 y7 M! E0 a* | public String list(Model model,MiaoshaUser user) {% ?$ v% J. W# C3 p
model.addAttribute("user", user);6 Q+ ]$ J# o$ [
List<GoodsVo> goodsList = goodsService.listGoodsVo(); 3 B2 U$ y& G* R4 |! I) d# S model.addAttribute("goodsList", goodsList); 0 B, E ?9 x4 s0 f return "goods_list";" J! A; O0 f+ o+ D, w' R
}7 T- N, @2 @9 i* u
1 # O0 \: \1 Y* A4 {9 _: b1 X3 Q2 ! U/ E+ ^0 f5 m* y7 t3 6 Y/ a7 s# K* v0 O/ i" Y. b, T4& X1 |$ E# D/ b1 T
56 H/ k+ N6 Z3 ]* A1 A2 m. }5 N5 M7 b
6. |" \2 g+ }% u: O
77 W4 w" g s: Y) \' y
/**; G) \9 v# l! t2 v( P+ g
* 页面缓存:取缓存、手动渲染模板和结果输出& A- U, T9 S8 M/ a& a
* @param model' v: f/ h$ o( f H' V& q* d
* @param user , Q- K- v* |- Q3 W( d * @return % q( [1 s3 G1 S5 k- \ */, s0 {( v5 g6 ^2 c) }6 G
@RequestMapping(value = "/to_list",produces = "text/html")4 _5 f" q0 _* I1 H5 `
@ResponseBody ' u- Y. @5 x! t public String toList(HttpServletRequest request, HttpServletResponse response, ) n3 E7 n4 M3 v. a9 m Model model, KillsUser user){4 j) W( r2 [- K
//无cookie返回登录界面 2 u# g" k4 o$ X. `# z: } model.addAttribute("user",user);: K: v/ h2 ]. K9 b" l2 k P9 ^2 x
//取缓存,如果redis中有相应的缓存,直接取出使用 9 [' I& ^) {* R6 v/ A* H7 e+ b String html = redisService.get(GoodsKey.getGoodsList, "", String.class); 3 M7 l0 u3 P& {5 F4 M- P9 ` if(!StringUtils.isEmpty(html)){( i C$ C5 p4 Q: A4 V* q
return html; - K) ^" p6 i# N& Q2 f' G" H, o } + l6 D4 P2 }5 u$ ~& G e //查询商品列表5 C( M1 w) d# A
List<GoodsVo> goodsList = goodsService.listGoodsVo(); 6 V. [9 w; X2 w1 y model.addAttribute("goodsList",goodsList);% y* Z& e q1 L
//return "goods_list";) G. H) a2 o* S7 M* s( T
6 Z' b2 k: d. D3 L' ?" e SpringWebContext ctx=new SpringWebContext(request,response,request.getServletContext(), 5 Y5 _3 R/ v& c0 j$ t request.getLocale(),model.asMap(),applicationContext); - {0 v. F% p* k2 E2 T //手动渲染模板并且存入redis,框架渲染的具体方式也是如此 9 b. V( t1 I2 W; J4 @ html=thymeleafViewResolver.getTemplateEngine().process("goods_list",ctx);# t% r6 S" K$ @, y9 u' Y9 e. p0 v
if(!StringUtils.isEmpty(html)){) H" N* F( D2 F" f* R1 {7 W
redisService.set(GoodsKey.getGoodsList,"",html);9 g4 h- k M4 l* g7 X
}, S( {0 C# N4 j) ^* q+ f8 R
//结果返回 8 n9 n$ o# @) b; D/ p o return html; ( l7 P, H1 @/ ^& X }1 S, `7 h* D, A& K: M
13 U* X; n2 M4 `+ p$ x# p
28 M8 h$ Y8 D# ]% ^4 Q9 U
3 + l& g4 \7 U h6 ^& m2 b4 , E* ~! U1 Y4 I- f3 N5 0 `+ H3 e% A, p: {- \66 A7 S. S& ?. E
7- j8 d' q$ E. }* D h' `1 _6 |) p5 {- j
8 0 A9 P$ ~7 r2 V- ?9' N7 B, `4 D1 R% j& I& X L
10; X' c1 N* }$ r
11: X! R) J ? B: T
12 - H* F# t7 r! [$ S138 G- A" m( H2 |1 f# E* r% I
14 # s' F+ T3 I% b6 g) @158 W5 e+ Q7 e4 e1 e* }3 v
16 6 n6 d2 ]4 C8 y" P* s* d171 `7 B O2 n' a Y
18# @+ `! v! P5 x9 K3 n3 W+ j6 v( u8 ?
191 w3 ?. g( m& F8 {8 U3 F
20 ; Q9 N- [9 d& W2 E219 }2 t/ @ w5 ?5 M& C/ W8 g) [/ A5 }
22 , Y% g1 b8 E7 Z23 8 o3 N2 Q3 Q/ [, k* c* P; h6 x241 A" Z# Q3 P% P
25: Y( K9 W _1 v/ z4 q( N
268 H) s0 b0 L7 b8 \& l0 g# Z% ~% S: S
27 , M2 p2 |0 y) \, a8 ]4 k5 ^28: b( \8 t& F4 l
29$ b d# v/ B6 r+ U5 L ~
30 `( ]8 ?, m! M, D8 x
31 ) T- T6 \$ l/ o/ _# p( [' o32 ) i/ r9 a' A, E! ~% q B& @0 u( f; B, p$ E