From c3cfc2d4aeb1ee0e136adfdfa6000bbe304a63a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E6=83=A0=E6=B0=91?= Date: 2017年12月19日 00:12:57 +0800 Subject: [PATCH 01/20] Dynamic DataSource --- .gitignore | 19 ++ build.gradle | 34 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54711 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 ++++++++++++++++++ gradlew.bat | 84 +++++++++ .../DynamicDataSourceApplication.java | 13 ++ .../common/CommonConstant.java | 24 +++ .../common/CommonResponse.java | 50 +++++ .../common/ResponseCode.java | 22 +++ .../common/ResponseUtil.java | 103 +++++++++++ .../CustomHandlerExceptionResolver.java | 70 +++++++ .../configuration/DataSourceConfigurer.java | 89 +++++++++ .../DynamicDataSourceAspect.java | 57 ++++++ .../DynamicDataSourceContextHolder.java | 67 +++++++ .../DynamicRoutingDataSource.java | 29 +++ .../configuration/TargetDataSource.java | 17 ++ .../configuration/WebMvcConfigurer.java | 35 ++++ .../controller/BaseController.java | 29 +++ .../controller/ProduceController.java | 88 +++++++++ .../mapper/ProductMapper.java | 28 +++ .../dynamicdatasource/modal/Product.java | 42 +++++ .../service/ProductService.java | 96 ++++++++++ .../utils/ApplicationContextHolder.java | 56 ++++++ .../dynamicdatasource/utils/HttpLog.java | 62 +++++++ .../dynamicdatasource/utils/JSONUtil.java | 47 +++++ .../utils/ServiceException.java | 24 +++ src/main/resources/application.properties | 22 +++ src/main/resources/mappers/ProductMapper.xml | 42 +++++ .../DynamicDataSourceApplicationTests.java | 16 ++ 30 files changed, 1442 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/DynamicDataSourceApplication.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonConstant.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonResponse.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseCode.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseUtil.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/configuration/CustomHandlerExceptionResolver.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DataSourceConfigurer.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceAspect.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceContextHolder.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicRoutingDataSource.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/configuration/TargetDataSource.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/configuration/WebMvcConfigurer.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/controller/BaseController.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/controller/ProduceController.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/mapper/ProductMapper.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/modal/Product.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/service/ProductService.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/utils/ApplicationContextHolder.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/utils/HttpLog.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/utils/JSONUtil.java create mode 100644 src/main/java/cn/com/hellowood/dynamicdatasource/utils/ServiceException.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/mappers/ProductMapper.xml create mode 100644 src/test/java/cn/com/hellowood/dynamicdatasource/DynamicDataSourceApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6737b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.gradle +/build/ + + +### IntelliJ IDEA ### +.idea +*.iml + +build/ + +### Java template +# Compiled class file +*.class + +# Log file +*.log + +out/ +logs/ \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..a5a953a --- /dev/null +++ b/build.gradle @@ -0,0 +1,34 @@ +buildscript { + ext { + springBootVersion = '1.5.9.RELEASE' + } + repositories { + maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'org.springframework.boot' + +group = 'cn.com.hellowood' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = 1.8 + +repositories { + maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } + mavenCentral() +} + + +dependencies { + compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1') + compile('org.springframework.boot:spring-boot-starter-web') + compile('org.springframework.boot:spring-boot-starter-aop') + runtime('mysql:mysql-connector-java') + testCompile('org.springframework.boot:spring-boot-starter-test') +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1a958be6420a4dd04b588fe4b31eb5d7ff8748c2 GIT binary patch literal 54711 zcmafaV|Zr4wq|#1+qUg=Y}>Y-FYKg~FSc!4U!3l^W81ckPNrwhy)$#poO|cT+I#J{+5|g zn4p(o_zHIlG*8_x)}?L3rYzkrHvQe#f_Ij2ihJvNZtN&+O z|2GEyKQLCVCg%1Q|1A{#pP^o^CeF?luK$n9m5*-P)!(5P{{9-qf3G6yc6tR5hR1)xa5HQGTsPG$-fGY`3(PpBen*pMTz; ztiBlbDzS-r>kXNV%W20uiwu!4jcN~2;-)3+jwK=xr&{RuYV>rW55Scb|7fGy=?J04 z-Ox^P78~mPE#1}*{YN{=nLhlft$oc8kjLy5tZY$DPEU#ru{YcmEk+}~jDo^bgqtZy z{R=y1ドル`Z|3G8Xn&(FRJ7341BSL&0Dv0!=nUN5e>iF=oq7d}ec67R;1(j*bE@HFHj9 zH>kwXk&WJElj9;$A&pXleHLW9GMl@Ia4CCq)J8STiIB5u`Y)HB8NT5g4&}+T{gou7M1nf7H3>h z-$-Vmq0Kd+&{G=B=gg0v;xh9tExp_15CUNVR-2)&sXE6QK*775-gcqD4EQr)IVC^t zGIpn@1G2FzRY}ZOp}oyakgKpD@9brO9(Qi0Rhsxc*mbBb)lyw#Zd?;u$NmGSukbrk z43g_A!(Xj!>(Dh!rb$K`o?sP7b`tbA!+5^0vVu~*2J1=r^fZ0(#&pXA&~OYr1Yf^4 zVSn@c=e3(qrJ;lqOjGMx{d&!tU;a2RfC+o7}>;kTeMQqk* z7LKHBLYjDS^v^`X*V6$QEFZ$Yv6)uf^&R2wAb@|U;Ws4?%`NDtrWi{7YMD}93N;Ge zX?2Jz)O+mooK2>c>g8pZ+)zuzGJ_0%jh1wge$qok=&3pQ=I4-d`sWtJsEYYG-zJMF z{M*Yvh>iwy$UOt+=2`7582%BRiaC=ly)0M`IkJpj?54YPTtG3Cx>1Vf7U&kAQQjOA zoO?ZhxXtSmA8to-j<$*f(;a9ouhgfo?=z*mb5pyuc_bgxq`8n5i){83u_yygvk=ma zIkcN|^5i*%wrXPWgF&9OJu=_!N+m=UzOC&yAx;xcImFb>TD`FN=e^`1gXIC5iAwZ> zJ%ca&kiF*UPU5ドル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ドルdQt##l|*43;)vyl~pAGjq$wv^TpVzbBL%pb7DCk_oG?s=c;lN4;uMZ;lyjurgp z$PX;}PjGQ`XJjeC;Y0h{?LqF!pBI;Z&&v+>P z;H7bpBN3%KKLzKDQR{Ydo(=i#75#9o$TSgUyP~i9J7H78aJR2a!k1K5&60g%6EaAy zp7y%S%LbwZ)_iAvC3OLb2j0|^WyN3>&oOrf48JOJs>YSw1k6W@x(1OmPzilUo@H}0 zW?zu|8GhcMTuah^$#*FYI%tqsULVQpd~Qk+_PVoLV*nY^;tLewPHKjX^Sz^Ji0BN2 z$&6S9sthy@$~RZl_+vdGc=Q0Lqh@^9XzAl}t&Hu4uk!c!3!e;zC-)gVC9bB-yzrYA zi30A9DGRYn6A&J*t?O|!M~C4uxfHoC%_Gz0Y&u69TB`_rJFf{4)x<7+utgu(wp(s0 z81lM8Imq~FDf?2&ygzyb9+5o7pAH&?eexgYc+#alm8I_E@raRJva1augCMMYMRP=h zdj)_#eGSSC;|sm!4!-x&MEw*vKA2K<@tg(pag4?>p~ZLrrDHzHq?tXsNu@4PN(C~v zkU;ctK-}5>O}S9x;Nyk9KeXnp@`gOEp!gQdO&ZDWB$`_sx|0%$&8Rr?^c}s-4J%o9 z>ipRa`FSW6ドル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ドルe0D8jYS1gP^nyfQF){!sJhO$d!ehG=l?>(KoEteeLE>?-o#>PW6$I zTRtVq+QuLEoOxd@PAv9c8oSFZJ)A(sv++u4r;0BX~1zv?8B!; z=8cKftb~(}@iec#>h+@tc6<+p-o*wjvdx+ba{fz=n`w}4)dve=lv`~y_slo|15t*p ze(C53h6%DXh~-<7ドル~m&un76s~%jb_w5iiem^^}w#=ox0n$g@dl!gl|8yay}8=v@0- zjrdcp9^0N=BE4a^MOsYvUl}~snXO3rV7=27A!6D?w#Zkc$d7W$pHunp$_EtXQfBu=#2;}oGxSXd z%lA?wCJD5DK2d1o6Nm=R&bz%|ApwiaU_m;*-v`(Eox%&=t9`w-ZJoZ1MY$?~7N3uQqQ{|ZCnPr-#5Nqc{}^V=Z)f_3bB>;nT6 zP)JY7sRWaBLUp7ynM|`{f*oo!%Asea8q!2gs=Z;VlANJwg)BJc>(AOy{uCn8{H`-` zCf28&m0SX(R;?esE<^!x;`lpdf}kuejsioaqab?f9jb+wb5@3k55dwobcc16siznv z`V|QN&z9y?;XKd(t(I~j|JRl}y1AR!+y7^~UXIqAFNPLwfYKw|nB{jAU1vS(8Odb^ zMEC+_*dRDq2eGto_@WSI9*z9=P*m(^=L~6;55QKCZIxz;ZMS-qS4AQvhQnFS>TA^J z_n)s?&*fL#O<5cesw69t86ドルp$zqbx6e&etdz}r?`50o+f2m9s$x($iic}i*5hfrjy zUWqI!7>YdtLeZ9nDnVQXYwp&Z(pmO!j;z5VJ)t+DSHTpmghB{`IjB+EFF_rRhn&hP zi6`ui3{Z$p+$$xqW7g=`h)z6A&37Z?Cks@fb`}}Pli6*0)m1bPjvo0sZ^v1g%#}`y$tA_o5S8)~l<%=-nd~d+fz# zQ_Jc*dTy&LBAwbN+pMPWc}w#M1MNd3tHc?v_^4}42ie7y3b>Da2JL6q;XoOJXSgMa zCl=IwfO4Ib$BIQ3vpLDn*c`JI+|WywbO)Zna~#ZUGQ{1FW{u00%KBP^WYn^Ad=R70 zk5sc4UreUrG*$id5YMVtLnj}#D3vE7wQ!_%NK1c3gqy`CcXAyJPKU%j)edn?(yg*c4j--McReGUa= zO-@!)eo39qf+~5eU2~<_mcro9p0b=`q+yyh42*elwqpbijxask!z$}+t6wxx#&gy> z%={!@V>uB)*Leqgv?*( znDhph+y&z5&TxJ?=KLu!8urA!>_;NxcljCnWSkZ&;`gH`Q|#oKib!31O}6L{<``3y zZfumd$nf7BO4B9ES9jRUTreEl!w-9F?#3TCfTS_)S`1Nm_J)m#b^w%&Ftv1J2Ka;i zo~&~AP<)5ddt-$cp`iiytop-v(+jdzf5-bd;{w^lsj_r+qbzxirk_mS7r_)!-|JQJO!ZN?SLZD^ zytaG$-9BJLm4UiS*RG;IV8j&7yx%-m0M2Wj2dVc^aPAsBlK$LwO>&j%yM&P;1tXy` zVCFs!2aKK~e(0f`)eJP-I&(VE+Fw`0yir=lfVS`~(jRgKBn$POz3|bsb31Jw?SGhs zbbbL0*SLneQMz1a(RF$ba>wC(aG;y*-&tlDc+$v@dt=>uMXx=-M{U1u{Hs)=-jRt_}KiL z!p&7@bi~;!mKjVl)cvq-#x<<#l$*ejoulw7qcx8|exhgu-&hdzf80nhvs(27gr<9i zF&jzkdLP2^Rcd<@j_hg8;mu&lrfzwed-vuvb^tgst1w-vsnt|-c#^0t_!hz9*wiqh zYJkMpY4jbdJH*-?d1;1sU8v)dOpzJaYQir&$eK=fa257OD9meKy;Dv7xM~-PPQ%6O z*)^w4NutigAELtg_@Xv~ubOvV5T)zjMF2%^uy!XW5<6d#_mrz}j02&z6{0;%mahyz zQd|u_IdZDNYIio!unrKbadSym)#v?wb5M%KZIc;hJ)q*{)E3?RTEj~+ElA%dQ#GL&WW)<)dpuiqru_!>5Uhoix~TkiuK2UVRh!1fCGg3PLzoSJpR zlDGRzt-}%g!yE~qwx_Nu7$NnnX`)IRz6LK!90bEj4mUfrVI1ドルdcLckb|@9{)rh{_z5_N!*n+0G$qZ z9jGxl#qs?1FSV{5`1WrUe{Tvs(ti0u@?UuWfB3}z-F@qadC($E{d71vF;NdG+Ez`D zHbUgdL4%h_(m+aL!b-AB;guM@PC1z)hjyk(tf_lZ=+TPlRbHZ@j>bU;@>p8ctpP1A zTG{zuRQcCAo%q%{(Ov~wIyyQgiu~G7bF%C?sQz^8x$_4+I4KFriNn7Xp**;J!;{F& z=K#!x+)nSy6^$OXp`_e;hf+U>Zv`-kljhQxB^A@c+?eN*DVT(pxvGRa?%B+SVCE7P z(h7(jPN{oq##@DXBiX^_p%tD8a1WH-3Y^fU9&&^pg;^uTA-lk)0n1az_M7xG;cV#c z+9Rtl4N>+(;g}O~qr^D!(xg9UNtlz4Tv4Cgarw!`CG^qvF>eLfQHwO|6+M$~A3nqs_;ni$akxy4s#~^6j`v|Vo#UsLdc5&~~ zQZO@^NsAS-Fk(`%-!yY3xt_0zpHUEvv(lHLyK}9+GAmo88bK0G@Wxs+j%DI8b6Go& z2%Bl6V?zTT)yzSqKw!zP_w}4tn`7hHA+9v>kjbnCm(zA_EymonhG>a!rLvobgTU?U zZ^%iGz0&T)lfp!$nX@@g-k#->tc-V$i11#Hf{|$ai3;s36Nhvegh$=xh#jM=bNMzPiyA9fq|oSlkZtS8to&-5Hxxz-7BKZ%MncXkyx{% zt2p+QTozhujIX|9_HrXnRP>`9o0P=d=cfwzc&sHXzOr&@J=Q0Usp`=-s_N=>Q+Vpn zw(i_9mzKJ&`t(!yO>o(mJNiz#xCKBDO~OOH3C9;8V-R|gUMeN#2iSUW@1r`#;RKqu z7@AfBCIJRgdoKG(GqUsGw+S`C0nbSSzwjKgz5*iW~<)g7n~b1y*pta>}H zyJs0`E;ix52U7=WyL6ijj+?7~k5NRw`2(pz{Zy}|4|^do}J!I9+8~$wXomE zqc8FVbRmB&mC*mKtP}BtXRQ3JCd7P6gO>eNwJ%pPX;?8H)eK^C$s*WE0t#X>a)?J; zx55!e*jM(q0)!nJ>oo3Bz&xcXt6(gRS_7F$&4l-Yyd&%0a0ドル^%U9meohCD@=?S3&7ZUP0Ql)3A7h{?bGS~`Cck3y1Zv;0-C8i3w(mgZbIatmduCO!%^X z5@zjXqBNa)tMHJ8S{Qn8L2a1&k{yW>eU;6RZBWbYJ-K?q)SuXNBEDe(bxD9EH$|co}ic>mkYqtnrL@Uq$ur-5_ zm<{qori6nask5})e6w$-bg1+-vzt4ciy&tcz<7`^v08af)+m?!bg0bv)o~udl~2h5 zeN$d-zLn(7F{}Gz=Bk|Fz4E8jmNJ*$!w6Q+67@huD^>O-OXS~3bSRc=xYzV`YV@T3 zEWh>WlGjdI^` zqb#hTH=1IKA47&ZX})0fXdJ9Pd!}4%^C#$b*+GR~slH^rGp1Y}cGGS3Kgqh~jXp&| zA(y|CbpJ3g_PznCuXCA6Qt7c9_|+E0ry9^$-$fq0lSS>Br_#Xj1=v){c|Dw`qP87+Cjc4!2IKSlIDR=qoHjy3;D z7cB-*_mUM13S~ji36F27*f4Jt-G2S39o_n&(KbfgH10|L)h+^QLJo*Th!mNvO28c3 z3RaZsX6lo-SaQYI%+()m2O>I4MbtZEy{N6+ZBvWaW1YC1b>IMUZ8fdu)_Lf`GBm$& zXm==iw@X*alh@D*BDHYR>T>><0-d%db)a7mms4@fecql!toqi8|boz0p`$s;wz?oaq1p0?-azfu5 z8*&n68F68={lcIDA`)fmwnR=N0QdxxVx=L}H%0sIpAtx7%z%e)XA`L#Wdd#@){?y_ zs6TE)2wNqYbo^G(H&yixc10Yy%Bn#y`A+oK%wKvN^`0pG(8y62U9Vg^s`jF>`NLG? zowVV8b-FoWA#=2Dta&BRu%0z#fl_rQ9Q|};k0!jv$A5l0DVSYBu@^1LnU8Gp+?i#$ zXxJfQ2;&guV-~fk0yW~B3`Ny$`Gxui>d%7fIE@e3pB1-CFO1O-Z5H{XPIpu40byGb zh^IPl<@fv_?r`i$uj#*lnp2{p%eex8sdejkjsl_ta1ano_8^ajwooi%^_70v4r+tc znh=L^ z2$OF+fa*r^CxWu1$O)n}CNtS%C|7kCP`MaehC3IV)c*BFehC(`Xuwku3HJd=KZ9~; z;fUoKc-UxFyr8Jfd*#EBUpB?ok_(Lvy|N6yruO^UrLzO6PbMU`ZO@roi-u=Ujfu_K z82B0+aN~LWb9&F%&?h@9euU@*{sbm2+}L%ka#qqh`84(zlq`JgY=ReFEODKdJc>9{ zoRBfnPC4F+ZU|le(Lncu(x|nM; zvCgI#E&B?}8OTKl!JWrug?AvjpvR%wSKxv6K2iRXGU?EQr2v@;-z+-16MU#dx_3lH z9k@J_uqr6iIb*bzDle`EBE8{oO*8ドル|_#*sTFJYedxg?gk({yeg_qXh**Hh?PXMUd< z8)guV>zg-q6xwS z{N$N}ALYHw;?rRunhv&O1j^{m;l)1Gy?2~L9es!-Hbzgp|d z&&aKwrOWoY^BYflXa9StI5HYFT#O0Pikkp{rko^t(}QprrcCn4k>R9c>n@T;KhYsL z;fXyo7aXR7NwA&E1Q$_-95{~fYkxS#kpB;_PyhHpH5hxxl77&#;u9U0!1)j>H|N3% z7mf?O2Sb}yu+6%e zr5W;Bf>IP(?^=edGFZDAd3z?`;GsPW)fnOPtFquseSmx|Y<{3v56j=1kvyac&w;j* zgD;qmbMr^#1ドル^IfsiMPd%C+CCQ#gK9lDvRPO>#1|MrYHXNOr)Y9n9k1BX;1bi#CTi z2KoDI>q)lG5>DGg-FGEj_EooYB=tnJe({H|`lAitUfk|FJ?)P76sPA9KFI7>{t6s30EGMt#D4e zDxQ6@;f!?Bsb|4K67VHvOc5ドルx59-_ArAMBl1!SK647=?g9f17fewtcOW^e~O zN4o7PatcWapd;cMv&{^71PkAgMYpEUZ$M<~ia(t8%v8eerpvi-xbbco=fwum9k?5q zdP2%a|@pQe=znAK(4pT_V6C=vCTcUZQHiZJ+^Jzwr$(C zZF~02eDmIOF3x+;|EpV-RHZ7Zq>^9f>F(95*E*baZAiGvesYun+1ys136&0IF?hQu zf2f(p$EyIs(GNa@vCF!)H@%4Z_JE=DP-eD2qZaIEHhpb37~d zZIGVs0qkqcy%Q>FFF(E2^q=pNcs-Xuq&p+9-&5Qac)HULb{81#Ujj{o$jjx_!Yxd&Y;TzqY8KX z#I;6}Mu=%kbi-KRh7gmlO-{D*2A{bQ>kVOMs(^;mG2ke!BGkKalfaE}i6f+kJw@V- z71;SY-c6+g^8g0K4MNTb0EuX^EE|`ENR1bU&1Z&x8~V-Z^KBAEpAk}p)H@xR`Cey6 z#Pdd$z{#tx!5Z$~wX0jNRPi6~mV?|cgI{Nq2VwsHiVN!6HFiEz+T)Y{4$>Ao=w()q z$Q6F)5NA8AFV$T}J{TK+nlN6Wt2mye*^$Ae(F>Spl?{4bKOWd@8F4-q7Gx}*XV3V| zt+5LnE9t#Ieq{3SViGDe==Kg_2u(DXHWI(!BL^n>O;RuP_a=F*)q%JQA@qSvzMGbf zJ5gxgZ!SZo1GLXs9<7tob=`d--`k&mq2lk~6gv^p+aae9tb6fques}fxa-xv*pe3v zpu^7U3wlByRr60Y>J(%3{z4RE>?{I5S@T{Pr z;L7LDBV>n@qxl7}?JIeL%*q+{gJ*hHF~8BbMvjEOG_k%L2Yd#Yj`j-#>I z^3R8=Wl(7ZU>0ck;0xzW>bf>UuJpJpsSeFP+97Gwt67c`QO44kXf%h@VpiF=rC&rp zZm*W4ドルS*a@f2fiE=<_-i4*~)*gxpygh_d?jqo~socyqm1=ab3gn%qh~gs0)ufq^}q zNa(ok8WaOtNZkg*H0zk(G~!J6h9ecQrDw_w%dX5jUVkEBI1$ZzYB2N0MRWq2^WeUq z_XVb&om2ISNb2e5@g@@`#L|OvU$f~Y+U;xAY>@szrTmk(`KRtDT2o*pJxXWjCthdZ z25=f+59aOR6ePfg_YYKW;_)W^KhZmf#;fPEB)Vi-2O^HMn%bddd5)=H)EGK)rwd42 z?@^!NH77!x#lp$3x7}{+PnErzNUBq1sU*B1bRQBLI!1T2~3jH_b)cN ze-wp$u8vlq!;^rXPUl>Ot@yCz)yOMHRZ_8PCIDmkF<=fyarh!cp0hqaornj}hsxiw zJE6mUL4Js^tCrm+sI|uBb%>Q;0Vgw}e33X{x3k*lhkro;wT4^Fo&MTE!rv<2w1g8j zfM`+oo%)*ja+|%yWff!p67iNucjc-e5F-I&$ftk8ekeFdqUnVy{6*UO?gr=N^!)e> z8@shy2C7f`;&ck@H*@yYRD0b9c!dqjdq+g?RztKN)R>+eRj~c(y)@_)U!T3V^?qpy z!pj%HzfPSBU1{5t|B@d9`SAny-y>|2zfJy&j~^KS{(DXqX}CLin7o>9$VM^+F%v>a zCFnODagZT6JTAB~@q1-LdXh%In0Fw?-~jF)pg;K$4ドル$@(s`W8h-%1H=+4tn$ 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ドルyZoVN|#fqGy$z?4esKDyc_VpoN~s`P<0x8=gyexeku)rc9c@qg&*1ct1u z82c$|&R^_ECjI^>ws-{@~!+b953Sf9XZV!>c=9Ku9DCn|BMnT{|>L95v z0=W3BpEIUN$fW5@)3jcHqdiQX;=%#A$cqnZVJNGwCcU=Qbdm1y`FQb}ay7D_yycR1 z(64G7Q!Q0{x*BeD6E~bwxkjEt*eI#Etq0beiaVyj<7t8zj%dpjyt)oeqmoc?8nlr? z+*mGiYRnI)ItKR695j)eJsG`8&t^M@1rS%dP!A-HA4Ls;mx%)pd0cT@@GEiIs&K28$hc>;OVNBNkusQJb-OL`e zVz~`*dBHYj&#)alA847Ja`mvGDnEa+p}9e!zMhE0g#NT;<9vycvspkjfw;n!i8<}7 zg_%64O@w+I)xlLeKQ;+z0A`Dl!z7{7L#PjfUuod}l@E*l`14cm6{LDcCE`d-Q@?@R z0Rj1dTJHhQIdx6I0dZBt&8j0T`G%fs(Z-)bw@F zy4N{zt!xZ=mA!yC3*}Y-j#+;Z5MTwXvCrqn+M=w}O%J zRx*fuaKm5g4ドルma)em;45_?LJYIXevCuu61FP{^Vl0#!Ci1cy-@T1>YJX83fsfw(=e zMj4$NITh;zEDZGw_t_tpn(yz^(>gznZb*YAQbu)|!?7Zuu55XRCplT3TU~o5`7y%H zI1Oi>taxrNlv!%Dg7s=_O}*%$han;=Cm)NU0=M46PBowkONtHHt@6c~im9GE8T^5Q za%kdopxXEuEs#=5#LhO%bB=wiX!HYyF9Wz6t4*F{+NwrCGmMq8^*v7wS5mjmr_Y zF0WEEt>)`r)d&%LeJ>dnFshcB*Roo-Ya^z!Ts=Jlw%SS2V zO7nj z(?RMY^k91c(#^=epv`n5ogRrk=jnNnzW}!FOkm}sk5JId_(U0_iN_X>vjhPTvr8b; zO~|8*kW~%`l{1du>_^r_PDVR$r7HCnIXYjhNr1};k2l$~)kNGQI*Yos_Iv|QwNKDY z$^11rY13!3Kty~a3b{RIgUy2U%NE^G9-N+UANl)HfiOlVEZ7(ApFIunm;xyJeBjnf zP_eOJ_64ceK=N?E;>BYspz3mfTk}Cj9ドル_eN-50=$%K1o=@yXMV*b|8=LC3}MC5hF~ z{VX8lH5ZR*fRb17JNd>lpz5U4mOXjL01ep}Ha;N#HMZA2g8_!W)xZ^Pkx>P099r3%e!?!jVkpG(p)?EOtFZPxxPV14%S zqDcP>+BEL*E~1`C+_B8<%_$r=;*ioz&vfm}vc+idHWP#~Xfi7t&Dj>YwVG9ugP-#(!tD2>2*F9*O zjBS$KV^YYAJYcPEn@XGslgtx-v$pTz-x30-JcHO4*^J6oGnQP36d@g|?pwH=AyeZ@ z)!Sl=1*GDG#N4FK(a&qF=S)-T5u66gdanak?3Kq8PSAWo+9D~{ni^!LEr1GB!6&hl zNmiCbvt#A#hZPk})>aL>u{)6z>iPjB7g^Q4Wv9=VfDo9MRS8ドル?sD=qe9V%Aifw@c= z)O&APb*0XcPM+HB&5U{%Aj(Rym%f?GMulj;oyz&t5(t&C8< zjHz;GnDQ2aA-!|rp+Wq&bQ@#-4hgfcSg(wlq^lxL!6`nYM*nom`#pIO^dCs$KXK?% z+@5iMD^>}1YVf4i(z6WQbWD-x@bi^er8;D2COY3rBHg{ek^e-gbpIsUp0iYXpQ!CE zMw-}LnDnr9E7YAaIGx0kSvTPFmc0@ALl(e8@d8OAgkpgAN2z!F<{9oycpinliy0nn zSdq}a-0UGA%eTqVznge+40mkO;)?&79%NZQsYcb#v^T`it}W3bLU-9 zDUpk*TZj(lTnG>agiSdysEJf;CZ9E5{nN8&o$a#Y@i*C|msZ3A4b>7i&bYziHHrk& zA}3vjlH&JORFV?n*;NOd>eev2++1X;v(7>+chN|aEFOCBtCXg815Y>b=fFx2*=}uw zkx3sy|CEN8GyRp~V647>)fKP}_J%*A;pA`615B=?KUw9nHq{J;onrx|4m#L~VETL? zhAUV_e@B1xz7bx2qX%b9Y*JHP+3Za^dJhGzu}APNF0ttayRnz5L-XLSI$D)SxSE##0KtS#Ws9NZOr(vRcDHOqzLMu5MO zV}`wpLuGun#z=#=>3Kpj3Xs<(cqt2a1tc33cqy6bd`w(w0*6jf-xv>F;e%N)i?R`b z6dC3TR*g6Vjb;ac%P)Epck3FEJ$wej7$JPnBcaOKMw-HNt{Y8zE>)% z5#zK$p{lU*Eo1beQNu+3;+BTNbz^8}~JWAQOpBBHfV6r zyRRyxwh}}V`jtQuby-FA*DZ>wgFTV~KdLg|B`0L50<#mtkus*{ar5xixwmk}nv9`q z!&(X}>q)R-a&hzMBxyuD$$Q@WZxhM=z!@E!?;_}1ar-}X>;K^;LiJCB5UT$$_OSV$ z|4`ff7mz_gIyNmXMNQTuMI$abDz+#!HF`i!K1ne;A=L=-H=N}AUH{9{f>Hpm3@6eESRWVu1Unai9-N2 z+&Yx%Xq~DxZa>kCl&3n*u+sj7-fYB%8zdS|gf&;!6yjGUHfKS-$VE94`AkK(%=+rgqq-{FFV5DA=#+Lf4ErZ|tW7 zE_vmCO_(`a8^2`9H~$(JBE8#53AbM5(Mo4gtgpu^Xu@$hQ4suHEQM8c4+jQ4j3osw zXrY5R=#oeo)&= zF1qVFL@W7?@Ew1Pzi|BT$o<{cu7{_ceqafao1r}kz z`=>0=*QYI$>r|ev&r8@J*ZFw62;3;Qp#kBd_lHpdN*jqaLGBrU60)x(M!s9_Yyyr5 zM@uLJL=BHueK;NQ86ドルbfpZzI4Dj6$B<53~it)epp!t30ipz8)y^(tt8vo#x;ys?ca zgJvs=$}u0!`IvA?10ihv)bdLdn~)Xu9m2_0-qQwczV*Zo1y>ctk(uNwOhX-d>!b=z zf2RsdF2JU7^F5{~SSnAKp`lNW;EofozeFE`W$CN%_*6;?7*!k?^{BkcUADdL(}3LG8965SE&?$A95QtNgs zMBle+rS%9Q@B<_dn!(eqamg@`?9azxjddisjy$a4ljicpwnq4zt^z}zcgi>g92kY^ z!lQtupP*ooNg$wj%|WjxZs9u2f{ zDW#xwsc?pl+h3b{QgLiMXsu@R`9i?W{)~F|qspSWt>hbDs%;&HJ4+0M%6@f}??%5h ze`b>ks$lP4FpLh48-4IN4#Mwz>7(@I)dc)P>~&e5e?yT2Un^ySSA7AwV8ixE$#d*6 z3ZjMHYOeZ0y$|sV%!9Gz-O?g^pJTMc|21hAL+stG8w2tW%yyM`uP;wC#SHNQ7Vy$O z4CvCnU>FRjv$h*Fe~x3AkM#UCecwSWL5i8W1-^}p-kS*_i#Q@F|5^krY~0?~7ydO+ z!?D3ewLjj^Il3Tp<|=ff;}>`fhnAijz%Grx0yr#N+BPgO5U)O$jFDP{i1*rihN6(W zU_cnZcz)7foVGW@=d(QBL)o!EyTjig3Xu{bX^r$_>u&H4@uXgyz*i0W1_@O01j9pS zX{1m3RQs6nKqBUYbpfwiZx7dR4^QpyfLP95>zV{_wSF)A+9!qD`%eMdTJI6CcsCEt z9Z-moWcd@-jaZ38*1kYWvVw7O#L?>8i{)Da)X3()p}NG_NpT=Lq(GTBhWy4Rbt{UqzN-eMpUa7UA%3(i zHHGgE7)7zEg7ge7ドルOmthHvk@_bYc?7RDNn32U#2Mn}~Oxw{M_3P?HD{EA)EnLYqSV zJ#5E*#aw=Gx!y9krQd8qw+}^Ic&F$f;6MpBV_>ChNT>8cf+A1{B(uV!aUWrUvX;?f zeZ0(@fSrM4@&|sQVfcH5ドルcg#Is8Te{kwA0ドルl+cGWHeFb<m+ zlg$%!*Ut9KsavGh>>94khTnQW>+3)!GW#b=!=No}=be_h|5j6x0EiXNPrOFTg|6!mSQY*n+c!H zu%AD?6I!Hlf#dm6lQLcFufMIpj-Ssld$^{s9k4SHG6)qQtDtkYA&V`0|0Iy@cB56T zvL5n*yJO3^>H}6oz_Uk>2Y6$ombUsc_+g6Wri?O?Y%GGqimMtnDB`1m+G4ppA!NDh z6$R2TrWb6;d@G#OaUI9YF{jfpffuf|)}Lb+Fn3jD4h16#t*apGhsv9t^th8efZBGO zb5>-^Cmgcx%Fs8yp%S&ux`AtMSE&Y!Urwc02V8kW_DwqN`J=o>P}Hv~rt_NWI;K(a zBT}Vbu2vY`GGk#f)#xa0q=^qJ!`P?}SR8;254zv|O*#$s5U=z?zqcvf*l-L{WU`RMukF=5Ob2t~*@suQyDe z^<$aavmfvey7@vr@kp zM!Zgff;<p`2kG5z_6*Ubr$M+a)Ae31P7zcLc-ogOen+q!}hJkK8!-FmY01;m{i) z(n!%|q!p7;7~R!75PK>+%qL2ksGqXv&0WnJPd~f>G-az4hU?Io_9)LT`m#_BDynm% zCHQ4LZJK(3W+|)nb=j$_OX}%dCThJ+)T#;?*w@9lq zZ1bh`lM$K!>Q9y!AS>5DZoF^HahDl6i@7P=`DHoRfU=vXu5E|}!ci+Btmfi^a6zpNQ84c+H@W?MpPgZI2(&d;WiJIm{pO_R zHAIBq8gqwd?j^#3uSsK+#XrU=u)d+tz{5v)&#=VB*H9E&PZ1*4VrK(_jew(%8Q3y# z9~wGA69QGmYu0~}@BR4}y0sR&Zx5^QaaHhz)HV~2b5xhE8WeiSSxBeeAs7xt6%@O3 zo%+FGAE5ibZ3x&T%|N=%TujFmYI`muFQ57Fv$*ZS!)qvA5NO^ zzLBFua^CSniG*OGGblbQ-a-=uj4d8H(dFV8*?AF&Gs9NvQE}3vqHZ}ALpk^Kxi-tL zzhkNx%sv7`Z5ドルT4WWYS9i8n`pGYeAp>IP7Zb#r0#%~%?y{Uwc!&0lVMG+VoGjlrSr zRBOLN``MmUt(MxLpK|%YzMy`5^b}$gXPWsDt~0W!vuc#S zY2ioKFQQ)Mp_KvZE4S5PEy@`$C;b?79KEb+_#?GXtsyo|64xV}*lvCrkg;l4@Ijk! zr(;dPjA0O(MulE&r{FS%UTx{7lfo48-3$Czbw{T@3MUr(2s`PnU@X@F(f4R*!E}g) z_Vw!L!XvMhW?c1`RI9UNyZeK<+=hxkjy*vi?3*}=cs(#p-qf+%!~*^)>-kiS9)fq5 zAx|8TVwzP}Pxbgypejaet=8L`EjtI24R^yi`#e`sQOmZm#%1bt(Wb<(a(66vzq z1RC7<#acim@z)jktw9y;libm_euhrbcy83&zo`$lj5BeP_#)@B81zGJiYOW%ca7 zHDUi#mnSfS?=KrYp)b=$bx5bfwh#+}X;~y-p>!uy6%9NTBsdaI!D~m}IGwgLog6p_%nkhfJ%K2H(=3)Y# zaX?{hVo3!hayb9u-mz;UJa34zdi`XgLlidNX)M2(R_K1=ZXQ9wm#Eko8<2;|3ck2j zga;^^d-hx8ALvJ_RFA*GBEn5z&s^Vx+p%x@$iHbW|3?P<=xd2z{)ry&&ft(q6dd*c z`Sd^dmxIdZ^7FEApVAs&1pwk8104EBIaCcG@HH-EO4RbCszS3mxdRxa;PIfh$R0no zflM4^Q*HkoM?~)luwIElAW5Y6(e7v0yE{F5m^jzBvvq%ZyudE!vXSVi@mbeoAm{H%@!gp#V|hy&9)9lg=w*4n0E2p^0PXSNSmrQ{2Q-uiF+&So9P3&*TAF* zyH(U4jUULKtGDwKV^-{xOzL z5kcb*<*pnl7lq6nk0+q(uy!y}pal|p1mza(zavoybz5fon2iix2zh7nz-hvhh!&m) zves7g+hRC@*MulBK%!*=J3W8Ru|u~B&_jes$UBnBXc@{;;WVk8X*!v)|E6@UEGgvz z6LGyR7b~(fPXFe>lmGBaQ%EVihdGZuzFw_R7A!)zR6N++G=jUGp;?j%H#MH+b%7N> z2WEINEQol~vuzdTORSc?W1kB)^;O7*dL!T!it)@G&skp% za7_`G*4|dkfmYHJQot7Q=IHzf6fH>w(?12egA*_YRmZ`r{NOD%Kd5t}cTxsHDofZ< z`B%owy94QbAO%TCfhHgJ&Im`9@|HRA-9q}7c}euq0KCly;Yd1@Pqc1C)S(b@P>n;2 zIQRZWdeDL8p3DEMX8-xW92EZj3G^TT%74_D_W$@dRwi1>%ztled^Ka9pMYYLhD<~h z@j@o#=7rG7d4P=yB07Y2&^cRRFIF)*3*8dDXnEj;isyy?MStLIF3+!v^`nz=n`V37 z*k(K4vR}{c?)vy4I}`?rJdaVAa!MkmLRXF#=?YDZqL`pWNq=zWRX@sAzURW+?=pxA zU60ptxMsZRI6

