From 57100fe4ab531bd6ed7e6b493910425273439bc8 Mon Sep 17 00:00:00 2001
From: "Yangkai.Shen" <237497819@qq.com>
Date: Mon, 30 Sep 2019 16:02:02 +0800
Subject: [PATCH] =?UTF-8?q?:sparkles:=20spring-boot-demo-ratelimit-redis?=
=?UTF-8?q?=20=E5=88=86=E5=B8=83=E5=BC=8F=E9=99=90=E6=B5=81=E5=AE=8C?=
=?UTF-8?q?=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
spring-boot-demo-ratelimit-redis/README.md | 296 ++++++++++++++++++
.../assets/image-20190930155735300.png | Bin 0 -> 349453 bytes
.../assets/image-20190930155856711.png | Bin 0 -> 302433 bytes
3 files changed, 296 insertions(+)
create mode 100644 spring-boot-demo-ratelimit-redis/README.md
create mode 100644 spring-boot-demo-ratelimit-redis/assets/image-20190930155735300.png
create mode 100644 spring-boot-demo-ratelimit-redis/assets/image-20190930155856711.png
diff --git a/spring-boot-demo-ratelimit-redis/README.md b/spring-boot-demo-ratelimit-redis/README.md
new file mode 100644
index 0000000..b5fbfbe
--- /dev/null
+++ b/spring-boot-demo-ratelimit-redis/README.md
@@ -0,0 +1,296 @@
+# spring-boot-demo-ratelimit-redis
+
+> 此 demo 主要演示了 Spring Boot 项目如何通过 AOP 结合 Redis 实现分布式限流,旨在保护 API 被恶意频繁访问的问题,是 `spring-boot-demo-ratelimit-guava` 的升级版。
+
+## 1. 主要代码
+
+### 1.1. pom.xml
+
+```xml
+
+
+ * 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 + *
+ * + * @author yangkai.shen + * @date Created in 2019/9/30 10:31 + * @see AnnotationUtils + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + long DEFAULT_REQUEST = 10; + + /** + * max 最大请求数 + */ + @AliasFor("max") long value() default DEFAULT_REQUEST; + + /** + * max 最大请求数 + */ + @AliasFor("value") long max() default DEFAULT_REQUEST; + + /** + * 限流key + */ + String key() default ""; + + /** + * 超时时长,默认1分钟 + */ + long timeout() default 1; + + /** + * 超时时间单位,默认 分钟 + */ + TimeUnit timeUnit() default TimeUnit.MINUTES; +} +``` + +### 1.3. AOP处理限流 + +```java +/** + *+ * 限流切面 + *
+ * + * @author yangkai.shen + * @date Created in 2019/9/30 10:30 + */ +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class RateLimiterAspect { + private final static String SEPARATOR = ":"; + private final static String REDIS_LIMIT_KEY_PREFIX = "limit:"; + private final StringRedisTemplate stringRedisTemplate; + private final RedisScript+ * 测试 + *
+ * + * @author yangkai.shen + * @date Created in 2019/9/30 10:30 + */ +@Slf4j +@RestController +public class TestController { + + @RateLimiter(value = 5) + @GetMapping("/test1") + public Dict test1() { + log.info("【test1】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } + + @GetMapping("/test2") + public Dict test2() { + log.info("【test2】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); + } + + @RateLimiter(value = 2, key = "测试自定义key") + @GetMapping("/test3") + public Dict test3() { + log.info("【test3】被执行了。。。。。"); + return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); + } +} +``` + +### 1.6. 其余代码参见 demo + +## 2. 测试 + +- 触发限流时控制台打印 + +![image-20190930155856711](assets/image-20190930155856711.png) + +- 触发限流的时候 Redis 的数据 + +![image-20190930155735300](assets/image-20190930155735300.png) + +## 3. 参考 + +- [mica-plus-redis 的分布式限流实现](https://github.com/lets-mica/mica/tree/master/mica-plus-redis) +- [Java并发:分布式应用限流 Redis + Lua 实践](https://segmentfault.com/a/1190000016042927) \ No newline at end of file diff --git a/spring-boot-demo-ratelimit-redis/assets/image-20190930155735300.png b/spring-boot-demo-ratelimit-redis/assets/image-20190930155735300.png new file mode 100644 index 0000000000000000000000000000000000000000..2018be762e578df7541f20d993dd6cb8469fcf85 GIT binary patch literal 349453 zcmYJabx>Q~_dN_0Ee-{WyBBv2uEi-7Yl@d*!JQO$cPkX96mRj8;#S<<-Q6NT`gy+Z zyqU?FDCPl~>=sYgpRe2)Bu1Uj9k>uR<^#%EH9O>3GY8tOmuMwsLQuk#(?F%E z<$0GoBc8+Q7thFz_OLI0;jDXuo`26dglAqQpZ38w`qFTQOjjIpX%nQF>XSO@TiKS| zFZ8MHiXZ>77SCYkXb*q7SDf>^bOyn#$|-I~dW;?AfvV-#jhutEE4!79WSwA#o*_hmB}=o- 5`VrRRSgU8mD^M_qvI>-nbhfUx;+7Hm~&?{yQbvM7S4+dnx^HzU;Z zc&u+u^30MWKG8qe*@0AKQ-|Xlzr3*IP epWRRa`mSpmiysUt^_Xne tJ>ieusHb%U1l-E9wLy^M{MOp~{*8+x!5f*d1GdAKRa$1)x72bk*-{ zNO>M!CNvN4ztN9QeML67lL4f^8p~SihM#J{iN7c7?c@ievh*h)-bgBR?948$Q(XW` z(3TvIR~nROo#*KqYD+M#`Wy4(1;?bWBy%GVUHUIxF6C!raEh#YGY =EytQTc=K0m(BVQ2aqxO90B{b1Pl;9d=BP zi!6lwf{;u!--7F1!*Vf9+rwxea5; Cth$h>8~5tECq-*uBV;GL`wlVwt$QOB zloR`h^es&X5hi4RTJh^zyPdi=r!QfE{C#&sLh4a !Yl*t8-W(i h#xy8CpJVEvY zwe )+(RB%D+y?*kkR71691`_9(stvT9= zI>&g}QZKgp3RkOiaU;5({Ud{!d__T7v (15waHwomR<-|NgIgP7^w1x^7v zkYNzNQ~swdcXBY* T2kk7lW~e|3qt4dMC^@hk#?7{cHgPJ-j&o7iQ35E6PNcY z%(!Fs_l&oTWe);pXs&F=HM2ossMA@|wYk)PzKPtMdPSc9N-p5O $ z3ULM^kpemiNusl9+(PaGBv7`!=Z4jR%1)Z4vsFCP*C_cO?3!%*ER1v|>$$=8-+W@M zNW>po6luHgL4U-eUnXzjuexnpA`53fm_e+~H|*tC($_^QkFoNZmybk6l{v*P;Ab)L z9}+tM5toFRQqgC3Q1IXFv8d5!;?C! V(k`}UdFffH#7Uw=a6@O`0 z&KmmoC|>@Oy;%BKi~E$HR>Sba5q`GJn=!YK2zJR2TRgMyO;3DCXbK7=-F*v29Rs`M<#0qvRJ_mttpp;Kaj^?=7XwroEZF=MNT61O zl}|`WHTw3b0wEQ}#r?WYA))!!&~RqJ8T@3DdX}gdL)Nf!-`!ctnuEy1aMZ~rV8d%n zK>Oto3oT ej*>_!k6I u<_wQ!jeVUu{j$Gq@mYBLwZ!LSTtS=B@-Kp1TJ4wp4X)qA? z#tHeEkT9M*I3Cx@YgB~kq(57K5WFCSEC)3d6q=L5&wek6)k-CKpVLobJQv_m)c z5WEcc;}2z|NfI4TD}Jw}u@4^cIT&F%ImYYLSCL9@qRtTAI}zJJ#0_u bDF|M&uD5Q|3GNDFWfrOx$iG|bjD`r^!)8u z&%E!^Nt2OOV|hi7h`L7;Kn<+FxVRk3#^-=+#W~w6iKtVMg!(H8U!0S(+fA1z#j~iy zg^fv$aA_RA??;KfFV3YL2arn7t?d_-2r7LL&x)lH(B0_izPnBs0#4FeNBT+nkAc6! z2NwZ}H-OpydR6}%2On8tvcMW}i`Csu)qFJ++X|+u{3#T7hNID82A3 qj-HNp}HOw^uR=i?QSlu3E|;-8@(X7MB${g$sDpCU=(AQJX7}?7XqivcW#w zXu); HzeVQzUBQct?Gz?-QGS~Qg; gH6EPQAs6C)9Eh=0a9 zEVhr&v?o7h9O3{ETXju-mO#&@%6aIN8~odeLO+qM{KWB}`>u$XE6GoBzcxGVZPhXs z1>A)`g*2#QQ_&i4IF1dxs2IqJZqEX79E47U^q(C%5P7D*tUTBONmiWuH2|{WV%nhU z{O}eJj3O-Wr?S_CX!o_ro} L}}qTmk3%*&Nx;kW7}y~-dRo-yQ^s`5T0 74o04GsxGvY4$?41ih3akEC8b-D2 zRHE=NU2JQ>2d$Wt0AhP^-!Q~(s zCP6PG?J_STewD`(O?=p4I1f#{-ar8aWP4br`r=nH5HrDOm?q;J8p&CZ!oGP z#5EYo+PhKLPn!~b`BL)LSPBNMf1*|L+xhMenBUz=hS!>3e)*BhMK2j+cJEsUjAXR_ zG*}nh+2ON-dv?g>t%UPny?ss00r(ZxEI=A~)6$5@bt5g*+O-!<37EGy*fIP-D&D2f z&^oXLVraf7&3Uv%5a{$8Xs@A&zIQ+!H8-krEqR;Ci>mzVN&SZF~>tUkwG={ADAL z-xoWYt)99#{66U{hFMmY0rb^b%8&18vsw7X?*v5^ wiV4ioxP|oOo`8slS1WOG*YwA{7Me``mvr zGEvqskK};;vGH8YqR$rGM*HTu <`8>qMx2_ljnt&T`?zZwFS@4Z* zMr+i)ms5~j*v0_z;&mguz_ mqzP8v*A8qs&RCt>MY tI21gO1M`)!3 Nk{$?mss-Bt#J}Rs0mKgVU-!vz&OQ|4*L%&kGjod{rYtTK)>{4RR~f zEI AI!IRd|=TR}zGc_Em~>b3$V;eGz`AqH}fJvi7UEdHm&VL&@e)SRJ^N0OaV zxO$G|fsS7137?$WEYIQU8h{glnL(3mUt`81ur4Wk`gdeFiCI0JE?X`_+LRTRO_9Tl zZ!+*Gt!jg)xAmI`e**r1kpti{(b3wh>4k)8;Y(pXLu-Z~d{mdT#KC7@0V#uetek;8 z50mQL>4k+TK93b^!Ns6ppREMfvAfgR+l+3kAdDsO3ja^_qQZ|kCCjEQ+sVvCN^PE} zi*b89k=|4D<=22}giZ%8>|vh~lKk86j(wTD!DhhBtFr}Fa`y>R4k8!~iP+u8hnCnD ztFzN=D-V?aNpZJ$Iv8K4)m`&E;&L4g4jWScD}ZVe_BL3cXDsj*e{yz_g%;c{H!5}J zuTV_By{Ls!iyv~6P@+y)hh{Qdi+Fe!ez~>Lelb3VkbUlx?KEf^4)$i4GW4>Ib)3e* zi<#jQ=;!n_@Ubs?J>sGY|E4jANA@!nA?xox(~lWDtWEyVaaSN^{H|ff)f6m?9;WcJ zU?fvGr&dV-d1;)Wx8-Vgc)7(^fH*!emPSa88hf?RKoC{4EE|is593DE-*lB*;b#`t z+V3Z)s)&;^Mtv96I_Q=>KpCNAwYCadId{pw&d0GWeB1uTeRxDf-P3@SPcBP0i{JWX zWAlT_!6)Y_F2l^~xc-MYk#+atlgN)pc@kd)8nqbAK?zG2Z;=8J7x{3VJUtbG6Yweq zYynZz4*lMk=M6qP^|QMCSz#=)1ioO-P?I%Jl7wAPabWMyyWdHz$ BLZ(6+v4 z@)XsP;GFr~`_g*f!@bT`?Wn#04b^Q4y#QoON;j{;13OOu;-4F{3VD$ (**?0MEa!ro-QwvmNAnl1fPjyS9_pGicK$~D!`!1 znE832Gkd$mLRoB9Z+%26>T%+7v)NWkp(%JD6EWtQ0;lQt5QK1mv0psWK2b?2s&ORv zz_q3k`zrm0YG!i~CB#>#Gt~B<;Y7_W4Z#AYH*oy-Z M3i*f#L`%%?Lt40 zkaqTL^y1unib&Oq?X)L8oKj7c2HWXtc1rL*TE}LD2uf7aq@E$$`{=N8*t36K=a;7* z&pFl#SN3`|f^{AdFafE#rTm{2HKpM{JMWI<84bmaGNSf@Rht>qrAIJcT?V(j;@P)& z)Y64bUGq#qeD{H=v@W#L1D8uX(?KcHSS7;-4mH|eB1yKE7Oha;W+kTzvUi!l*$0-s zTnEfMS+KUh6#mlrS-X@9-(e-3_?5mYUYZ$*^dJMO4EY8xiX^%isG&KjCh!mx6s9K{ z)DdO==tlGZD0}OuxRx$`7}ww-xVr=mE)BsYxI>Tt!CgYIMnVYg5Zo Izhtk=0|P5b(e}2dO?zFdhE@q^{2}Clvg$%*MlHT zEfqB#MuOeJ#YCEfXYM!`-pgR4h*Z~`TpI8dJh(Ilm{pl_*lcwB`@SvdVOc3o<4zf- z9kBxlH47?iU~?~WLOWza^uon|!59>uO``AoAi MlpTU3r&UF1mk&SP&Z ElHT;+RqJ*c@VhV`I`e4FvK2gIV}jKO zqb|oJ)PKn&C-daE9{1^WMvoOdL=$Ej)Q2y;RA4~|l~F*(Z@5L@@lvYJ+3Te0=2j&R zbG4c*kDF|Fs_Xd|?0D3+4`qpX)Q*mZv+kA+Q2d-i;3ptZV(vbV@UfqFD0jZQ+#6RY zN&$Vfb5$ Cojpc% z9$tQ=9lbPrmi6jwx6=MFcV}{H>N7iOloUr{djUZq<@f!Xqe7m2u3jI{&r&?geD-Vk z3|q_-Dj_H*gQo@$&H3O(v+bmzq|zUT>-vATJD# d>%=UO^oY*NXNlphxGsPs82i&Gf4u}~G69o1{Wri>v0B6i#6 z(GB=DzUJ<@LTXCJ3LPAq4Og%#o~_Uy#^!Dd`dSS{XsLKOctZSOabfDPs5$I)?)E!+ z`qmEw{k#4oZs;kMhR_sopeL$&v@7n!#V*W`PAsV$r4J9UKYIpqJFW@+Kxqjq4zRd% z_F%2w%7br;;h~=-CK2ZpPz>NH>wCY)jHtyL2&+3!`vWu%HW9dAmI1VKYVXH)!Ei)h z+By#uk8kQ~+(ip0x%>`iMBQb3Z{Z2PF~V@kw6L)qNg*YDxFPJ_e|aw3d7#l9#;?pK zbvB78`-;K#=`YzHEDZZM8rwDJ=Gm34+; 9)fM5OW_7z7f8 z)ZgE9tzhE*QgN4U@%i-qfP8_%pX+BGxxgdhM{kh{FZ3zwDJ)OGy<@aMktO)u%b~Kg zjY#aNAex5?p*v+WB*{hCX(%k9acw=x;}6@5g$?Wk!BK=ai5DKdfybf$P#i?q9{1K3 zAQTQZ`=Rq)Xj5TC3m6vuOXkL&=9yXcSx4r8PS-D18GpEf0DIFwgd%Z{B(?Md-KP{R z+vch=ox-#}y_|JXB!jY!+#43Yp7_IKjNxaFjg!+bnVqf(_jdd-q~SC(7XGuY&neR| z(U1F-A9}?fZ)uWc& -nTxQ6(uf9t#hM1Rvqs1{+ffV+J4~q0)Z>SUO^kZXE>i!;dC%T5ehX5ZRS_0ZkB^s*%%l} zVfUr;l|}lntI?%x2DvaPP@Us}-LH4lxoYD(BU!({He| z{oc-G28FuW=jLjeqQqlNYu(;#u07@=f&@5`C*Tr}-@f&wP``#mXn28eNgkKGG5y^% zzQlIir$d4@KpCtTG@za-NxWRfgW qny4Q8jgmXj@O<%B@FQP~^!Rrjo zCxE`Yo1l}8veQxso=X*U+?&Bj%-4GjAi`|%BJH04q#+%SKI1QXV%*0=16v4`K&UM% z)A(736L$&@&o4yYNRm{j__9q(k0gK?NYP|v*Zu4z@j|NHxn<0{Uo1HG6(0B^c_=76 zhe;D7E*AlIMK;|P&S3%+2A0&H3?iU|KG8L?*(a)ZSCq|7qDH9N2%IdKewx3&j+w$W z9UU3bxI>Y9!0WxUeh%l8z!Oe1!(cMktn;&F0 ZVNlspshnuyZ&-GiSB6(Fyh51f+tS>-3r5hAT^P z?#J$E8HpOlI0!&$+VXup*m*;orDHJYBip#xXcK3;w?p@)j)a~uVC63A6tFsZ2OUbl zE
OC+PANcnE9) z>D15oHTvH77JJdHl+*fGOeoIDG{$dnc_pKY_-3^g5I(0^k=r+!J9&1Q*jsvD7DdWB z;Lxw~Tsb-a({NXTBKos8_=!4raXH~f83(_a9^Ojk&gZ7%VP_^pZy-9&$f{uE$}OPr zAsm$1e6jryRb!Ekp}FKH3#XMDI^1?2-xc3~{t|Q4aZ>Xloop+X=Zqrk`(ki=_dBS% z`CXPQP;cOJ#bS>JdJj-Q*}&0%1$21tu|>)+BQp7gi?55pe N}Cm-Ryy29>VEQC+22-ZNa) ?LbUYZXPfXC~`hR_?NBe-ru`bA?l4eTB zARoJL2S#cNH*-r~MkfeS6x`22vcYPKu%niAxy*=YV;=Qu0Of%j-$_X^-w=>7qyn!4 z=)+LMH1ASAjch!cZ4^A+cuH)soW%$ozo<-O)z#G#d2j$Y5+lVr-yEFY$yVq=G=rT3 zdOr~hG&Z^-EpYOeyqbmqD3fC10Jo7RS3NtYI(RKmArYgX>^OPe6s+4^h=>y8?c?*n zcCHaItWK#Y_ih)-)ZQq=S)u3zkB=97xU{5%20a&Z)Rq1{(0<#{c`zYuB*N*}!;-yU zAd}_bV4p1bK^ujfom$yaP9Z{O=391>3uw9EM8TJss_Qc`reCe~xT*7UAzMp2z9i-5 z7YBS|Yg-;Q;t%qJ7F0(1NO!VR%^*F1m7Vodk4u-H9;92)GY-5S+=u&PTyG-$YM7-a z{vv4aUVWL${I`#)r%KtpS%9wOz#W~d=s`n|;ox=gkK@I9tMdZ6izqXg*3lcr9ILYk zu_Ibw_5J+2Zi<>AtRECTF8y;F&&$%#&vOL)N}RLnldcO?>1@>@2I0N8KKFaJdv_qs zN5i>Z^%pS0Ml%9>C90Qo|Dgq7!junRL{Pw)^o68wV4<6`X`X5^%=&`F)3u&U(H#O& zS+u*1%LJTY{XUoj(MhFNID8mm5K% I&PY$iI`3Jwb4eS7*oUInsI!?tn0 zHZ{e$@$K^QgL+$S@(21ZuFp@;rpY>5h{kdwo8NjXnwmC+ZfD@H#3BD4CPJqcC&*s6 zaWsOnhW><8zKB#xrBJT#v{h*kVZYhTNRW_gdU2YNCEhkfysh2xAlLuHT6e+C3?5Ne zWL$kU*L|ed#@6>;Df8EQYd_|?d*WX#5`XWkKOv)dA{JKos>AJyF%1ZS!&gDf;D1q$ z<3>SWSb*~JP7PMu3LoQ0H6~b|B_@^Xom+T4hOvbQ584k4<~O2 oRZ6 z&}~YrZc1y~p5Q*Y1)VWG#oXmhzQ3-4HOmWZtN 8u9C$y2>ofsKqsS zBu79+^5KrbFv^xjfncM%84Qo`E_mA{nfP?0x}RL^EE&<~Jn2~ah(#rJ9(rw?hu)B$ zJQecY^{1H>hUXgkAb~nC)g78X+>N(1a>MT{y^$xac{kE4I53r~9>h=3-nSa@BSa3{ zdiQwo;ym1T-gll0FZX0K+<1z!6Xx5Wkdh=T?*F)`*0Yz&&Y%$Iy&Sq8xOs!_q%&gj zC;`oWevIXPaMSf+bBRU_CWhR>;%_$K)*7nx#m$lN nJi^3U&%Pfw7{Se+s{}tJI0J6Ce zKJ@#Tyge-?9aA9~N%(kseJAzFVuw?v1Iv0VD2=;JbNQe{16`?LVzsfFw^rwgzbWXv zZ|i$;@)X|v5D*&V;H(zw9IkEZX24L6{|;Y!IX;5KN! 67$m!Fa%dLr>jhyT;AxKc3R-TBGwkvi(($nN_5{5;|l zvCy-Gk1A`xwU?J8=9Ek@vPpcLmDEloHJ9Z^Y46aUZ*;=@y{tk_Wn2dPC+}U4Tk81i zHW@9pi{PsO@ufh~Ii}E4%$Iiu2W2FD7nF`E4Sw^^A|` (oz~zmktxlIE?SN)<3!olIt25hzIsL0t@2Ncxi=l8hYVBXK(Z6P~(BNkXDykz) z6FT@OGpraa3zv#8E5a_52@QI|2c142BFh6S;4n@D>JE?LTkJ89T>QtpF-BCjcPAh4 zz~8$S3e}TBadnXmPQ8={M+~*@2afo#2A)^?>4hNgcnb7Fr@kW=4CbG``7MTGf)u^1 z{DBK+{Bu>y6 _iJ9OEiN z*kBWH&ptM>DbB9_z ;eV?2%aE50TJwdAFqeGw6TgjwJ@dr(c$x%wlq3VN!g1iu(Ifkf)h zso^wbB6`~6m2WbV((wJRpEnVX@Q8nr@m@5g2v(;r4X}+GMtp@j>>##K-34P*EqN#X zqYYQ=*ED#4f=~b1yQrwh!joJvmmE&xCRGPz+}uVqfg?HDueDx3riQr{o~9PNvP;1O z!0{;@dOzW@hZm;`9FnQuF88`O&`;bI6*kN|A!tK`sS@gtU8Prk=V2sPqy>fWwG!Hi za3F-}tLVi#r4#6aAZ04?P1?tsc^!rec0Qi$)iHNdpsc*y`#xUJ$b{UfQ=V^ =8yK=v{zRK_pmVMz#g#otwNnW-i`)ub=iFNTGkWMGsN60fBwB6`=HbIyukTx zTR;%;`tD{+3!Y&bWrc`*a(Danp5({px 4^ z`x}yp55=zBsQ`vB8f@%!tJP!d$w-+~Uef$i;ohgub_;QnQK4r3Fu54#5Y8p%m#o43 zq2Xql!A=Q^M<|zT8@)R%#J#`=ysSvF_vqKIKSq&3&s#sXMTTBr*+nmGY&-@=g{jYA zB&A>Xc_7^lHs+=vcZiy;XPmKxrg9o?9%A2($8}aMH`Bik&VNEF?K33$R6WOVWHl}v zgrpfQ|3slL*WjU^^P;9(P_#1q44oJcD`WGpU+BxLmB5~@CevPpJX1jvY4o#60G^Ms zP-@qGg3hmyN&2v5s?pj*>#vVKX%X;&a@)Lhb31O=IySI@W|o%4jsLDFZEq{+yFA!6 z{5%&|5ceqsm>H~7odLmNEE tjV!72j3k&AV3d}2$?N6sZLMYjmk zd8EC1CUC}c7^noUt55A{5B~zjnhplOP>oL9w9Fb@(oodjcj#i z>pe%ql=|#a8a+zMv`50##U 9@}#Ov4>~jc4ae?JXr4EU*iHR;TNqB*rzkz1;88zu zpSQlp8<$sJZ$)}`Zdl+ 0N|A{`B=6vf^L++bszOZGS7a u!crSidV5(rj{SbP3__C;Z{C!5biBwj$x9WUuBIHN2LI+|`FcILy{FGfDPLzXs98 z*2c1xu1h=}c6&B1ak|0^1wlQF{lB_1#Yc3x6&sYOrDrrisRQ4?59?7^QBR<7`EEE6 zLsc=RYXV+h%a>VaiKm5!@LUldZZ|G2Y#&LU8*yTfrE{Z(y~kzp#<2YctRsfibRplZ zJTMEO#&Z9t_a3z`uX%T*oCADLg930+-ui_PjJtc`2HNQ~JZGDn9y|fa9^>_Jlkpi0 zeS3FHUEV(@AL+MpXV(0dN^yN3Wc|~MN!sNKe9}0-Gam;t3WOe+m0xB&`vez@a)dSp zJbF6;@n8AKcpZgBiA9u_h3`Jd-K~&^d%+&`I|puFF2JJF1wiAmK*g5|t7So9> U3}&@-)lfV8z@6=W9m1An<;Hi9EP6EqND(uU4<#uA7q>|V@V$YQ zNG6c-$`KOKda(y*H^_a6khcrLk gSKJ5`7}<;o)(ut*uQI^QH&= zh-@KH`lOVaDGo@;&8=Ck(Kx$NfzhYIG$qkhi8#R3vJKI*W>u+UuHlv84BfvZqL+Bo zF4b~*U&GO WWAyj$pntb8sP< z#cSK#e)(Gr&Xm>Jka2rP?=SEB{=PqL=J#P!r%5q8&=Bw&>c`b3mc+x@`LGuEv!nfS z#%-_T@%MCiOXc`0*-!K@2cIoXeFlI2T~ zTY#A z_o5)ZM?jb2Z1qkrDk;9y+9)k9{b?nRQp!KJA+z&ccle$pYrHihx3o|p_Gb-sfR26L zH97+yWWE#Tkc_vHe^>bR-I}CQXzx_}EmTULRvGE&`i#tt{yQuW$K!dS%g1o_Tiy~= zw^?F${4s&$CYPj-18DD`s4?S7-sO2XzI>}M9`Xau@IlHxq9ET{m@IU~@Ad{&;`K38 zFFlh*EU&6gc()dy4$lR>$GacK5a0m4E 6^i&dYKZE5Fe}43n`Q;ysO^nd5 zPsR|2=ugLM&1M~U$Nb6NdeR36e0EcPtp298uSbSEuAu7oHSdx^u2FY oK+3|9{kknGuJA7w$%30Es zoh@RHBIJZ*YQJM%I5+d1inL^twe)#x6Yl$)Z#Ko77+G(9SeI7M4k`tADu=#0arh9& z!rOZ ?#FDIkPLJE%aDN0`m^A`Ples6l>$08PP&~sinZBYlQ-1 GRHlINLE z1GTm5MYD@}HsPtLShdc_6PFa=vo}H0)WsFLve9X2g=Fu-NE&LHesk?h CONwuY4^=6IWPRP=s0A5JM2nN^H;GiNRgl>GC+ek$5a% zn7z9-6pYOkLdZEo0yX4ebvRpvZ+>H7R!oB_h(c`K{Xf@OvlXj@!dK)pdj7>{!EpH# z^DJ+edEY<$Dy3QwV1Kou3KtTS02loZLS~r;si^_(6op;#Pv$`IB!kE9+6yj?{f^qt zq*+=g(E+dKHKpke1{BY 5oUkfSqR&Xmz{<`Uo#7oBKA>y!dz97cF z6@D>U%)^bx$@*eurl6Xln263bm>}{R$BZnNUy_&xb?Ct$1H1qh_4v>b`(5R*f+nel z&frNOeNykxj`qUP1cUR)Kq&T?S)8&XKf*EdPg*n7VZ;6r8)7{n)sa~h)z}WmIn{+h zYj{6@C!5WPKQ9kek?Qaa@`NJbubxH7K%{FCV=XwJB?N*vTUF$3VrS;+q0}97#R3cw zXS#+10!LEqxXGczRY{8N&JXp}kb25X(YGSi&}u@n%)-uY^o<1t#@flXbGVevA0`01 z>MIzUhcYddh{Jy_XwqQ^C%G>2KnFY&iBFd|-F{z#1vZZ;`;A5X%(l!M51MPde3s8h zrApfUAwgMGRJ3gS%lBa?J1uPsZ2Fp|Bxx!mNj*RYcGaM>9c+mJdtd-`)AUR 2*_d|K#2co1L>a6Vely>ferNwHe&DU~XCeOlJE^j8_Xs%5UewPQi&P+{Q>ONpIka zI)u(cz56(Q5kF*C4nHw`YRY{sOR*UE77j0Z5{lDqefg|?F#HhiU&}>_UkJV!!WGu3 zHEk5omT@)s{Ir3nzflH(MfmZ?0d1);R`_dRFPeBVNV&+BgMQXaz HA(!BF zfe?Cv44DAaFIcDdV&hjT%_ra2G0vw{xRpefggxd|S0#(vHft-pyoGRZyef^F&nB@n zEf;Go7T8fvkf p@q0M|YR;WoP1GlQg7QYs&T?U^r&rQYS4R#$`x)B?9>)3C ze%X#&H_tb?-XB#pONBD YlTqQb{#oM}3d#tB#qBq#9Jw4A$=^ z2wOfF$$W= |6GsW;uupVfB{3KE@ z_=Svq9mrw&^80aQF&22EC+u*9D0pP8^<9nl!;eBfzcI%hGSPGY5ntZ11@8A 0wZM5gDI>Os$ER4(b#N*zr8+ogh|&>sy05OzQI(YU2k@Fjc!v1C?+(xMC1y8) z)gPoBTG)`85TobEZfhhcTnBCx6i?O!do~OHORx=|Kh_0q5^Bq)$}$vAKDwBf9j<8u zrH1R*pr3UHPz5K!dBRFd+RPh{BU!Gep3GtOpqT+oga>~#pl^R|k@#6P%+5xHfeQC$ zb?1v?><1~l09*`*&&uKvqp@cX;%m xE~=|-cNC#lkgAFDR#QPw)>sGXQdl;r$yOdEZb zup%#+lEW04#oIRJC^zwPKk_rAO?)e&KlQca==j`L(r|WiiXHf3C5D}cb) Kk{Hk>RC& zRwqldd^Tf?SbIwj)d_{J%) xRr;__ngh!=WFYYQMM+<@jm z)g2G^i;WH}zt%8^17)Q*&`H_3#MN`eb-jBwJ{rrrLC((}xWT>;Vg`;Bk}VteHGU8~ z%~2_v9Fh9nA4^^iQd=05?Z2kuN;SzTe-7jd@VcT%Tg#%OZ|v>t48_6O6*>M8H{qaJ zMGuDx$2FyJf E}a)NXK}B=N2y@w9$;|qL4PH5H&_EEhpL;v$boW2je9s6_&`7TeZUa- z&)m}iN4|$y{gEk2Dk%ZVzNk|~E }l~0O^sn^&hpCi12tA|0) zotDk~?KrRD3s#67$uHBqsiJVx7MgASNJMJNkr$`g23;xkMygvIT4Dq0@u|lqcEP;a zQ9?mlEmS!Axam>PuH0yzoh0(SOrieb5P#@UoutRvP$x;vg0+@{4L^dXD$IunRp;4Q zCjDpvy`5--n)}O>#Ef+6_3+1$q WI@N^_1in*F5p- z;%fp$|HsFW@yA#KH5@nz;>KqR_IFnBnD@T$tCn?3p&WSEOhHH}_cH{q1!Md2vU5tu zMw2D{TI?-sCx8O3?};>azjUn*mnHt4c+e>b8{@o}K~BZf=0CUi5aK8f*$bbC2zUG0 zZ=e25;W(>pmxC+MSi>`$_WIx4mL-5 UAVbad;*3+q~ zH{K_|sx)7xamZL 8<_FH^JA~S`khi&?M m3-iqVEiN@d~O^_tR4E3r5?~P@`>2KVP zPsp^`LnG1cXlCYTL(yL@d|}h(>e#0b8|L#2`Yh_j?kXD6^upE?TMprbyW3k<-y-Ul zLz-E5pnDuy03bx`J5cIsL=PniP1_IBdVfq>(EOr26oFuDco;GFdE)8HdzST{kMn4k z!;QvZ_T4 Ysq z+zXn*drC=vQe&u9kKx;hO`>ryTuVY~Rl!p%O(kC8J&|>3P{)WLJ^{}HApVlAoX>ZL z_1lTJu(L%4wft2Veu~O^a^H!QKQJv4Gs)KyhC`*c03@yB_k5z6?-5}oauDK>gc&UJ zx%Hr&P0GGe#lQl;DA-n&Ey{J5O?GQj-X9^*N_ShdR@r$tN<-dL9aV2~34L;hVtncP z%KD+ 3QUD^tqr&b}4TZnL>cg%H6*wOU$l(9YQSxjP|P-!7CZnexq` z4|wl`E$zp1Ruhx58RokVBK=1~&lH}}AliH(=)QB%aQ*NT`)3knUT^#DuTqGLXY%G9 zJy57r_6~1AF_yFSEg7XI{!X|7GIMhkIq#dI3-rQX6eeQp3qbZq*JB45&QP}$<4E10 zA?WE0r&)s;+(CE1O?|hWruGyK#84x!rw@8de*+vcSKU#%;8&z`hO_AdV;GHrZ3_*0 z)LouN#DcegpvYu`pY{V_(Y7wq{}AL=PzWCFECEY>)2Ls*;(mfT-H9}+r0cz8EIuX+ z x--pcBU|qmuQfKPbD@Ra5t6dVj~xI z#Y>y0<0~)H7z=-^u(2s8tz@YV*rsaKetz0bVYGl?kdd)m^hJ>KB