U}@pFZ4!uQKYp9B!`y%1Q1>pGh}U_h0Zb#>|K?P09A4aU6lc`3 zx7@06*ca<&deu)eivmgy*hn{g_&4d2b3xmyym=&@yq?rpizgyti65&n@!mpvxqf_i z^(rO+D&P7DExnfO;I`fTp?3cUSinA$vN1Edye6ZeomM;)P#3B|NlPE?LejdN8GQ~0 zvwMvDfH-wtIMwrZ^xgF9R@?PQDe=T?t8b4tra~5`XNVT zmOIrUsi0Pa6ドルx&ywoTc3w=bJ~EUd07=tXVP4>kAXM6YxnCyVD_xq5q*FV&|`gN2wA zROg@4zg!aA*PrlkeaXci1}FHNzG^PW;@)ybxCzF8n8AuEm`IL5PEYves~S77X|f5C zfSfo;lS4tpE}LOY7aYRQ^nhSUFy^Hcdgu7EnRfrJR~48=IXe5!L>}L+A)Noez3M+R z5xv)rFPAFqGY z{3x*18B&|b%rn*&MzhC4M@K+0qTEscA<;5*8=69cu-7l)fkiq>wgg%;Wveg*%AH6T zA{CVp0oTNOht-V!c6t5i<`asux`-h}i4jl2u3o0osgwg_tpr`gvqt#1r9>Zq&QEg| zWKBa?A>-DsBf@&L$*gH?p3XOx){fDSuD4>oAfDzZLZvi{FqUf6*jB0DmckMlmopBh zJ$sd&)KsM5*giwb)cJ)N#%r+*?3De_Y%>Ek zXXpXU21wcwtdv40s5Kjoc|7@cgsmoXYYLQzF8~zmwm7+Ky)?(^kvly>T7#)EdS2&+ zK@6mEKe<92ドルo}~=k0fl(az=lmpi*nfz7`likkkhmj4dkh$z#@5*ot0=i&)7hpm3bl zbA-dPi)l)9B=xAvRS6LYDlM_9=Qtb>drzH`*#cv~wx|43TJ|UZyiq|a$|(6RzV(z4 z@z6QqJXEj$pNV7~Q={~K4iIv2eErnjvg26hXx15b^_Eq%2b!M zx%nu@GG4u>+OEatwW0fdXw2^9{GHn5YE;RMZd~jv0?Fs!Ld&jWk&88t=4VN7qDa#M zpyYr2KcS95{BO07K44$E2c9ドルvDDWS0Am%wDK>YJM=0{+&OP(H{ z-J*?I%&v;HLkGPBX;5S?dcPPZJzcWb=Jg}B1aLL@eYA#u8e$eP^%*31^rOo)5@Gg> zw5&Y5v4GBqbT+}3qRG^7Jy;ET`Cg;HAx`&tz5oG1&V@XX%vHTE$bel6P!E}5gurXN zwng{qpENvhOd)-Y_AvlI(>~bY=H{)$V#R9XZM&}Jx#MJ<7>AxgN7N}r)G`sSu+v-m zZ-ui3e;OsLhIa*lOCng&rRBxK0$ltG0><`ci1tmvkct5wyjbvktq1|a7ol>>QSP?s zN6Dn6L!E=@r;|8iFmV{2m589;H#m0JwDr&&3|^>?aWXk&^T?ukc^ zgU9l5p*F8ドル>46ドル)YN3717UTAi`e2FsgZr;22iD2>)Ns5CJ`VBQa}x zBWrszhi^9`h3{1|Ym@J))<*a}m);pj(s?6gma#i)ilro^qj}pjrhcvfq|unk=tti! zB@P%O>PwlvdHF(a8LwJ7B@cD)dG8D+r@iQUJc*zmQ)5Pe3=Y6uO=NP}8&6$aJmczg z0h;a%_(a9|MzG7fU-z1qrs=O~qWe2=vy>CRVv{WIhCX z552<(l1_k%$+w+cae$gjz3ghpdt&e=yr+i$iokci=$ukuyt1{fzk-mghlejzm)n%s z<@-gt>$rSBytHyGAfgH+q8^ZG<6khkwer)o&m6`wu|ic+^}>)|t9x&C%b|_4}fQ(-Q_Y1#>JXO*kPzb zZ1ドルV?ce{%_o3e+d&xIMrgs4Xg(MqF8t2}zQtp(f=7WvM;J^5CGM8m!wDnyYQNvwYT zk?jKd+E6NN#){-H!$|=KV1ドル`-|H1SN=tl742GLbFw-}#{N%s zx3nN>Tx6qJlca#6BSDsUYZSG9ドル|dLExt{nU0#Q>95awnZ#M>D_HNJogRWo4w2k^P^ zhLDYKObC~o(V~}A3E3G`DG(63Mlnluw2VMS#=;eguErB(9ac#B;vgGDGWj@?8~vVy zchx0F(wg^d2oFGZi)$x9g|Mkf!d>DTagMHl={_v8A%M5l!Hw^92)s=9yV zJOu|m2cv(~cblpfE=Y&SUfqWAYQ#W6fj@kei_>BZ?O_4JXBzPG3W9hsB_kTv^qeI@ zSs2z+SAj1E#WgNBwP%=>-D|+LSvb+I!IkpmOL7b~{i16|e4G<*j@l+$k?(st*ew!<I>zJ?XoAf8UKZ z8-A~~>GfR)-Es|W==fgm|Lpi9+ZX-v!ivlDYab0K;l=>nv8L|@+?&MJl{6yz?pY5# zddEca{T|{E;IqUFQMs#PU-kF4qin@D1OLd!0`n*&AUUL&03VM(>m^s;tnh>>B-B<0o((#fhnindv#x3!vy=wm*@jz;tyk3xpw_f~pb z9xb$R!j?%T*(R*T6A=^*Z$^G&iPDYPQB$JeB`MJ}xY4$f=@27yiM!wS)N;^=PT$;m zjJTHxGRpbwJ-|4s0rdGw-}b6xccDS+^Qa3%vU8pmx>;g}-_!QaNI19UIGn@Hb;=SA z3G7kT_HGGTaxNQ#qhS4Kg8+D+;9EsX654{L|1$vnz!rk4bG(^UN|zGb?1uJ+PhZ7> z#wvaLW$Z$VbsJ#&*?s?F&4wbl1-fgjhb>_F%5&P~)47PIJk^YHwzfmKJt{ao)jiZ;9J>X@f4xIU>akn&j6DGOT0(YL*%I;$zf zQ;te6bW3b53GmWHaNT-SWB!K8%gHi?Q?5v_vZaa*wiD?}Xf)Xgf3qJS6gVTbwE}y* z6K?1!-%B&_@FY09%7=30(jvQ~`NkafVuR-5;?;uEq~bjKHvfk$_x>|Bd&Tfb-S&vZWPVI^&3_YqZ(HYxbn@zWB!2xd+m%-_{o0ドルNF+Wlo@ilk=sjv$aKv4a!p z3-qXgT!(|JSm!ykPJ1Y{S6|2OLW)#9_>8$&V~*TzZL8re{rWZ1Q|8*ILqGm?p@@^m z?+T|(O^;))q8*~mR25!?J!P>`!3S)Y^B_5mMQf}N0SR+pY`HU-5m<9ewrprc-805s z??K8XTopSsX&$T|lsi{Jr}g8C){s^Z>#snr%zYwAYa?*-XTWSnhV})4WrxfNF7nao zJ$)KCT9`~(rWed;3oJ3FK2O=upNiogYN|P1SfaYVFM@8dq2b9&AwK?GP@JHXu!I>n zuEEp#fx9$NCk>KUZ26-`)a0vC zHuJB%(z9`xK+vxC>v`XGaIs(p%=QW{YJnr@pQ^XR!9sc4m0Bb0(D#;_S-_bh^w}`N zs-x}#AF`Yc$Ug7!#i1@$AcnoZFx_S_2}g_apfT$C{Oxo}7(8Xg#L83H>#be?#@X9ドル zeu!lQpQ)hXW2AA4j`lYu6aAQv*D4X$SER6{J{Vo*%koT1gefO|es%FO`($}u4jmn~ zQO1o1uO72eIpy)4OC7(cE4HK9Yel<2;z62qvm;dmat?n8l#qm}e;8fso!6z@)5qpm z%UoIBk}5t~$PO9j741AJzhW=qnoB^@sTgpb*U@=DX}RuccAR;8^4?dvqNyr62=&t! ze-m`BTaqI@5lX!e`KFsFQrY_8c@v!efhKR*0=}D0q2!vLqjXN}kE5uF6hl#GoRO@E z9K=PMcJsVa=Lw-Y!(a=Esh3I>Ds{yP-+;P6lqDZj6-nwW)}SP5P;opDR2hsKN|>(ALms*R2GLE<8pe#th(5ip-f?uuuy4zwdb~p;@q_ zJ;_M0QJFOb%yskqc(3IJk>q183o6x91ruD+S~f~t{G3{^hT-nc%wQv}fFW!hbT?nC z%WtxoOG>+9DaU*^_WYto&hRnLL5+WSblH|m^^&T!Rk!H;O^-u5R}VVMXJj5ya49aH zyiC&@sr2D~N|R<+%258)^)^zgt-ogcl5%k9hy9y%m(vrnrhz?$dyl64yld4jm4`tBsUSh8uXu)?Fr@QsCh^97q}pIV*Jtkj{|a#5?V&t-$lHmC2ON#3L* zbULAc2g9fl>*SZTNK|^G)`iY75CZZ9oPm?|q`0az2gGbxX(1(?rn^EsBc8mac{=87 z4qft2wRcXe%0{`?MYiq{jDExeTW8h@eD5Dq|Yj>Q$xKUpMSAZ%Kr-iJPts;Tf zudXa?;8N0N1FdP6XPa@-GE(Kbj`4>nwP`T!!Z zV4Z<)ph!w*b{(teaneobh8sb7_92fmczn#ojyn;q{a#(+vl(|_pv0t0!f+fppm09} z=C9Y_zqtmMHFIzP%vk@JBLH^R|1?2Ji_?~$(giqx#@HH54-7L2TA9#eTL=t-KYSqb zrDv35>Cp96MJP#PT~?e#TcvM4>&1Q~(2>X{KI=qS3t~zypmo5O?u{oq;*=k|O`EI! zmvTp@KPoEYmU6d3a10CSOfeNwV!Vkc<%kf)b`l9f&w>-2vU^Cg#_)|~d{ z0qYpTDr04e3_-K{R@KGXqXLh`+?e&MnywL9YwWiO@WjV3ev`Ovh8z)uMSS0Dwc(01 z4%0=&<*7)oh*m;icn1z@#tbzy6cp0mlelswvccwwhz<-d8s09e+ka~fqvk5leu91$ zKU~c{`jgJm+F?*lFs;tWHMk&5VG$U8B#UXv7OF9ENKw!-0qQz4ドルP03+*rFBN9SLiW z!yt>;?98-QbH9r48jB2Ndf#oVaV6YE@b)`fxnK_z91O-(MKCtj=z4P?X_&ZR$?els zuwGFD8uRegia?;uwKIbZo&kqfjzOS9y>JoxskUN*o^cf~eb|1DetAAj0^#xqrrF(x z9zsLFhWj3E=-~$J~P7iWZw^gvOLzwJMVJ?`*95M$i`eSbWgIX5I7`Fa#}g% zqCn-N)x_q^!obG!fQq(YoJ3k#U7@$$dRuN#z(x2na~#;2N&}Ayhsa|RBt84s;(`Pe za>brl)yw129bvQC4gisp#^t^qKxe@FU;_@-OPXjORx8ZUzKvlcuo;dsRgaR1#=|S1 z5Ha>tCm(lw2%~>k95m%2jGq83t4H)`QN7RqGxaP zw>fgD@{t@eVBuD$-FW0&iJIlL2BDAGhyu;pUjU87NCKBU&C=mrccg({*Yxdp_LKM- z)War%tVlQsAYHUks%E^#WU}Z+#^~s9l0&}Bx8tXW%SVV0ZYAWA-EeSRRo~5oaxzk| z$QbhoA%uIX!BLs1j9Ez-3iNeEqX^XMFE#t@kMF%(QHDGG`+N;Vw0vL6LWz$?tzN zP-@rI89@3-@VVa^lj)PrUHpM%^EaY5)b~pT&8IbVO6cClZ1CWIcx0%6S$Rpy?@;8+NHnvv(?#jfwt6rE%98ewcMQxlhn3>DkK&dA?f|RCMZK5)SH89}KcDP9^Wi8G6 z?Y0W51T*Y4jC2h*EHn8g4-Y<8;>wW}{6bBS;%Fq{GrdXS1|A3oS(hp|_o~iv{WdO~ zGmYgOCb(P6(^@#aYrrDyIsK5*Uf#Y!LDIR#$VKyECyw^gM`SJ0hFtomp*Y^Xua?)J z)6y+?a0^Rhd&L30@n_ zp}9Xv>+BfnV+}gD$huiCFiWsa;`wfr8j))Vr;PGDb&PQOYp5!)jTTX~y&IdqE(=LS zOWrmBOuiIg^6bB}Ed0adUwSFxlq~=be+)0Vxd{a>jLqy}m|#T}-Pl*YZ$xaxz8JoW z!9?PZpBRoVBefELza5XpcDFaB#m6|x5 z6Vva3@q-%_43h}WV6J9#1&yAlSjahLiIm#rX#&zLsinj?<{cyta9o7s5|~qlawc&c zS=XBx`{8Ak^Y#lqEC@EqD&;!s`TIZJYMXQ|d$Qvc`F2x# z9lhQH3=6jhJOcv*U4x)RKC>13S4Tr#`Lg2 z+a+BVdSK<~o|2|vz%dfblhtz?o+xe+`=0r=;fh+ox;umqiaufg8g?!mr<=tfq|yzd zJM|3~F0+QnRNkP|aL8B07l)fd&H)P9aj!oxEYpcaGR{ zxzH9`NIOBY3PzW(Yi;8oOc&&9iC_x`C1)fWPXo=ubt1jM5#62!;juKS3K18v8!B)O zldMY13YA49HFl$R%u(7#r2r*G+#ic_U}jpw9&xzuxAtWtb*MScGt(opJWs zHq8ua>@@X)hdzQ{z$)WQz4#8x3{5zZ`gSng+Y%JXkm+C?Px}y8pQRUXL#n0LSOMV3 zTK?Zvpnkpe^LZxW<`)ohw?4g-c~1&odsuqtsa>z`E(3 zG6RYZ;0wtULI-#2gq46X7$dGnAxqRi`o5FyFd@Legh-XQ5zOC^(fG={<5tqg?`y1~ z=d%w2%8nT5%?)IIH5*NCeXu+wogQaT_6RzSgAt9F&~8}I*S4Ne;!X*kaFxpV_|k0ドル zICF$Sqg_kA(aR`un%7=rB6?R~e;o?NCyWoH0YhOb;7lF!?+=B4xNZNf0;LG}<^!jl z6hdumjrL#yMY6B*0<96{26=ev7bczgwlwtlxffzaop7@8p52@q(5e;nf{ax3)2ul; zd}^UZ&>M%H6>f)P-ks5ZYAMB)xWvpxT}2FZxZ+^@9wWhrDwslva!+ z<~qu*hi5vxb`0;x{?g9z974|d_m{qiw9;-;btg0yegpui1ikmd;j!j=sRZ>3WyLrLI^^7^U(7P}+=|}&3q4G@& z*M6YDIrK?4zKo(|(ZeZU7G*OuOlb5DM%c6W+Mt42jnx9D8fVe__t3I7@9({|o23ドルS z``oF9rGq2%Fu3e7qP|5%(7@{J6q>r07+_MvA}{Afq*HGQv1;Xvn+17<6l*t>_st za>*n|HOnvjHntha^H>l2IEx`0+bgLfCzmvvI~YEmf}LlYlFd=7evV=H#Ut3C(*@Ew zGt&S%KJ%K>A9RYb|1-sqZtC}2cv3ドルma0>Zay}_7p{jDi05i7pW7;21d#8J|CdTTKG zUP6m}Vp4&Au_o0xgpa5(&uoFCmLA;eECd#ndOgA?)8VcRwlfLq^2c;G)Glvy!$O47%YP*OGlx(um2K#~E-(1iHeKeJ-rCKGa8bS}pDAiET6{ znVBTY7Edw`Q)zbRHb!GCAMWJ*qUHjAYfnV3cll!4^rEX z>?2ez=G!?pX3K}2;fMH=DVo5ul*S&0#b+{8I-EVW&Ya!6aodrL&<-ye*3fsoxul8y zTI}&9dR3OuEKI@lTE5-{0S5lFua34Tb0y@Rf$GEUcGt779Q{^_#k_P2TzwMH(DKjV z#Vp;=R4tbK(>szGMiF<84jwx)ub8p`=r-#o1fxuoj<-_r$a+jcd!`si8s2hzy8->~ zfkes|pq%~@veYTW8OqEGNfB`XcZo0@xk1^`HeuH&s*86rY#JVM%=x3uxLnxQxX>BQ zu;?8f!~E0GUJzH$+lBN!vaciqO{E6xb#%+Gv>3xTUm2V62L>VX1&K$cc3}*_0+d>H zW2?=gCEzBlP~V7rMsRNu=xU7BbXcUlq3 zRVVP)9Y^0Ywf?NK$Svi9$wmK8HIKa}%fj7V#VxSt3Z0YmQ0es>p@>wSTpmtyb~z|@ z`etZJDAq|DlMP5}pH!hQ719)BcW^5O4k5#oP4#J*mfs>_w)6KD;TW59BgEu)@yBKrVVlmaTB_2*9d8;cdAIPfCopra*vE_f_^rX1mgqg>iQ~ z%rVUpo!89gJw9Ij5OKG2OhDAE=Jnmbv}a_QBHq%^9V67#HlN!4y^R{=QH|jO z(iO^O-d^pySmes%t4(ZbaLXs->X^a)%5-^PwK8BbWNS56vFQ!#ODr^D@RVOjJXwuDST*5`$r@5EZ-2@Upt2-JjQ|@h%!8F`32e9FmI)IY6T6Xe6qA=V^H7X*buf(apduws>& z8)RrX^*s#NV^bXrGboEd(^CZTR^oul&DdMwN4%z?>puY}3z(N3)0>E7+A1YA9a z_tw0csibBU!S95>Qwxklfy+{UTit&>P5K>CBHNRt^0AtFf=;IOodHB-btSKV^&wv8 z!VTUQE>~nEQHrk&^AjS|J!pWZ82i01-xX@Xu1qnKE|3Y3Hz}Rrlavnm+$^s-WZLftgpLP#Lu=PGAQ^3?7ドル!;+`v1x%VW@pD}|rRPK8h%0~k< zbF~M9(G3=5w~stX`j*Cayd@7WPE`fuRim3A0Ri<={jug>KStv}Cedl?Fs@27H9FMi z95FElwv^{AOnM3CFG)g24oVGfD}GwPg7kg6ld#tsuq^2 zh)M=4l4ドル(6TNQgQ^I4u8U9M;@y4$!fzPHfaYr9HH8kT`4yV~45EsxM<6c_5*xp(g=umm(g<}gh8o`s_k&fotBU16J$EV=Bakg+2?B6KRhnincj(k3u6 zPt$PAxnR3>{i3#GpH_E@%9YYQ(>Ib0^)QkBrKgn7I%dE*+)t+4umm!w)Kt4atfvHH zDp5J@OqXN{;}dw7l~YuN9J6^Zu|jwqqih+o!EA`_Fm#{q*_s@6N-*WBPFkxq_f)-f z*J4+cww}9v4Q9CdL~lK29SgkRMo&S~fi9IT20SzB41@t=aIJ|&j+HsId!bdhrh+?5 zzMa2WH5Mm7MtBc)Q>{=rJo6GT_)y8L#PlH6Ts^bVhXrlp>A}cQp8g?>cCTKsz&Fdh3y0uhFeu@W|w4T?K9oaROU76O~ooHcBZI0{VY`a)J-Ma z0|rW1PAy-dl9zgQHRr=zL@T?tlfHx=W)LzjsTeV<)iwvk6`o}9(-iajaiww+9a6>Bvg zRyTuM2BWMHWl?-OdRUUCFdEIVHVQ1nrgWkfY2HENl2a6#G#g9}RwGxf#zc(}qyjbZ zh_PuJCfwjjcP94=okL=OQ5tN7k15o58Zg*5^@zvKaf>*t$$>W?S%4X9KoE#ngTaMsCO;3g<%3e z*6|q+A=y-=ImvAO;BSmP(zzfZR6B$M&)H$tUr`;bbT2qdF9T;{jchlU)WQf+7ドルQ9X z3WMyX!+9~w%+5>3T-3-N6Schs7nBu=|2Pq1r7!F};vpQNZEEQNs zHB5!DMKcI{Sk7|Em<8iuqci~-r?vax(!>6aG?NsS1{ljvt(40ドルbRxu{gENUZG%)Dh z`vTq3JjO3@au)^e`L$n=5v@&7kye8Z2`+Xes(V7nV*2q=Yk+J@2T=#290ドルErzzwns zEt+QF=I6}<8wswj0yy(htb8utnweg1ver4z%iznga{~jxqixb%irp8 zKGWFstH97fLj&nF7m}E+rRjpvT_vi0V}~!Op|UC54rEgo(OH6-C}0Ak4ドルMa6J41sG zfnV(3Ohg-23aD+7dJVrVqQT(n66^`ELEJqO60!+%6ird+f{OTr*`56s1H%!0*=k&{ zaVnF3+zNbI(1j^n{=3c$>d9e9SAP6gz0h$E>v4qoq0FvkWc>GU_^yFoL$JAKVOsQ& z*==y|Qoy*FH7~ANw@zjT?J?$WWJUN19!B+Iy)!z+TcL*KC(bBNDhlGc!a2}T_YCj7 zhi#27pK@*M$k;xBd%{@-N*#)z^|RRm3HA$t_TwL82T0^mvb5}@sk3ileEvM|db=^Q z4Zhk50oxkarh97jAnIqc!wqCgDs7Ml719%D1im@|5wJ_=ck)db$cmMIib*=C+sy4_ zWynaJk;D)ShpbAqx0l(pC zmq-5^{S+@af9EH2kM6Z{$T~|1_(3*5{LOMQ5F+F@Qm@vu^|cy>Az!~FW4JFLuHHZDhrVgQFZz578AbAF#O3WPzA zF(l;pr1p|85rJimQV1IT$D}s1(>fAt^Zj944kUUuO^py@GYi=gICAl1t7NsMu-;G? zu+5_$z?PB{mLtq^(b*7qTW7iXL$YD_r3`k^KqUtfyc0URc&8mRMqqZ@kuk>NNAQ%; zl*RQ*V}CW+Gs-HZFGgb949Ry~-1fjv!sWokDw};b~zQ!-NT}dt@kRqj? zVj`dO(D>^1k(t`Law)kbzwrWC8^Gvtj1PH7}N08>L5q#9Zd9^%1{Rj=4uF{*Vflcgbjc^x{ftptg{4^=iN5;C|`NJXbM#aqE-r3(=W2+-JKe)knV~+E{l! zg(r>%>WyoP@r{LulY~ z1rWg~#Sq9PY%$^!ax@2dS`||%?jI;}zwm(IVhjY^g@2pzA$l9HD2ドルuABt1xobrQMfDHvW-`KV_~AV> zeMc_W6CC-%9&g`!<=c$kv95upp6?afzic~tcs7;9ow0(@p2{ut=w1aotn$~{z~ef2 z9S)JV3bH}T%Tm*OaD6q-^7`;qhok75z3VOdxO8u(Y#b0TJY+!iX~dZhqAbq{NA=^uVj}0DM(?jB>l#T-$~`se})Y_F+0+zY(unnDEsEks&2!>3HhPkD0b65REP*;-O|>EhZ; z65MXYM;lhtBWaGLCwav;Ku7E1X7v0Kt9_>U{PJ^W#nljVm56xBX55pBEzKnw(Kgfj1+nl&!nv!xPkdMjtK*Mk37exCX*}OCU(g zwMJ|y94QbwS}-k$H1#c5eLH%!rJZ(t{OCv06tJeOhQLZ$U+k9Z%+}_t4Uw8fC{#G^5(1dwdyu@HFhVrtghF z7&*aa$q14VYLh^lhD9{t=u3R?XS>F**VrdqkRMo~>~!5l#Yw!8i?!OnBi*5yE-492 zt!I!|bg^c-Ch2s{>kE|K4Cir>R5~EH6je+G^?Spuj7ho;;<*w9p2^rFz>>9!w@T^@Kkew5xdAtKnyG>zpAb=H9b z&s!$hBdwka;qjH9=`*vcfmkP$ro(N{Wv($yQ_K1^Tf8lGljPGmv_$_#F$UOlx-p!$ z8WCILwZyRra_w8t@kK4yh89YBaJLxljtL!rh`#@Ne1RgND=DzF02w;ep*VDcP}5h5 zwHZhlWHW5B^srY4tQaOT)C$Lvn4*nDOkO`}V{(M5?9x~+W196ドルlSJyq>r|gWo z+-lw7%}rc4gs-e?U(l^e@o%Z__C0Vt`pf%DBdx$^>vK@*ip~Kgk{UV?-ZQ?3X=asZ zOKTOjA?8^vFav5LXT0b3zq+0dnHj&N=vzZ*CqP{m{p|odnY?2Tk)%GOg7I(>8i!EBMFI6RsdC{%M&v+}i#A^V3hx;{(+ywX zJc9!4876Mg#+1`Dv$+y<0rzxablrqp)i-hes9cs#-r-ku?@zjffzgl{`p+#hmib7o z_A<+nz5qkb?+}2h#p{l;bcudq4clxp4?xj{>x-5}k3S+zlnF$+JBN!C7LzN4-rN_~ z6jsni0j2kDPOnG{Dse<_ek>U&e%bl-R)S_~LU_FkDp`VU#ETv1zSm1*i6qa2-f0#B z3`(5?*y(UY7zg9~k# zJq$DNCX`y&`_{%@S0xx_L+?9ls?q^T{Sp0 zq8&M5WcWMtFN|fk;GK(_@&{dkSF<-d>;f%}`8|8kpdf6DOR58ドルVy_x`=#tNPU&TNU+5Y_We*q*A#rVwp^NiX+~LGmvE5wJ>y( zOcrO6(oWJ=0~*At~|t>>X;eQnW$pGi)UI833ドルifG4eCu*4KTZOs$D)mGq?J5ebGK zoIchVb{?Y5?;UnB6Q)C(d~2&dSSArn)^+UExF~do^#JNjR_Og;s;ab!k~F%7&6l3-R5E{|7+EMn z&vO4Ot`i)CTC=m1x6U_?AaRk)RLfyq7o&$?UCAW4_33xxfR@QT_6Lo_udWCJNgP=w zE40cK>^KCxFOH8tR)>Ka8AzLm_HNXwQ{P-!zI%fdE3FKWEeh| zQvD2hT5cWt&@+0G%vRTA~`UrxKts$r<%e^awoc(q&bj=c$(fbdre6q9ql- z#YD<1*i3q5lyyna0ukdcyajnvl@l1zuca&ob5|wlon${|k{axv*0quvmu}tst97o- zK4dK>&RqkX2tZZkJwy#E>-}zELv)4ドル{G;L5x#|Wc$yqenH(xoUoF0dH5_p|6TW z9bSv|MSbv99V1ipa}njsSxdM<4h7h&r357m0j?o-rww)h>mX+=SgX6i6?a(=9-m zc{@^^qxy~YQ&3u&e9}s~lua+gk&Iwd`!3AAXfIuVef}Dk3Tksu0m=hj@yHe%BROFz zs#BD8x|5l@t`#OiM^Wr-EgEPL|Q2>W2)#TgUd$}5#O|fvGiJS;p4F| zD>!U83l-9Ej;VXwTFhoQ@^Qa;uqh3tkBlhewensJ{dXhT@k7Z9YWN=EewOFOqnzZy;)+?uE*(47QUyeT_fM@%9@ z|K677nvY1kvaBD=+2782d$djExY;Fb)St0)=M{yAGxHYiL+PquVGRF>3)7Z6H#j0*+eF_WyT&@>b@ru| z6AHeH*uYqm_=G6h2{j14dOVsdzac4=isKP2;_xdADL7I(?&uI0pgwVr~AeR+ev z3JZNt3_pGk@zLY1HD%t3Eo0@VxiQ9<?=`95uMR~UzrWLGpEHNm75 z*KoXx#52{&ITc0D-Xxioj0X+Wq!QwjVo24b7!DJ0r((wNRIXYi=FY&Bj8O1y-x;VG z%p4+3_2>;~MqanzA)<#bh<6%k!&6fsd(wha9fxkdri`!|bz444p9n72ngr0{@hnid zN$Ne&yeq>YiJtrl*p(#D_V&JPD;S6;=%V5r;c^YUS;jymGhTCusi70#A#@dcL32rD ze=e|oTNy-hm{;C%p_Vv`Se*h7<(f;5?6rj#dwj93san;wh{~odw%dy5s^hhlfstz!NOra)Tdhc88EH0NCbp=Le7M*1ドル>R-VXUNxLQAU?qtC8(Y z()Vw*fl*fl!9@E68Gx3BS(-tQt=EjiVzpP2Og&i`D~@djp)_PtsR|~eGiZr!oSnI+ zBaHYDxhTfEz+{2Asz45kkzPpSmEyizTstD8yUZhm0)4B-i27TOlziZ9N z+eLUwv+Nd8*M6RQPk(cKv_auHPni$u-lifW+&Uy2kwYX*3gK|({nriLn6zfpX21q6 z0pP?7&EGe0|Mp$~<3qgzu-xid&3_$msjz_ubffw35u`xrq8$udz+=q@%1ci|oes<~ zBh0NH_~SGeEuuH{7+LF0=0~YW5l=wy?bsn*9*U$#QX1!d+KI<1cv)4g$my*)l~2ui zH*XYV2z`YR?iez%w*x~`Bx!OZKcx54(vaC{dYyc(r*6C6s>VH5>y;}&~rLQxz4sG1%mEikjfJV6deMCjrHd7vnR40;-x**HDd8OBy zH3zXvs)%Ub6XuoMv0K!&2D^b>`($$#`Qz`H`nE5X zn@akzQ(V5xWC}O=~NTg3q5W$msEt1OXkvwxi9Wh~UJEX`E z%l|1lBBlgx;7MBEKByr}-+TfatgvX=-u5ドル<5*msxec#t`leggo45ywz6({iodi0m# zYQ`4#3pANraajr(d!qVfV&B}#Z2g#M@CO#vx`vc5mh^R9SFRE2_ngm6H2p$~RCwmD zpwT>7osw>46HL4lEN&khp)@)T8hMuGSYyvbx)do*ubz9|0*wsZ6Xre#C5%P{9tTse zfVF5A#064|SFUj05yUx)o9Sx%K|c?)7I$h}u(Wu}Or{Ks`(^tK&`r90kVAwel5;ob zEKQ(%D;Dc4knaka=Ni^OOPKu*@%}KUe4J{Pkb+o@lel^cn5RsCdI!I&CbAgQ9*`S{ z(CfpB0ZDDuK4CO}=)2e4LI{|i_<@s#>$`}Ph!4J3h1zzBEWYN2N;w%feT71J$hb2J z3p0I`cxS~2j?vE>(U-9_>ktLp(VKeqQ$WQ?d9u40I0sxc(z;=AE%PA=5nBFQ+3 zTi@Vge;-(8G4{Z8G!gE7OF@l&u@>zM^0^Yh@WCLV_ZB|QvfAk3=CAnga1ue_F+3H7P8YOu|gPiHVMYH zFn2toRS+x))U%W-8QsIHCyGs!BF1@JA zdrQrPUqPYcmX*$jIDR)i(4msPuAjk$UaiT8Q&V0!#Zqa(4a-?FW58qd?ze#J?C^#U zx8?9lbAiyPT;)(=FgfPO!hCa~mp_`4WEC4Wdc^COJ}e9kruYTf0T(78t>9HoD69ドルC z5Tn+o^$U%CJ8)W0;i-4KQozaSqC+2d&4H7XM5&=Z-@1?&UcLxci2>qH`teL30HYh{ z-=FEryla12yS$LwOp6|p>JmT+9Gfq;s;QkdpAlVR6+#y24nU7YLw`&EX3r}bZz_re znQTgEMM~8d2ERQBiQMdUpi#3ZmZ+Jc z1m1%@WMfc<3wmzmwl^upnf}u(d>BQ7A?{rEv2RZtLb-&g)L7)YOHwO7v@2x5ELMZz za+_+vGehVROtc^`i4$UiYacgGV~dA6H?S)bg&+g7VjJm0+jtJti)T*E-hXlwlMQ-b z%NgnGR$Eo;rRGjS>0m3P%Wgzh0V=IR19zjO<~ooaa@ijzecqyk2_y=fe1rqd{iav9 z#b@)ClvSCjS5;{*J`1Pdk-2MW%|r!U>6Am4DIkE+WI8F^`ZdQxA8X;Fb|#&e56X0O zn?nTz=jEEh5b(#*?2Rg{Br4=MhkJSfz_J%git3v|CA<+tws(uvhdffe4cpb5zgp)t z^#``LtR#9zTCfqCZ0+jLd8u|Ey&%O-nU8Ja$e!A}d8( z#X8G8!wd>oAVWZMtu}3=0xAMT0|0-&0!9G>v=0Cd1SAN!eu~-y1l(Q}Sy3fHT1h!E z`j^#eO(unZR8|38KYr}b@|ETeAHU591`q2kJ%K_l_PZj^2Dd2C+C+{^9P5_OZ0rb1ドル z3}QbE9^l>m1%Qx^jpGk_e_L5SGiyLZL%#qXbO;4HZ%AsUhQ5`4JHCvn&8!reCxcpek+Y-}FM`ND>S`}b;fJB`?Z12~&JBMlxMF6x#zy$#MQ^5oLf?xEB zTbJj?(5M13X&p zZV^6{6XuPwkiOFMwEm&yu1nb1D{2GbwrAoxq2 zg_l$>BiesbK}G*I6<~a^{j&<+ools?!aqss<9?gukn5ryswpp7uph$;wi;*zzi(ab zr@kb384viA;5zC5li+{#*vkN|pHvfoRojbi+@FT`e-CQ^v#(x8>ioo~$oLKXe~#OE z*>5ic8-9`*Xa8@qUr(}L?m+*Lm+NAV}} zN!#C%f8W3Ow_otW%k`6|tK)AxFQ=V \(.*\)$'` + if expr "$link" : '/.*'> /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/">/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED">/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "0ドル"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java>/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "0ドル")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version>NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/DynamicDataSourceApplication.java b/src/main/java/cn/com/hellowood/dynamicdatasource/DynamicDataSourceApplication.java new file mode 100644 index 0000000..65ef8d3 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/DynamicDataSourceApplication.java @@ -0,0 +1,13 @@ +package cn.com.hellowood.dynamicdatasource; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +public class DynamicDataSourceApplication { + + public static void main(String[] args) { + SpringApplication.run(DynamicDataSourceApplication.class, args); + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonConstant.java b/src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonConstant.java new file mode 100644 index 0000000..2e2eb3e --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonConstant.java @@ -0,0 +1,24 @@ +package cn.com.hellowood.dynamicdatasource.common; + +/** + * Common constant + * + * @Date 2017年07月11日 15:46 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +public class CommonConstant { + + //Request result message + public static final String DEFAULT_SUCCESS_MESSAGE = "success"; + public static final String DEFAULT_FAIL_MESSAGE = "fail"; + public static final String NO_RESULT_MESSAGE = "no result"; + + //Operation status + public static final String SUCCESS = "SUCCESS"; + public static final String ERROR = "ERROR"; + + //Error or exception message + public static final String DB_ERROR_MESSAGE = "Database Error"; + public static final String SERVER_ERROR_MESSAGE = "Server Error"; +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonResponse.java b/src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonResponse.java new file mode 100644 index 0000000..de2af1f --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonResponse.java @@ -0,0 +1,50 @@ +package cn.com.hellowood.dynamicdatasource.common; + + +import cn.com.hellowood.dynamicdatasource.utils.JSONUtil; + +/** + * Response bean for format response + * + * @Date 2017年07月11日 15:33 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +public class CommonResponse { + + private int code; + private String message; + private Object data; + + public int getCode() { + return code; + } + + public CommonResponse setCode(ResponseCode responseCode) { + this.code = responseCode.code; + return this; + } + + public String getMessage() { + return message; + } + + public CommonResponse setMessage(String message) { + this.message = message; + return this; + } + + public Object getData() { + return data; + } + + public CommonResponse setData(Object data) { + this.data = data; + return this; + } + + @Override + public String toString() { + return JSONUtil.toJSONString(this); + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseCode.java b/src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseCode.java new file mode 100644 index 0000000..3f6d35d --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseCode.java @@ -0,0 +1,22 @@ +package cn.com.hellowood.dynamicdatasource.common; + +/** + * Response code + * + * @Date 2017年07月11日 15:41 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +public enum ResponseCode { + SUCCESS(200), + FAIL(400), + UNAUTHORIZED(401), + NOT_FOUND(404), + INTERNAL_SERVER_ERROR(500); + + public int code; + + ResponseCode(int code) { + this.code = code; + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseUtil.java b/src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseUtil.java new file mode 100644 index 0000000..3bb60a0 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseUtil.java @@ -0,0 +1,103 @@ +package cn.com.hellowood.dynamicdatasource.common; + +import cn.com.hellowood.dynamicdatasource.utils.JSONUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Generate response for request + * + * @Date 2017年07月11日 15:45 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +public class ResponseUtil { + + private static final Logger logger = LoggerFactory.getLogger(ResponseUtil.class); + + /** + * return response with default success or error message by status + * + * @param resultStatus + * @return + */ + public static CommonResponse generateResponse(boolean resultStatus) { + CommonResponse commonResponse = new CommonResponse(); + if (resultStatus) { + commonResponse + .setCode(ResponseCode.SUCCESS) + .setMessage(CommonConstant.DEFAULT_SUCCESS_MESSAGE); + } else { + commonResponse + .setCode(ResponseCode.FAIL) + .setMessage(CommonConstant.DEFAULT_FAIL_MESSAGE); + } + return commonResponse; + } + + /** + * return response with custom message by status + * + * @param message + * @param resultStatus + * @return + */ + public static CommonResponse generateResponse(String message, boolean resultStatus) { + CommonResponse commonResponse = new CommonResponse(); + if (resultStatus) { + commonResponse + .setCode(ResponseCode.SUCCESS) + .setMessage(message); + } else { + commonResponse + .setCode(ResponseCode.FAIL) + .setMessage(message); + } + return commonResponse; + } + + /** + * return response with data,if data is null,return no data message,or return data + * + * @param data + * @return + */ + public static CommonResponse generateResponse(Object data) { + CommonResponse commonResponse = new CommonResponse(); + if (data != null) { + commonResponse + .setCode(ResponseCode.SUCCESS) + .setMessage(CommonConstant.DEFAULT_SUCCESS_MESSAGE) + .setData(data); + } else { + commonResponse + .setCode(ResponseCode.SUCCESS) + .setMessage(CommonConstant.NO_RESULT_MESSAGE) + .setData(data); + + } + return commonResponse; + } + + /** + * Handler response information + * + * @param response + * @param object + * @return + */ + public static HttpServletResponse handlerResponse(HttpServletResponse response, Object object) { + response.setCharacterEncoding("UTF-8"); + response.setHeader("Content-type", "application/json;charset=UTF-8"); + response.setStatus(200); + try { + response.getWriter().write(JSONUtil.toJSONString(object)); + } catch (IOException e) { + logger.error(e.getMessage()); + } + return response; + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/CustomHandlerExceptionResolver.java b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/CustomHandlerExceptionResolver.java new file mode 100644 index 0000000..36da04a --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/CustomHandlerExceptionResolver.java @@ -0,0 +1,70 @@ +package cn.com.hellowood.dynamicdatasource.configuration; + +import cn.com.hellowood.dynamicdatasource.common.CommonConstant; +import cn.com.hellowood.dynamicdatasource.common.CommonResponse; +import cn.com.hellowood.dynamicdatasource.common.ResponseCode; +import cn.com.hellowood.dynamicdatasource.common.ResponseUtil; +import cn.com.hellowood.dynamicdatasource.utils.ServiceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.NoHandlerFoundException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Custom exception handler + * + * @Date 2017年07月11日 21:03 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +public class CustomHandlerExceptionResolver implements HandlerExceptionResolver { + private final Logger logger = LoggerFactory.getLogger(CustomHandlerExceptionResolver.class); + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + + CommonResponse commonResponse = new CommonResponse(); + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + + if (ex instanceof ServiceException) {//Service exception,handler exception from service + commonResponse.setCode(ResponseCode.SUCCESS).setMessage(ex.getMessage()); + logger.warn(ex.getMessage()); + } else { + + if (ex instanceof DataAccessException) {//DB exception + commonResponse.setCode(ResponseCode.INTERNAL_SERVER_ERROR) + .setMessage(CommonConstant.DB_ERROR_MESSAGE); + } else {//Others exception + commonResponse.setCode(ResponseCode.INTERNAL_SERVER_ERROR) + .setMessage(CommonConstant.SERVER_ERROR_MESSAGE); + } + + // error message detail + String message = String.format("interface [%s] has exception,method is %s.%s, exception message is %s", + request.getRequestURI(), + handlerMethod.getBean().getClass().getName(), + handlerMethod.getMethod().getName(), + ex.getMessage()); + + logger.error(message, ex); + } + } else { + if (ex instanceof NoHandlerFoundException) { + commonResponse.setCode(ResponseCode.NOT_FOUND).setMessage("interface [" + request.getRequestURI() + "] not exist"); + } else { + commonResponse.setCode(ResponseCode.INTERNAL_SERVER_ERROR).setMessage(ex.getMessage()); + logger.error(ex.getMessage(), ex); + } + } + + ResponseUtil.handlerResponse(response, commonResponse); + return new ModelAndView(); + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DataSourceConfigurer.java b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DataSourceConfigurer.java new file mode 100644 index 0000000..a3ad8a8 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DataSourceConfigurer.java @@ -0,0 +1,89 @@ +package cn.com.hellowood.dynamicdatasource.configuration; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +/** + * Multiple DataSource Configurer + * + * @Date 2017年08月15日 11:37 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +@Configuration +public class DataSourceConfigurer { + + /** + * master DataSource + * + * @return + */ + @Bean("master") + @Qualifier("master") + @Primary + @ConfigurationProperties(prefix = "application.server.db.master") + public DataSource master() { + return DataSourceBuilder.create().build(); + } + + /** + * slave DataSource + * + * @return + */ + @Bean("slave") + @Qualifier("slave") + @ConfigurationProperties(prefix = "application.server.db.slave") + public DataSource slave() { + return DataSourceBuilder.create().build(); + } + + @Bean("dynamicDataSource") + @Qualifier("dynamicDataSource") + public DataSource dataSource() { + DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); + + dynamicRoutingDataSource.setDefaultTargetDataSource(slave()); + + Map dataSourceMap = new HashMap(2); + dataSourceMap.put("master", master()); + dataSourceMap.put("slave", slave()); + dynamicRoutingDataSource.setTargetDataSources(dataSourceMap); + DynamicDataSourceContextHolder.dataSourceIds.addAll(dataSourceMap.keySet()); + return dynamicRoutingDataSource; + } +// +// @Bean +// public PlatformTransactionManager transactionManager(DataSource dynamicDataSource) { +// return new DataSourceTransactionManager(dynamicDataSource); +// } +// +// @Bean +// @ConfigurationProperties(prefix = "mybatis") +// public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dynamicDataSource) { +// SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); +// sqlSessionFactoryBean.setDataSource(dynamicDataSource); +// return sqlSessionFactoryBean; +// } +// +// @Bean +// public SqlSessionFactory sqlSessionFactory() throws Exception { +// return sqlSessionFactoryBean(dataSource()).getObject(); +//// return sqlSessionFactoryBean.getObject(); +// } +// +// @Bean +// public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { +// SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory); +// return sqlSessionTemplate; +// } +} + diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceAspect.java b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceAspect.java new file mode 100644 index 0000000..65eb849 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceAspect.java @@ -0,0 +1,57 @@ +package cn.com.hellowood.dynamicdatasource.configuration; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * Multiple DataSource Aspect + * + * @Date 2017年08月15日 11:37 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +@Aspect +@Order(-1) // To ensure execute before @Transactional +@Component +public class DynamicDataSourceAspect { + private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); + + /** + * Switch DataSource + * + * @param point + * @param targetDataSource + */ + @Before("@annotation(targetDataSource))") + public void switchDataSource(JoinPoint point, TargetDataSource targetDataSource) { + if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value())) { + logger.error("DataSource [{}] doesn't exist, use default DataSource [{}]", + targetDataSource.value(), point.getSignature()); + } else { + logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey()); + logger.info("Switch DataSource to [{}] in Method [{}]", + targetDataSource.value(), point.getSignature()); + DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value()); + } + } + + /** + * Restore DataSource + * + * @param point + * @param targetDataSource + */ + @After("@annotation(targetDataSource))") + public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) { + logger.info("Restore DataSource to [{}] in Method [{}]", + targetDataSource.value(), point.getSignature()); + DynamicDataSourceContextHolder.clearDataSourceKey(); + } + +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceContextHolder.java b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..b650410 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceContextHolder.java @@ -0,0 +1,67 @@ +package cn.com.hellowood.dynamicdatasource.configuration; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Multiple DataSource Context Holder + * + * @Date 2017年08月15日 14:26 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +//@Component +public class DynamicDataSourceContextHolder { + + private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * Maintain variable for every thread, to avoid effect other thread + */ + private static final ThreadLocal contextHolder = new ThreadLocal(); + + /** + * All DataSource List + */ + public static List dataSourceIds = new ArrayList(); + + /** + * To switch DataSource + * + * @param key + */ + public static void setDataSourceKey(String key) { + contextHolder.set(key); + } + + /** + * Get current DataSource + * + * @return + */ + public static String getDataSourceKey() { + return contextHolder.get(); + } + + /** + * To set DataSource as default + */ + public static void clearDataSourceKey() { + contextHolder.remove(); + } + + /** + * Check if give DataSource is in current DataSource list + * + * @param key + * @return + */ + public static boolean containDataSourceKey(String key) { + logger.info("DataSourceIds is {}", dataSourceIds); + return dataSourceIds.contains(key); + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicRoutingDataSource.java b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicRoutingDataSource.java new file mode 100644 index 0000000..e9f45f2 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicRoutingDataSource.java @@ -0,0 +1,29 @@ +package cn.com.hellowood.dynamicdatasource.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * Multiple DataSource Configurer + * + * @Date 2017年08月15日 11:37 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + +public class DynamicRoutingDataSource extends AbstractRoutingDataSource { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * Set dynamic DataSource to Application Context + * + * @return + */ + @Override + protected Object determineCurrentLookupKey() { + logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey()); + return DynamicDataSourceContextHolder.getDataSourceKey(); + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/TargetDataSource.java b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/TargetDataSource.java new file mode 100644 index 0000000..6f10427 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/TargetDataSource.java @@ -0,0 +1,17 @@ +package cn.com.hellowood.dynamicdatasource.configuration; + +import java.lang.annotation.*; + +/** + * Multiple DataSource Aspect For Switch DataSource + * + * @Date 2017年08月15日 14:36 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface TargetDataSource { + String value(); +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/WebMvcConfigurer.java b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/WebMvcConfigurer.java new file mode 100644 index 0000000..f790fcd --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/WebMvcConfigurer.java @@ -0,0 +1,35 @@ +package cn.com.hellowood.dynamicdatasource.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +import java.util.List; + + +/** + * Config for application + * + * @Date 2017年07月11日 21:35 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + +@Configuration +public class WebMvcConfigurer extends WebMvcConfigurerAdapter { + + private final Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class); + + /** + * Exception resolver method + * + * @param exceptionResolvers + */ + @Override + public void configureHandlerExceptionResolvers(List exceptionResolvers) { + exceptionResolvers.add(new CustomHandlerExceptionResolver()); + } + +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/controller/BaseController.java b/src/main/java/cn/com/hellowood/dynamicdatasource/controller/BaseController.java new file mode 100644 index 0000000..279752d --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/controller/BaseController.java @@ -0,0 +1,29 @@ +package cn.com.hellowood.dynamicdatasource.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * Base controller + * + * @Date 2017年09月25日 13:31 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + +@RestController +public class BaseController { + + /** + * Root path, The HEAD method is for SpringBoot Admin to monitor application status + * + * @return + */ + @RequestMapping(value = "/", method = {RequestMethod.GET, RequestMethod.HEAD}) + @ResponseBody + public String root() { + return "Hello World"; + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/controller/ProduceController.java b/src/main/java/cn/com/hellowood/dynamicdatasource/controller/ProduceController.java new file mode 100644 index 0000000..a7e0688 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/controller/ProduceController.java @@ -0,0 +1,88 @@ +package cn.com.hellowood.dynamicdatasource.controller; + +import cn.com.hellowood.dynamicdatasource.common.CommonResponse; +import cn.com.hellowood.dynamicdatasource.common.ResponseUtil; +import cn.com.hellowood.dynamicdatasource.configuration.TargetDataSource; +import cn.com.hellowood.dynamicdatasource.modal.Product; +import cn.com.hellowood.dynamicdatasource.service.ProductService; +import cn.com.hellowood.dynamicdatasource.utils.ServiceException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * Product controller + * + * @Date 2017年07月11日 11:38 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + +@RestController +@RequestMapping("/product") +public class ProduceController { + + @Autowired + private ProductService productService; + + /** + * Get product by id + * + * @param productId + * @return + * @throws ServiceException + */ + @GetMapping("/{id}") + @TargetDataSource("slave") + public CommonResponse getProduct(@PathVariable("id") Long productId) throws ServiceException { + return ResponseUtil.generateResponse(productService.select(productId)); + } + + /** + * Get all product + * + * @return + * @throws ServiceException + */ + @GetMapping + @TargetDataSource("master") + public CommonResponse getAllProduct() throws ServiceException { + return ResponseUtil.generateResponse(productService.selectAll()); + } + + /** + * Update product by id + * + * @param productId + * @param newProduct + * @return + * @throws ServiceException + */ + @PutMapping("/{id}") + public CommonResponse updateProduct(@PathVariable("id") Long productId, @RequestBody Product newProduct) throws ServiceException { + return ResponseUtil.generateResponse(productService.update(productId, newProduct)); + } + + /** + * Delete product by id + * + * @param productId + * @return + * @throws ServiceException + */ + @DeleteMapping("/{id}") + public CommonResponse deleteProduct(@PathVariable("id") long productId) throws ServiceException { + return ResponseUtil.generateResponse(productService.delete(productId)); + } + + /** + * Save product + * + * @param newProduct + * @return + * @throws ServiceException + */ + @PostMapping + public CommonResponse addProduct(@RequestBody Product newProduct) throws ServiceException { + return ResponseUtil.generateResponse(productService.add(newProduct)); + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/mapper/ProductMapper.java b/src/main/java/cn/com/hellowood/dynamicdatasource/mapper/ProductMapper.java new file mode 100644 index 0000000..34a0a9d --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/mapper/ProductMapper.java @@ -0,0 +1,28 @@ +package cn.com.hellowood.dynamicdatasource.mapper; + +import cn.com.hellowood.dynamicdatasource.modal.Product; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * Product mapper for operate data of products table + * + * @Date 2017年07月11日 10:54 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + +@Mapper +public interface ProductMapper { + Product select(@Param("id") long id); + + Integer update(Product product); + + Integer insert(Product product); + + Integer delete(long productId); + + List selectAll(); +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/modal/Product.java b/src/main/java/cn/com/hellowood/dynamicdatasource/modal/Product.java new file mode 100644 index 0000000..f020b46 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/modal/Product.java @@ -0,0 +1,42 @@ +package cn.com.hellowood.dynamicdatasource.modal; + +import java.io.Serializable; + +/** + * Product bean + * + * @Date 2017年07月11日 11:09 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ +public class Product implements Serializable { + private static final long serialVersionUID = 1435515995276255188L; + + private long id; + private String name; + private long price; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getPrice() { + return price; + } + + public void setPrice(long price) { + this.price = price; + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/service/ProductService.java b/src/main/java/cn/com/hellowood/dynamicdatasource/service/ProductService.java new file mode 100644 index 0000000..a31c095 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/service/ProductService.java @@ -0,0 +1,96 @@ +package cn.com.hellowood.dynamicdatasource.service; + +import cn.com.hellowood.dynamicdatasource.mapper.ProductMapper; +import cn.com.hellowood.dynamicdatasource.modal.Product; +import cn.com.hellowood.dynamicdatasource.utils.ServiceException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * Product service for handler logic of product operation + * + * @Date 2017年07月11日 11:58 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + +@Service +public class ProductService { + + @Autowired + private ProductMapper productMapper; + + /** + * Get product by id + * If not found product will throw ServiceException + * + * @param productId + * @return + * @throws ServiceException + */ + public Product select(long productId) throws ServiceException { + Product product = productMapper.select(productId); + if (product == null) { + throw new ServiceException("Product:" + productId + " not found"); + } + return product; + } + + /** + * Update product by id + * If update failed will throw ServiceException + * + * @param productId + * @param newProduct + * @return + * @throws ServiceException + */ + public Product update(long productId, Product newProduct) throws ServiceException { + + if (productMapper.update(newProduct) <= 0) { + throw new ServiceException("Update product:" + productId + "failed"); + } + return newProduct; + } + + /** + * Add product to DB + * + * @param newProduct + * @return + * @throws ServiceException + */ + public boolean add(Product newProduct) throws ServiceException { + Integer num = productMapper.insert(newProduct); + if (num <= 0) { + throw new ServiceException("Add product failed"); + } + return true; + } + + /** + * Delete product from DB + * + * @param productId + * @return + * @throws ServiceException + */ + public boolean delete(long productId) throws ServiceException { + Integer num = productMapper.delete(productId); + if (num <= 0) { + throw new ServiceException("Delete product:" + productId + "failed"); + } + return true; + } + + /** + * Query all product + * + * @return + */ + public List selectAll() { + return productMapper.selectAll(); + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/utils/ApplicationContextHolder.java b/src/main/java/cn/com/hellowood/dynamicdatasource/utils/ApplicationContextHolder.java new file mode 100644 index 0000000..42c5875 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/utils/ApplicationContextHolder.java @@ -0,0 +1,56 @@ +package cn.com.hellowood.dynamicdatasource.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @Date 2017年07月11日 10:37 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + +@Component +public class ApplicationContextHolder implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + /** + * Get application context from everywhere + * + * @return + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + + /** + * Get bean by class name + * + * @param name + * @param + * @return + */ + public static T getBean(String name) { + return (T) applicationContext.getBean(name); + } + + /** + * Get bean by class + * + * @param clazz + * @param + * @return + */ + public static T getBean(Class clazz) { + return applicationContext.getBean(clazz); + } + +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/utils/HttpLog.java b/src/main/java/cn/com/hellowood/dynamicdatasource/utils/HttpLog.java new file mode 100644 index 0000000..4ffc2af --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/utils/HttpLog.java @@ -0,0 +1,62 @@ +package cn.com.hellowood.dynamicdatasource.utils; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + + +/** + * @author HelloWood + * @create 2017年07月30日 22:26 + * @email hellowoodes@gmail.com + **/ + +//@Aspect +//@Component +public class HttpLog { + + public static final Logger logger = LoggerFactory.getLogger(HttpLog.class); + + ThreadLocal startTime = new ThreadLocal(); + + @Pointcut("execution(public * cn.com.hellowood.dynamicdatasource.controller.*.*(..))") + public void httpLog() { + + } + + @Before("httpLog()") + public void doBefore(JoinPoint joinPoint) { + startTime.set(System.currentTimeMillis()); + + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + //URL + logger.info("url:{}", request.getRequestURL()); + //Request method + logger.info("method:{}", request.getMethod()); + //IP + logger.info("ip:{}", request.getRemoteAddr()); + //Class method name + logger.info("method:{}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); + // Argument + logger.info("args:{}", JSONUtil.toJSONString(joinPoint.getArgs())); + } + + @After("httpLog()") + public void doAfter() { + logger.info("request cost time:{} ms", System.currentTimeMillis() - startTime.get()); + } + + @AfterReturning(returning = "object", pointcut = "httpLog()") + public void afterReturning(Object object) { + logger.info("response:{}", JSONUtil.toJSONString(object)); + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/utils/JSONUtil.java b/src/main/java/cn/com/hellowood/dynamicdatasource/utils/JSONUtil.java new file mode 100644 index 0000000..90b2ef2 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/utils/JSONUtil.java @@ -0,0 +1,47 @@ +package cn.com.hellowood.dynamicdatasource.utils; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.SimpleDateFormat; + + +/** + * JSON data util, for parse and generate JSON data + * + * @Date 2017年07月11日 16:11 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + + +public class JSONUtil { + + private static final Logger logger = LoggerFactory.getLogger(JSONUtil.class); + + /** + * Transfer object to JSON string + * + * @param object + * @return + */ + public static String toJSONString(Object object) { + String result = null; + ObjectMapper objectMapper = new ObjectMapper(); + //set config of JSON + objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);// can use single quote + objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);//allow unquoted field names + objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));//set date format + + try { + result = objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + logger.error("Generate JSON String error!" + e.getMessage()); + e.printStackTrace(); + } + return result; + } +} diff --git a/src/main/java/cn/com/hellowood/dynamicdatasource/utils/ServiceException.java b/src/main/java/cn/com/hellowood/dynamicdatasource/utils/ServiceException.java new file mode 100644 index 0000000..56b5685 --- /dev/null +++ b/src/main/java/cn/com/hellowood/dynamicdatasource/utils/ServiceException.java @@ -0,0 +1,24 @@ +package cn.com.hellowood.dynamicdatasource.utils; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * For handler not expected status + * + * @Date 2017年07月11日 12:18 + * @Author HelloWood + * @Email hellowoodes@gmail.com + */ + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class ServiceException extends Exception { + + public ServiceException(String msg, Exception e) { + super(msg + "\n" + e.getMessage()); + } + + public ServiceException(String msg) { + super(msg); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..83e5fe9 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,22 @@ +# application common config +#spring.datasource.driver-class-name=com.mysql.jdbc.Driver +#spring.datasource.url= +#spring.datasource.username=root +#spring.datasource.password= +application.server.db.master.driver-class-name=com.mysql.jdbc.Driver +application.server.db.master.url= +application.server.db.master.port=3306 +application.server.db.master.username=root +application.server.db.master.password= +#application.server.db.master.database=redisapi2 +# +## application common config +application.server.db.slave.driver-class-name=com.mysql.jdbc.Driver +application.server.db.slave.url= +application.server.db.slave.port=3306 +application.server.db.slave.username=root +application.server.db.slave.password= +#application.server.db.slave.database=redisapi +mybatis.type-aliases-package=cn.com.hellowood.dynamicdatasource.mapper +mybatis.mapper-locations=mappers/**Mapper.xml +server.port=9999 \ No newline at end of file diff --git a/src/main/resources/mappers/ProductMapper.xml b/src/main/resources/mappers/ProductMapper.xml new file mode 100644 index 0000000..e7906f2 --- /dev/null +++ b/src/main/resources/mappers/ProductMapper.xml @@ -0,0 +1,42 @@ + + +