-
Notifications
You must be signed in to change notification settings - Fork 12
/
vueblog.sql
64 lines (48 loc) · 303 KB
/
vueblog.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*
SQLyog Ultimate v10.00 Beta1
MySQL - 5.7.19 : Database - vueblog
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`vueblog` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `vueblog`;
/*Table structure for table `m_blog` */
DROP TABLE IF EXISTS `m_blog`;
CREATE TABLE `m_blog` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL,
`title` varchar(255) NOT NULL,
`description` varchar(255) NOT NULL,
`content` longtext,
`created` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`status` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4;
/*Data for the table `m_blog` */
insert into `m_blog`(`id`,`user_id`,`title`,`description`,`content`,`created`,`update_time`,`status`) values (2,1,'测试标题人生','摘要人生','?PNG\r\n\Z\n\0\0\0\rIHDR\0\0*\0\0\0\0\0????\0\0 \0IDATx???y|T???????d??+IHBvA\\?]pm?Zkm??ڧj?Z??.???>U?<??Z??ZpTD?d\r?B?}Of2???a.3Y ?L&???k^?̜?眛̽?{??r8?B!?!?P@!??7?!?bؒ@E!?Ö*B!??$PB!İ%??B!?- T?B1lI?\"?B?aK!?B[?!?bؒ@E!?Ö*B!??$PB!İ%??B!?- T?B1lI?\"?B?aK!?B[?!?b??uz?p8|?Fo*??????????uq???o???B??Ӑ*]O@v???=??{:Y? ???~B??7t?g??Q?=???}q?-\0Q?T?{?i????!???N6?nW?w}t?Lo?????7V?T݂R?J?Z??X?o}??)??\Z????Z?V?w??%h????@?=(q?t=G??{\nX?[\\?ŭk`?S?? M\\?zN\\??F?Q^?h4?3Pq.?!??aP???\'???????oh????aN??????&?;??0??ai?+j!.*?\n?>\0?!?\0??????HO?%-y,???h4\Z?ju????-????G?b۶m̝;?|???H??!??;?c???][Dl6???~?tZ?;?????RT????J?)m??^?F{??q>?*?Җw??????.?d????ծ??X?X?U????7????q??Θ?^??X\\?V?b?Zپ};+V????^???????E?1SB??y??u܉{?b?Z?Z???W???O?u?cTj?0-AZ?z?#?1 T????b???j????îaZ?\rL?pa1h?Z%X?\Z?F QZZ??W_MSS????F??!?8??*????Wpb?Ziim????`??C????0?}????I?\"??h??T?Isc?g?pֽ??锠???2X?A??o???߿?M?6a2?|?}!???,Pq?\nP:;;?Z?t?;8xl?6???&5??h4Ҍ*<I?\"zc?٩?l???????e\\???j?J???j}???d⩧?B??KP qI?I???X?Vl6???tvv?????????\"???3 Tą??Y?)kg¨Y̾?.B?????J?ҵuŗ?V+W\\q?????=????m!z7?@Ž??f?)-)???T?V??{O?2tc??\'??$P}?p8????a??۞!*\"?^??4X?J~~>W]uǏ\'<<ܧ?B?l@?/]??͆?b?l6??X????????D?H?\"?? ?JETl\0jc???RQu?٬t5????k???̙3???>߶?g?Tz?zl6??X,??U??????\"c}Y^!?\0 2&S???_?MM\rJ??????>??=???<???X?V?o[?݀ZT???X,???y???Ldt???)?݄Gh?͏~E{{?Gˊ?fSZ}})77???,V?\\???\n!z֯@?k?Ogg????e?Jj??????!??KNmS[??????Ţt?l?A?s?̙>|xP?-??4?????f?????l???#L2&E?*???T[??GiE?eл?T*?V???6?=?:Pq????6?jM??s?C\rh???(?B?H??fdߑu~iU???gʔ)>߮??u???????p(3a??Ĵ???\">???֖g??Ue??????\nTz[3?b?p??N?F\rz???(????A?!@?ѓ;???@ŗ\\?B?~u??f???},\'??*??!?NP??Sg?)K%??S?VT*ՠ??\"???_???8W?Ou}FY_1??T?)cT:;;=?_+j?Z?~????~?)?V?s:??B?NM??Mi?uݹ}0ZT$P??????9??5T:m????g+?:\Z?\Z?լ)?ӓ}?U???E???U????yW߯???2!Đr??W?uT|??T*YB_????!?.\\AJ??ɾ\nT?EE??????*B1\\?.?\\?)_?Б!???+?v}!?pѵ???c?dz??3??O?[X?b???w??#???k֏??*B???k????E?~???Ǩ?~??U!?`]HI????ﮟ?A?B?}L?!?Gn?#?^R????-??H?\"??l???E!?G!?e?넀????B??֛Ľ\r??/l???\Z? \0:,-8?x?6?y??1?Q???m??;?/?|?5??(iQ??\nT\\|ye?`??L?Z\0@U]??z`?ی????????:\02??x?#\0?s͏?n(p>?(WZ?Dn??S\0?????:|??=?#??m/?R9\rWn?\'???6M?FP^???????D???ԙ=|?e?????;?????L?*??ڧ|????:????`?L?@E??W??+j???ԫ??1iD??P3? ???C?ֲ????v??μ?\Z?Z#?r?????u9?=F?^??w?I?n??ʸ??\\;q\0Ͻ?Dy]?ѣ?\Z?ht??p?[]??V???d!?dH????1?M????d??7??D?w??(??~?ڦ??&????>???Ȥ)tú??????ր????|?#???1??J???\0hj??????? ?N??[??????X?????Z@\\?Hf?.`??7Ш4L???+??䩘#X????S?5̚|#??p?(?<?????????f???IO?D?)???\"?ʿdב???J*>2?ico??=c??ٌL???n?D?v?\n????HlxZ???N??ܯ*upoM\Z;r?#?!*,?????|v]A]S???h???;??IP@U??V#?hM?5??{L?u?͘CLx*????B?????ە4}???E?&????u?f?E?ѓ?r%?O??H?p?\r&J?????\n??Y??!<$???B?x??w??t??????ST~???m?K?????$\"4???R?y??e_x? 㺉w?Ahp,\rM??՜`????07+??O?S`8\'??S?T??Qq8??????<?7\".?ɣ??O?学??\0\0?Rf0v?l\"B?0w?RV?Gq?!???|u횯?u?5?BM?????????ʢ?? Nj?STq??:\'??#??Y?b???4?E۩o.#5~a??$F??????648???i\0l????z?1\\y}??0?M?x????\"3????y/??\0????????Ef????1,j???/x???L%Mp`i ????í??n?ɨ3Ζ%???%}J?Xr3??ښj???\\??P?{?|??Y0?~&?:?pv??J??7?<L]S??o.?#??J???X?R?3y?b^]?P???;?;L??# ?&L`??w?@??????{噓???v????Ȥ)?1&??n?JZ?$L??잋w%ǎ%>*?.p??c\"Ҕ?\'d-????#P? O??y?@?5\0ň?q???N?? *,?;??S@???E??Fz?$?Z?$??\0?Ə\'<$??Fj??@N???(?^p6??oٓS%{\0??s3??|?????L&?^̦}????{??k?|???b\"? ?&:|Z??Ȱ$&f/??M?u??# T???!????r?r?:Q???3??>ߜ?k??͢ʃ???a???創???a̝f?t??H???7/??????Q?E?`j?MJ????J???8?)\0?F?`l??nyG???z?X??y\Z[?\0??:6<???rI??Q????ì??k?[??nè?ʱw\002q\Z??8????|??}??<I?Č?:????J?RS_?????V???q??????????9Sy?֎FN??\r@J?8???͏????yk?篯~??yz? ????+????U??Lu??f??{?????????k?????m?Es??{??=j??*\r??)?:??}???#?????X??el?N\0;\rM?@aB??g??a???????\'???ޘ?^_????u????D?N???i?\\?|?ǎ??k>É*B?ϐ????;?????TT???Ev?լ??7??˚-m????8?5??V?1c????O?~??\0?U\Z]?C\0\rΓظ??Ҫ<6|?\n\0ŕGHK?H?)?1?39xr??6???ç6\0`??qˬ\'\05?P^?ߧr??f??f?????h?h?ę?5:b#????W?m?F6?}?vK?N~@?)^?N?)????eR?????l?%Mg????|?^?<*????n?ӝ??????C?)?g??N?`|?|?j\r?G\\?yy?ݚ?b??k??=??f?d??g?X????&5a?!qB?I -a\"\0?\n???.?>,???!???+?J????ef???ɛ?Je}A???o.???L?5Z???sep?PTy??????d?.????j?t???8B?c?\\W??֩???U????NA?~?????$P??$P1?3\".???]??|?8????,????M?ַ?+E_*??????-<*B??Â??ƍP?7???\01?[ ??ΝD?????*?wqXp??l?U?????\Z????????,??Qa)????%*4???\n%H??+???L?!XI?Η?Wk????D?\0?*???َA?????\nT????빖???\n?C??j?-?Q?)?{??\".E?Hv??I?8??6?9o???\r?????q\0?=?!{?9??,V3__EF?T??????L?CѨ??7?o?j\Z?<.DZ???!???/?%7%?????>utl?,F?]\r??퀙?>??????ӌ??M=??R??juh?^?:D;7+???J????x\0???_e?I??+?K?yӥ%L??YO?????mTԞ???ϕ???\Z{???????@?}|??/??_j????k??ߺ?/?????F⣳?S?L???~?o??{?{??ep?om]?????]?ocKe??p???1v?,\0?m?????g?5Zn??????^O??]?&R???9w????m???HnJ(??I??>uT??+cU?e&OC??c?Z????g7??my?%??BK{=??p?????{???<.????Ȱ$?e?eL?Lh77???\Z??]?F?????WV????&\"C???7?G\\T:?Q? ADh?3??.??&=~?`?y\"q????贙l????s??0h=O?]??/???J?GA?~?y?????2???}?b;r6W?w+?:?G[???`D?8%Hٴ?uv~???}Ի???o?.v??#??(PQ?T^&2$???\0N???`?g?g$_???9?ud&M?X????????m,B??1>c?y?r;?VE?I2????v\r???????\0c??|?\0???e???+?q?r??䓖8?@c(?#?&?h+???̛~]\0{??8gJ\0?T????J4>?????>??T?^?\'?Qk?1?v???\\9??ni????~??~????m?c??[?C?R????k?䤞T?:\Z 5??IRL%UG??u}?A??^k????????4??Av?՜,?Miu#bs?.???\nj?J??.???\\??????;sͶ??jε??V\"B????g???!?l!=??W????p&???????Q????(?6]????1?lڙ+???????y?J?\Z?F?c@K[??&e?Ȉ?\\????^?q???\Z?S@8?}?ߨ?/$:l\Z?????m??FP?=y29g ]\07?|?k??j?A??`?ur??3???/&)6?̔??8?AB?cI????p?R?<???????O?4j? L{??G?R+M?Uu>??_?L:?Q91???w???S6???+?K\".\"??????#>*?\0C0w]?:,??AtZ?݂???sL?Y?\'??Ы?.6[\'[????W????8?~?oin?%80RI?i߫????Ԝ???&???s???A{?~?LQXr??T֝&=i\nj?s???\'????Y??L!???ӓG?9?}:,-??.?\rE4????8 ?!????I??>o?.?ʺ?|?????udV???@\\d\Z?w?j?Jy?????/D??:?????V??랦???W???\\?f?Z??2%8<$?ZCK{=o?}???3\0|??%J??Ш??͘MDH\"lz??f瘆???^?m?Y?ק?q??9>ȨR???;??gO˓Ψ?+??\"?pk?i???-?????z??+ܢL??j?l??F???\0\'K?PY{\np?Wӟ*?N?cՖ?c?t??q??;,-???k??|???Kvz?vs31i?I????#???7\04j-?ε????P?i?H?\"???}?a??n?????L[[???477S__Omm-?w>Gvn??74@!?pؕ?i{?Vk4??R?=??G?)???*???q????ʥ\"<8???\Z[*z]?>4(??x?-z?O??`b+?V5b6?\\?3?w??/?C=?j?????Zu?.?>?.??s????j\"?3???\\N]K??@?ZC|d&Ս?X,??M]??N???s??Lj??$,,???L&????Z-j???ю=?????ѣG}Tj!Do.???nܧ̞??n???~???7W\\p??7?+??c}??4??>?07S\\y???/fV?Ei??????\'?q???6?P????v?????>????CY\'???B?ϐ?L+?+??,??H?\"?^?1*B??*B?%?VK?? !H!???t????!????G??@E!?$?~???*?G+????j:;;??B\\?EE!?$]?B??*B?%??#??H?\"?^??!?G!?????$PB/i4\Z,?PC?˂*B?%?\"??H?\"?^?!?G!???F????B??*B?111TWWu1???I?\"??0j?(?=:????\'??B??u?]ǚ5k??B\\?$PB?~X?x1}??P?????Ķm?8|?0???C]1$PB?~?2e\nV???۷z^w?}7*???????????^??T退\0?/_>????C?1i?$??¸???7n<??S????.??#?P@!.F*??Gy?g?}?իWz~???????????J>??#|?A???w{??=??Cnn?W>??s/^Lrr2???>?\'O?ȑ#?X??g?}???z????u1???}\\??n???ى?l??????V???hhh?????;?#;7~??+.ay??HX??rn??????????d\"00???V?E??McrGGcƌ????????O?ٓ?ロ5k?t?ed?????!,,?]?v\rZ???p8?4iuuu<x???P??o??6V?X?ɓ\'9r??R??W?V?fǮ?!?p??1????@?F^~?e???^???|?ݾ?h4?9???\0?????ٳ)((???,\0??[oeŊ\0?ر?ٳgs??1????????????????͛?={6???|?k_???Yɫ????????瓒??W??U^y????t?M|??G???K?????k?1{?l???????;?????g???8p?G}?[??????{w??\Z??ݻw3{?l??????⩧??X?????g?ڵ????/?\'O&++?|?ͦ?q8,_???????????ݭ[????fy???p??7?nݺ?ֿ???_???<???l۶M?w?-?????犋????~Ƙ1cHOO??{??[?/W?????E????É?n??pt?1???:???o\Z\Z???Kmm-?ׯWZ???ٰaw?u???\Z&L\0?\'?S?NPSSÆ\rX?p!%%%,Z??+V?p?Bn??f????X?r%=????O<??O>Ɉ#x??g????&o????f?֭??????#))?ɓ\'?y?f?{?=?r???Kh4\ZC?::t?y???X???L????3m?4\0v??ɵ?^??d?O?˖-??^`?ҥ?g6m?????}????l?2fΜ?/??s?=??y??Gy???Y?`?=?{??ay?~????ɢE?hkk?G??z??n??w?}???p??1^?un??&?M??w??]?o??m??8??s???\'???????|?O???;??????I?ƨ?\0???.-*B?a?uLr8~?\'?/?K????1ck%11??y???{??8u??W??l6??x?\r ???????^?????_???\0??_??_??W<???\0????s?N%?ƍy?????/~??e?غu+[?neٲeJ??r??1F?\Z??ٳy???y??\'\0g??_|ѭ5???Ґ??ܧ???O?????t?effr??w?e????\Z\0?j5{???h4p??Q?~gΜ??_???W?ɲe?HJJb?ʕ?w?}<???|?+_᭷??????z+???Ϲ???{??k_?ر??ӧ???L[[\'O?$????+Wr??70?|~?ӟ??ѡ??r??%c\0\0 \0IDAT?u???\0?!?p?:&u=^\r?^x?_|?ɓ\'?????C???n?Bkk+?<???\\?ӑ??????O????????y?pvS????\0?d?????l?ub?????j???????SWW?fɒ%\'??K???o}???R???????I?+?шF?A?ן??\0f???7??Cy?;ruy?[ Tn??v???رcٲe\0?7o?b?x?)22?={?Ğ={(//g?ҥS?o??F???oQYYIlll?????R?W?v????bcc?j?<???\0̝;?9s?0gΜ??r??????w???,?]??eI???/?NZ?㕿????\\s?5<??s??ƒ??????d9QQQ}^??رL????_]????C??|?r?~?m?f3???=b?ƍ?x~?-????}?>????+W?`????z,Wff&6?????[Ul6o??&?Ǐ?h4?p8?1c?G???p\"\"\"(--U^????H????1*?T*F???Ƶ7lؠԥ\'uuuJ?ҵ?\0??g??{???SO=?-?܂N?c?̙<??#?:@?bү???@fl??Y????+??\\cUz;f\r???ʛ?O??%K???~??ɓ???dڴi?cxx8,????g?ҥl??^{???YYY?s??7???n??]???ロ??~[??>???????????????F??;::P???????????III?n?By??fɒ%<x?5k??ꫯr??7r??ƌs??^?????L?k???(????????@??_??n`?ԩTVV?g?????m۶QXX?o?[?ϟOdd$6??cǎ?i?.]???7????^????Ռ37n???oiii?x?n????????P?̙CRRAAAlܸ?#???j???}??+?غu??Zss3qqq????\';;?\'N0r?H?q???|?It:]?????͛???;????????????????{???KI??+??ͨ2FE1??????|?~JOx??/_ξ}??я~???)CmԨQ??z֯_Ogg\'????u?]??????q??/Y??V?O?S?,Y??d?5?N????+Ǐgƌ|???q?Fn??v>??c?/_NDDj??????Z??u???p8(++?駟f̘1}?:???????a???SVV??C??۹??[?~<s???/???????????|????d2\r??j???|??G??????????Ne&?ŴH?`?jﺾ???TW??*B???}f\"?;~\rF?O~?^|?E>LBB?Ϸ?Ԣ??y??\'?????d\";;???L~?????????c????&??E????Ƚ??{???O?ΪU?hoo???o&==?Y?f?s?N>?????o+i???p?M71o?<L&???tvv?????o?V?Y?r%????1???d>̛o????????$\'\'?կ~???P???oq??7?????)?ބ????SO???ϒ??Dxx8O=???կ?8q‶}??jeZ??JGG???477???H}}=??????[_ Dƨ_?;X??i?Nhh(aaa??hP3}O6n?Ȳe?8r?!!!>??p????ѣG7n??JYY&???u???x???????s\0?p8(,,??????4F??k???RN?8Ajj*????H\n\n0???N????????>O?????r???P?T?????m_??j??i@?`6?\n!D???5\ZM???|٪?????????/?|?)?\\?cʔ)????????^{??{̫}?R?HKK#--??i}?~ͅ?r?O????x???B?\'^\r???L?}??Mo????Wo?????2??<???N??Ӊ??:????M?[Tܿ???Z???B?a???_?\\??y??W}??K???q???2g?9_?~?ׂo???Z?Z!?}8_ٻw/Z???bc??\r7?0?E9???Բ\"??b?q]L\r?8??>??ŋʶ??x?\r?\Z??`Z!?p?V??Bj?ƍ,\\?pP?-?8?_Q??????bB_puS???????=.?.???~/??\"????Mo?}???????A?C??!????l6iI?O?M??!???f???뇺B\\??@!?d?Z?BM??@E!?d??}~? !D?$PB/I?????ʴ3?ZMXP??8?v??e4??u?j?*8?۬C]!D/?EE??l????\\7?????y?^P???_?BU}??윯/x???L?*??ڧ??8B?^H???sYt??%L???W????���?1??L?CU<!?E?n???d!???hQ?v??Q?TtXZY??Jk????>??S?I?1??r?????r???W?@:,-CZ!??9?????)Đ??i*??F\0p?pg??\0?anf??I??@l?H?B<[TBL1\\1?&??Ӱ?,???s0?3\Z[?<?%??0iԍ?????VǙ????֕ԗtɱc ???????|?u?????ۈ??$4(???\n?j????J?V??nژ[1DPZ?GMC9i3????0\'??S?X??]*?eN?~???? Pq???HHP?i?p?x??_*?U?]??n?H???M???.??k???͜ϻ?~Fe}\0???a??(i?\"!3?\nm?NJ?x?nԈ??/?>??W?????D???aI?L?B???x?????V@N?Lb\"҈?ILx*}\0???v??y???)(???]*?eN?~??????v?h\0]\0_??4??o?????]JTh?GZ?Vς+?Ǡ??????^b???in??ΒkS?Κr\0yE???????u?????JŌ?wx??\'s?|???D;Nj??????*ً?? :<?k\'???3ɱc(??cՖ?s?d???????sB??Ȭ!??2hQ?\r?^A?52.c\Z???@j?R?\'p???QX?%??????bF&L!???{??ɒ?\0Xm?O?? $FgS?P?^?lq 2?a?SP???-?D??(yt}Jד??8?R?p?`????c?[?ʜ???8?1?ױa?+??7(?k?hd??g?٬)?Ľ??HT??e??>a?ن?B\\6.?@?a??????????M %.??رD?&\0?\Z??[g?????????sW????97?????\"3)???t?d$_Ar??-\\???????:?????\0?;????\'1n?LQ????Iz?$T*?!)?*U?????aij?%*|j?\\\n???#??\\??K{GG7s?p3\01??,?~? ѣ?%.\"?\0C0????\0??ן???g?_?b? 3y\Z:??.???+?J??????]?*]WC??{s[??{ͭ瞻Ƣ?X:ۻl?q??#??3??#??\\?ߴȐD?u??????r?M彪?B????f>\n?Qo???\\y??|??????5Z::??t?_?d??H?È?\\rү?Nn?\\??m??>?;?S?t?,)??(,?p?y\\??tͥ??;B????!???Tj?J鰴b?1>s??)?>\n@p`???WH?u\'im?@?R1u?Ml???\0L?? ?p?`????????~?i9NJ?p????N?蚇Q?5?\'??S?????Ӵ??c\n\';?j?KvQ^s?䘱?L?\n@Cs%?M?t?Vk3???^Ggg?PG\\b??G?????;????]7????݊QoR??:?.?mu4??q????˘?U??d\\?\\l6+?!?\0???Mcs?%?X2????JCK%ɱc\0hn???.?Fߧt=?Z-l??:g<@xH<???w4????ٴ?U?v??s???ܩ?!.2?F??ᠾ??²}???\n6?U??-?B?!?KrM????!*|?K??f?C]?A%???3?@???8?>+ڂmC\'WO??????F彶?&vz?/??R^[??%??ML????h?????X???\0??-?X???Gl?H?PY{?Ov? ?Ղ?j?S??:?9v??yӿ?A?)?>??GN????u??1????Z???R??M \"4???Q?????M?\\??????K???)?L?1?ۘ?K?*B??eѢp??NN??IHP4a?X?\Z???ԷTt?S??jf?????[D??@????B,]ƫ????????\'4(T*?Z??k*?W???<?cُl?h?\"? 5???\\N]K9??#?+?????|??>���Yp`s?~?Z???ɖoRP??6???n \'}&?Q?̜t?2?[???@E??l???j?Z??????Niu???7?{?h??8?vj\Z?P?x?_???];?????g?Z??WR}?????2?J??Ag????U?wh???\0?Mg7r.j????Je!??_?C?.Rk?̙?MP?(?:????\\;a} ?J???VMN???GeQ?P??c?tvvք??=@_?????<e?t_o?\0??Ĭ??D???Qϖ?o9?ĘlZ???q蝳y?\\?\r_?@Rt9i????????<?+S^s?m???~]E?&1m?mD?$R?Tƾ㫕?BL????w??@E???qiJr?s?`c???l{??g??N????1???^HZ?D??8U????Z&?]??t??*u???=?I?P\\q?q?s1??@r?X?\Z?Z5? ?1???{???|&f?@LD?R??\'|?=Gާ???W?I??????~?1?*9v,?Me$ǎ???H Tz?8o?p????R?;ٺ?t?_8p????.\"$???N????Mv?5???i?+?Qk???+??,???%???i4ZBMαDUuݺ?\0j\Z?p?`#G\n6v?a5v?,?T?Q]_H^?e????)J?Ԅ?\0?;?8Y??ci ??+`Ӿ?8zz???>???\0??????????~??<???6 s?~?ބ?????O?q?m?*?28?\']?0e?T*5???`?s???74??01??n۸P??E????\\????D???Wh5:&?v?????ndz??#???\Z????u??}!6????>EI?Q????C??OpN??\0鉓\0?/??m?PcK?^??2Z? #?\n?G\ZGI?Q?`?Պ??m?!$?d?/o?????????w????ͷk??Z\'???????m????Z͔?K?$,8??6.T???\n%??n???=?????\'??? 5?`\n?\'z???????&.z??&?w???\'u?%A\n??g?AHP4?a#? :<??^c\'?T?X??t?>??pS??˅n?\0(?I>?5TPv?.?-??T???w_?g???z?_??:¸?s???????\")6???~?5\'0???\\??4?Ӟ?\r???w??@E??q?3?[h77`&\"$??4??$D;[#??wxt?Ի??/???i?????t??1\"m?V薾????y??h?y????mܧ?v]1?????t??Z?ᖙ???|????f?T?? 9vl????~?????\\\0???n$P???&. ??*??#H??????g???2k?=X:=O?vG??K??v?d$M!=a2\0y?[{ӵ??????Z?k???&?]??$4???x\0?Z??t?_J?8%H?z?_???e????=??????????;?H?\"???7M\\??CY?p??G?J???HHP??~???L\0?n?b5?i??A? ѣH;;~?X???f?Lg???\"80??W??z??????s?I\0?N????,?o?PU?vs3\0W?_Jz?$b?S?~??????T\'?ȳS??9??a?`!3?9VFE??o??????H??\\8?b??]?OTh2?n<? WSK/?}?4g<??ԫ??o{A?] _dh*T??????Ou??s?}?s?`n??x?4?M?|vve??8Y?sg] ] M???}?z?t?3???hQV??y?]e`?kV\r??????c?Eު>?&?l????3???`?+s??i?h$?ڧzպ?ų??G??+ =q2??ev_?כ?y???;?X??^/!|k@-*??EU?T??F????pI???F??+?g???{ӟ??u?P?Æ/^??M?i?!x:rz?????V>???E?nQp???A??On?x?v???\Z?v[??=G?PҜ,?Ce?)?ٕj???IX?????vI??&a??_{?_??b???P]_???Iy? ??z??\Z?Z&Dž?M\n???л?????HgL?u?Te?n?,\"?ZKF?T???-o??p#]?B?ϰkQq??JL??Q??????b?????cE[8V??S1?#?X۩n<C{<???_p{g*?0.c\0?O}?k:???????A@DHՍE?V??07??????1??0[????z??ç7p????S??]SG?@??\\=?W?M?_g˗o?Iuc???;????????짽????w??@E?ցJyM>#???=Z T???(?9AF??n??j?L?^Bl?H??aT?PV?ǩ?}??-\0D?? 7c\0???????L???f?D?vN??q??6]&d]Oj????????ϸv??Ft??R?6?t\\??&MM??ʿ?h?&????,rR?`롷?N????ɴw4?W????$F?fL?,?C⩨?gۡ???@.?O_?GdH\"٩נ>?fIr??b);????~IC???????~?`0Ä?\0?V????????n+?ve???زs??$????w?˨?\Z?jO??????Z?贙?`???????n?????R?????.?t??\'?:P)?>?TbF+?%F?V??\Z?h?z???y\"Ò?גc?0y?\"j\Z?y??G?X??gJ?s?̰?x???I?II?Q???q???m,??aƤ?Tҍ??%>:?ѩ?\0?e?+?P???>?\'?\'NV?&D?\"7s.?19|??%?Adh?RΈ?D??c3f?y?\\3?.e ej?x?c???O?s>}????\\=?k????c?cf??@???a??{?经???8Y,m9??I???ý7????ʺ??s??p6??7?EE?ցJCs9???F???\Z?X?J?R?Õ?Ȅ)J@????p??0??Ә4zQa)???r??N?Ϥčc?^?Mڦ??????ClD\Z?u???ʹ??J?R?P̑ӛH??)?&f^??N~Ω?=d?]C?????}e?G9R???3 ???<?. ѣH??F?e֔{?+?JE?I??܄) ?Ęl??)???W>??%5y???/?u֓??\Z????тMXl?-8??K+\r͕?u4p?xG?????|\r?4?x?E??>??WN??#9vɱc1ꃩk*??????5m??~}ݿÕ*B?ϰT??d&O#>r??y?F?S?TAkGC??v??ߡ??Ğc?A~*?Z??YXp|??8?@cK%7_?cgZS?u?????g??;?f?^?4?m??<???τQ<??p?^*??X??\0N??$1z4???9?[\0?j???e?????#in?????5\r??>?9 $0?R???υ?ǩҽ8Ύ??k*S?t?9tj?N??`???Ci<?*????>X?????䦄B?ϰ???????8p?Vk(?>?c????T?22i*?\\?Q?)??tNܹ???}????j?}?fDH?lMq???ҽ??????9?Y?<???\\k~?kl=7ޢ?????H???p\r?ls[F^s?\0ڟ|.???rSB!?i?*?U?.????ʂ^=u?\0?????9?A?5?p8??/d?j???\'?Z\n??M?.\0sg????8o?\ZSb?zn???a????????ۯ????e*????\\h!?EE??7͟??????٭$D?R^??E???_C?5??^????????Ф?*??m6?Tj?!&<\r?Z?L+MO????b5??^?) ???kY???~??|???ÕJ?\Z?c?????^?s}??~?3p?Z-T?`?1\"~<??v???zL??홊?4??i?k?m???m???,?O????ƍ?˸?yt?Z?+;?\Z?#0\Z?Y:??Y??M???r?s??v>?w?b8q??F?\"?i?????���:???L?j\r??ǡ?.???b?br?L???Gbt????Z?}u???/?}????E??9?ܳJ[?m?????%80????oT???V???p????.?\'Ǡ???VG?)?I?7?7??}???M????.??K??#?\\???R?U?5?g?^??*?Z˸?9D?$????hhvN??J??u?}ݦ?j????$?p+mM???9rz??zIٖky??????i??Ѩ??Ge???hn???uOSq?%??\\?Y+?]GV`?Y?i\r?Ge??[?ˌt???*??ѧ?v??Պ?l??????f??????????U;~èqq?b??Р?:#?\r?C??1???\0\0 \0IDATi3Yt??\0??????t-<8?PSM?U?5??[k?@?:?ZC?!?J?1;h ????{?YZB???????\n]?c\"## #$$??D`` ??V;??ԛo?ɧ?~?믿!z??????S??b?;?h?>Pq????m^;a?s?a??8V??@c(????uZ?T5v?\\}s??>/?`?c??hi???????ݎV??v|?y>Ң\"???$PQ?T??tv??h.??$?;rz=F]O?!???c??????v?xի;?\n!?g봣?\0ݏQ??p8d??~?U??>?9?T??n2???V??xy.V?Tʫ?$#i*I?c??̤??????Zϙ??C]D!.y?mL??c??т?+\\!??ρJO??\\?FCdp\Z??e?EJ9/\nM???;??}??uQ??,5?w????b?????B????4?W*\Z??Z?Z?&&<s?\r??Һ????`1[1?ۈ ?@?Ѡ?h??????n???????o???T*?Z-\Z??VOTPu5??RP!?8???v\"?2?i?J??:Nu?\n??????բ?z???V?V?%&t-?X?2\"^??V????bB???tʱ?=X?e?R^^Ξ={?UE??????V?E?ӡ?? 1?0?3??,??????S\rD?!??^??o???</^,?T???>*]g??)?@E??1?-????YL!Χ???-?????X?j?uT|e??͌7?g?B??_-*? ??0?F#F??????h冷F?\rB???V\Zj[H??܃?N???/????Ǐs??7??B??z??+Pq?Mq???F0\Z???BH?KMy?-?Qn!?e????ڊRL?c\n\nS??F??#Hq??????ox???j/?{?\nq???`ZW????????\0 \n\n\"$8?8?U4V[?,k?1+B?p8?T??Pm#V????{?ҵ?g?-*????[??o~??>???B?^??+:??ժ)???? :;;?Z??l6??+???KKc-?#B?JE????6?EM?@?~:!???L&???\n\nRZUC?@e??V+w?q???o ?Am?}???K?F??n?V?????lX?V?v??P?L????3\'Oa\n??|??H?=??NEI3-?l# \rH&$$???`L&???ZT??u?????1?????????\Z??5??TH B???\Z? ((\"bC??\"??\rP?????OD~:Ҥ?4?^B/ %?????#??͒?M(?????d?f????3gΜs&?\'?x?C????( eRT?DG?S?Z??n??J??n??t??\\?k=??A䤝?|f2??\Zkh?h%???D???ғ?dg?P?G???UL|}}??????OTT\\}Tܭ)eUT?N\'?G?????l߾]?M???dJ???d2Q?P(8??|?WE@?iQ?T??0??\']#+3?\n??ԡ?)Q??R^ ?*????b?a??HM?c?????P??~????\"XT????j???????W?ҥK233?v?\Z\Z???-???(?2/\r?r9N??R?O9qUR\\oEE%/J?d??d??ٔJ?1?2dv4\Z5>j?j9\ne?K???? ???Ӊ???}?r_???&??\n?6rk8:E?????j??A9q???S?kM??l??e˖?`??{?9f̘ᱨ! ??Q&EE????)TPR\\??p????F#\Z??Y??Z?Ɋ?n¢?\"5#?f??;?l8p????<??&??*HTr??T\np(q???m?P9?P??sS?T?\"\n]?\n]_?%E?Պ9T?%,\r???:?v??W?^?;w??իWL\'HHH???q?¶????f?a?ٰZ???f?f3F??ш?`?`0??????F#f??Ʉ?j?b??e?F??$?;?~?Z?\\????????U\\`????s?t=??57?k\"IwE?ղ⚿?=?GPT$?????)?W?0?8Ѫ?:dM?j??Z???X,QYqu??r]?IQ??p??\\\'a?P???? ?n???Kiҹ}qU\Z?-??Gr???\"($??UA?u??HJ??ĝC???ݕ??ה????F?-/?ETV??k????%q??_X?????D?넗?????J???OAg??:ۻf?.HYqM?瘝?ZQ$%EB???#qv c??E???3??p??@?????j_????V@RXn#\\\'?wAY??\\}\\????h?e??t???$n>????֟`yu]̨T*??D?~\"{?4?B?II?????????{??D????:??d2qP|[\\?)?>\nE??W?֥? ?ݢ?z?k?swe?????Z??\"qsq??]?{?sxܕ?B?O!q????3|\\?Y ?;?r)*?????????{$????t?V??????5???_T$n=\\\'\r!??????\Z?gw?¸*&?2%????0??HT.?)\r?w??l?*+????9ֵ,????½?pW?%$$n_ʬ???n???p????x~??w??\\E???Y???Qő?dx?| \n\n?????ݻ7?jպau\r?Mf?2f??IHH`?ƍlݶ???dz??˪?L&??ן??Pz??B?z?D?????*??UV&M????ˉ????{????4iҤ\"?#!!Q??\\Pt??Wb????????,_?mHC?(???5:??\n?J?p:q??؍?ؒ?aL8??όd??!h4\ZqE\r?m?????6l܄6?!??HI????cO>???i^zy?F?B?Ӊ[G????QV?N\'qqq?~?????+VάY??{?NBB? ʬ??+)??IVV????d?u??(??+??wv?????J??{????DŽ???????Ǟ բA!ɖD????@V?E.\\?????\rQ@?~,?????ʕ+?8q\"Çg֬Y?ygO????JVV?????Iϊ???Hv?jx????_??t?|?v;999?j?J?-?Ɏ?B????5?ʊ??my?\0??Dpp0\n?????Z??j/!!Qe~rAI1\Z????[G??&?R?ۤ\'i/~^??????3g?\"8J?-???mғ?????V???~?{y?h4dggӸqc?x?\r?\'JB??(??R?5?l6s??EV??\'?:?*??w8??ꫯ???3[,???X?v-?0I?$\nGݠ#?}?9?ϟ?l6c?ZE?kO&?ܽ{7?|? /???Gʓ??(?2YTk????L&/^?6???7 Qf^?hC?t??|gD-[?mh#I?$?D???6?!K?,?^?=?P&?ѧO>??s??\'!!Q4?VT\\sX?l6,f??u?7????:JT!???lظ)?A?6n?dK?D(?#Y??/Ѣ\"XU\\?=?СCyꩧ<R???Dєy?????d2??t?w@E?S????????,??m0HNN?dK?D(??$\'%???????)??$$$*??i?)˅}_??b?X?gg!??*??U?F?A?-ZTL&??lI?$J?\\?C????G?թ?Sʊ?`NBB??)UfZw?????t:??[?F???d2!??E??dK?$??z\"?Z?-*???P(?#$$$*?R+*@>????HHx??|?!?%??$v????{?R??f?y???PjE???A?? Ob6??U?D???dOF???riܓ??$J}(?{.Aa?Y?k5(rf&k?\n?[?B?Va???2??ܐ?@]n&ɴC?e??\n??*+[?ۦV*hQ?????gs??ei0+?$p;?Zk??P7?:? ?$e?x?lo?\n???L?!?~x{????)>U !,??S?=?dQ???<J??????~?????q?ԫ-????[_/`???y?w;??]???y\0to???c?p9??c?Y?????\'?2?ە|?b{??Ӵ?<?:!???R2?y??e??y????\\??ӎ?\0_~Z??????E???Ѣļ??????{W?ҙ<?7v=[????xs䏙?\'?????^??????:?a?~ή?}??\r???]O??(?Ji[RB??(WfZ??o????]???{wE? \"?~Z??ϛ?N\Z????xtj5?NI?\Z|??&&??_????\0_>~?q????=+?g??k???_???U?%?ue?????>x??w5?ҵd>?uv?????g????ѕR??[???????^)???p?<mQ????EEB??(?֏??9?f??m?x\Z??A?z??k5X?v\"?jr?T?\Z??2?.;???m?\\Ef??\\E%$Џi?>H?f???6?io,?/Z??-Z??^x?N-#?r=????ɑWo?CTX\r?}??x%?????6?hT??\Z???R?Bx?`^??7.\\OE?V???Qd??<3?GjU???W???x??_gx??x??,\\??E?0??6?ӑ_???Et]zvhµ?f?????r??R?????ݮ1??}8?????W?vi???wa??;hN???8}?\Z?k0?š|?h=???W?7V2?v;???ۉ7??O?j~???Ͻ?P|??]y{?C?؝m?ϠS?y?????\"?J???K,ٴ?????[;o??O???Mf??{??6?C\\R~?cP?V<??*????????hX/?f?a???0?δ?hQ??????~14?ʶO?Ê?ħd\0?[U??D??ᜍK??%[xqxv:??l????ʉ?5?SH[??G???~?l?r=O???hפ?z\n???[ЪQ8-?????yZ5\n?l????y?J?>OXHu?\\O#?fu??MH???>?M,???=?ȓz?????t9??????Rp8ԯS?_g?e????>r?ϖl?w?]?a4???N?k?Q*?tlAjFny?^*:???ID-?|???ftZ/?N?j??ǖ?ԮH?4n?V?F&?V?%???m?l??r?~?H??m??j#15??M????1<??W?<v???tlAh??ᵂ???NJz6?<?xP?/??\"??s?)*???d??|??????fr??Θ??ב??l?$?1?W[toE?S??Ӟ?K?h??F\0^??ˀ?-?\'o+ ?z\0?_y?????Tc?S?HH?????Ҩ~-:???/?:?,*?N-\"iY??,=???֝?z?<:?k\0~?=??\rjc?بE?0}u$?fTx?U4??#)???D?Q?Ԋ??úm?i\0?7?ந0\06?9Nz?????iޠZ???^?h?2?W{?B??q?qb??E?\'???72螶????^??F?aSi>d*?Nš?z1?_?\r??0????*?v]ZG3w?v??[l???վ???i?6?Z\r?Ƃ?0fp?|פdd?z?4?5?箠????}1th\\?nm???E??????l>_??B?????+#(З ?-??d???]K??wd??????\'q???]??d??p%??-?ÁÑ۞?@_?9z??#f???O8z&?ZI??i?(?.?????N?\'ޥ???r?r??ԠEd?X?Z???w???ș??j\'\0??+??????n??0??/h??X?hՐ?\rjs%1?v??C??fr?rR?;?&QVW\n???AB???#r@~*??֧YdR2???????Wi]?v¶O?u??|F?}?h?0?;?ьB!?}?? ??8B?ь?je??\04)?߮?A?g?0??Ul??F????k1o?0F\r?Z??\\??Į??1Z?|?t+\0?j????z?a2?&.&??r?A\0\"?jЪa]\0?9???L?N\'K???yt?|?ٲ7?e?q11?T??(?Ӊ5ϧF?*?as????bӞ}????E?\\?A?TФ~m\0v:G??L??L?>???78|??XF??ȖC?J??Sq\0h??E?w??X\0??V???մA?=7?=A??@RVn?\\??W1??y ????PT??Lzf???ҸA-????#9q?\n!Ato??my?):?\\s?????@???8s)??O???lv -6?rWO?\"???hQ???W+????????i?o?\0?viQ`??ʂ\'????ꐖc??̪?ᚣ*5K/???Wg?B?w????Z??Z????W?\n??DىOȵJE?)????`ʈ??~?\0????i#??*???r(6N?6?7??ha??d??c2csq\\O?ҋ\Z{???b??e\n???x?n?M?kMEV??|T$$*?2)*mV--N??????GK????[u;??3?e$W?8s5ה-lq;{?Ǧ}?cӾaΏ????\r8??i???;??]?\\KK\\?D?J˨0??l??;\Ze?Ae?f??z?孼??z?\0?\n??D???;?G?S??d2?\'g??@s?-???r?y?z*??ر?u??&=5_^?-??ƭ\"_?aG?6???{??g?ЪT?????ғz??hT?1-??????c??ͯ????˸??0?\"????Y+?B?=ת֥u4?<m?g?&r?ʢ\"?)?GEB??(?Y??????n?p?z?F.?s✠?\\rC ? `???<???hE|B\n~:-??Nrz?G???Ѿ1??1?????*we??7???؋\\MJ\'?nM?-|??;#??x?Gk\0V??5???O?k????Q?m>?}?\n??T?????q?tC?????ؼ??5?n????_??O??p:???v?sW?IHΠq??,??<GN???}?\0X?jW?}h?K6??n??z\'q79??V????????ׁ??uX??+??q?????#+??7˶b0Y?Xm??4toM??\rD?D.????Xr?&???f?臐???>���?ƹk?T???x???:ʛ?>@??0?}2 ??I????OeR?8%?ʛB_?Q???<?dQ??>??d??S??$((?????k>?q??x??+IL?`?????я? !%?f?????l?~\Z5??C???fw0??e??_???9&3ϼ?=??] 8З???tA?T0?:????n?6.\\N\"????<ܝ%??Z@??S??c41b`Wt?\Z6?:ƗK???f???tmM?.wa?ژ??r?_J?d?1j?\\??L???_???n????,??o9???`?#=?\\47?[I?JJ???C>?PluC?ԫ-~:??]gĴo?KJ\'9K?O+?F???ͭҶ???\":?t????/\"3????]y???$?d2n?OV?l????~????D???l????\\?(???LlV?㔴?#!QyȜ%\\?:l6f???Hvv6??٤???????????壘??GQ)?Dծ??b?Bbj?+??5?s55?D??k??V?\Zz????SL_?G??|C???? ???&?N????O ???p?K?>aH/^q??5724??)??R?H5??]N$?\\?՟Z? ?[K??X??<ɕu1o?<q2?<y?m\'[???Y???Y\\LL?A??5?yk?<&\n?j??b?q?jr??5?[CX?j-V?]˽????.\\Y??/&((?????tx{{????J?*??????????P?%$$\n??yT??vG?wJ???sC?w??g??????^s?\0????????f???4U??l=?\'/?y??DF???q Q??e?瓰;??o?n|?pw?v??ǫ^\n??P?TR\n} ?J?J+*?\Zv???l?B\"3 7)3ۀ?&Y;$?L?^Mfܬ?ݞ?=۠V)9y?*?o??].??*???\"!QyxLQ??d8?N䷡_?????T?\r?R?5_??~[??K??\0GGI?*??{??vϱ?]?rQ??T????D?Q?<*?` ?ˑ?dh?}p??????D?8Lz??ޢ\\)\n4Z?$[%?aң??!?K ??:?J[??G??L???:b7??g?H?\\??L????墲???/ɖD??3??v??䪰x?R??\\??????(??[?9^d2?ڶ??t????Z??вyS?r9\n??\\N˻?J?%Q\"̉g?ж????+(???H[??G???r\"((??]?tƔp\n??P|!`70%??m۶???P(h߾=?k?lIM????s???\'?oO?V???~$$*?2o???r?J%\n??JE͚5iӺ\r?3E\'??(????h95k?D?V?R?P??ӢEs??lIA??u?iݚ??ql?]ױ???j???O?i?^?ʞ={8w(IT?dQq]?(?J?J%j??aÆ??g?}bK?IH??yb??x?<HTP???P?ը?j?<?-?LI?$\n ;O~}t(*?J| J??-*??`0T??????t?ڕ:u?CTT??????~?????}??q?t?1&&? ?LƪU?*?~????\r>???????O????x?o?s?0oJ??\n?B?@?T??Z???ח?Ɠ???H?z?b??\r???5\'1s?Lt:Z??F???????????Ǒ-ɖ???h,?̝;?N\'*??\"?u??SʊF??cǎ,^??#?????Nff&K?.%33??\'OҺuk?|?I8P)u(?&M???~\0\0 \0IDAT⫯?r?{^?u???*??,`ٲe7????<R????>|?Gʒ(??QV%v??B!**?5E?Ѡ?h???eƌ,]??;?;?1??Q(??ktR.?*????a?c7d`I<?!?$M?6???????+**j?\Z?B??f\"????>}:?W??d??R??<??8???D%?բRQ?ʇ~Ȑ!C0`\0ժU?H?q??????/?? A???gaѢE?f?\ZڴiSa??4J??9s?Tʽ???_?ߍD?P??o?[>?\0 ()f??V??j?n???????9?r??v?rr0????w\"??\'???Ѣ???it??\"44t:]>EE.??p8n?\0*?VU?`???????ooo?\Z窬T?CmLLS?N?A??>}??5kz?lW??s?^r?I??t|????j?*>??S6m?$^?r?J???֮]KBB???8S?N??/?d?ΝDDD????ӱcG\0ƍG?8u?˗/G.?3z?h&N?(?????y饗ؿ?<??ӧ???{?w?Ν?5k???O?&$$??G?r??????????ׯ3f? &&?ɓ\'??n???????ɡ??̟??B@NN???\Z?7o&**???{????ѣGS??MJJ??W_????& ?|?)S????~?:|?7n?`0йsgF?\ZE?Ν???????INN&&&???????\Z5j???o???0au?????_f?̛֭7?3f0e?z????/?\\l=???????ڵk ??????_?x???J?3?\nʊ?D?R????ʧ??\\.?Z?jt????,~?p8???Jx6??m????U??F?Ѡ?j??t7(*??qN??J%N2μ̣?l???F~|}}???????V[??OE0f?????ر#_|???w???ѴiS??4i?ϟ?W?^?o?///?x? ????D????|????????0\Z?lٲ?#G?0}?tƌ??_M?=????~???۷??2t?P???k???ϫ???B???_$>>?:??c?1{?l?]?Ƅ ?p?K?.`???<???1v?X???9t???ь\Z5\n????-[?0a\0\'O???ᆪcǎ̛7?U?V??_иqcƎ?ZF???x??7?r?\n?F?\"99?x?Ⱦ?|??\rN?AAA?t:?z=?۷?V?Z???{8?N^}?UN?:ů??\n??#HNNf?ԩԨQ????ӷo_RSS?֭??{/K?,aʔ)T?V???X?????+??????c?H?.]?ڵk??HII?w?ތ7??+W?s?NfΜI?:ux??\'K%K?+?VT???ē???m?j5&? ??**2N?S?P$?|??D?J??{??hD???[//?|W?HWk?$[w>%??|[??V??RQ???N?J?V??8q\"}??Ǐ?w??h?Z???V?ٲeS?N??????w?E?Vӭ[7F??#?<R??&L?????@]?????????? **?????\\N?=?|?2???>/??\"s?̡Z?j|???by\Z???}?r??q?5k@?f??????k?U?F?z??ҥ&Ӎq?z?j\n}??????<x?͛7?}?vbcciܸ?X?!C????l????-Z???????OILL?СC@߾}?;w.???4i҄?{L?Z?h4?\\????8???hҤ \Z??T\n??`?믿??9s?Y???xrrr???{i?ڵ?aÆUƚe??q:?(\nq??p8?))B?s??V??`?X??l?l6q2??)?J?W\Z\n{Ho?I?=?]?P??ǂ%???[?`?հ?EE?WVǂ?? [UA?Ci??U?r\rS?He?????Ӈ%K?0?|}?Q?????t̝;?.]??????H~??Wl6G?a???,Z??!C?0k֬|[\r?ѹsg?w?ZM?v?8u???^ǎ?M?]?t???>C??s???w??????AQ<xp??צMq??N?:$&&p??a?U?&*)\0?z??w}a????????{/$$??N a????g&? ??Ill,???̟??={?0m?4.]?Ķmۀ?=_2?L?5*I=?u?F???E???{?߿?\r\Z4(sn7?t(??UE0?;??̳^^^X,?fs>?KU[???\r??&?®??????}?f?\Z\Z*Ȇr,??????0??d2Q?d?}??v???*??4?#XW*Ӛ⎫?y??[?n%>>???z\n?RI?6mhӦ\r?\'O?S?N????**iii7???o?ٌZ?.?s???T*IOO?v???>?Z?@~y???-U??>effޠl?????sa4hРPg???tqG@@?V???oR?N\0z??͉\'8p :t???:?$Mq?d2:??????t8p?u?ֱb?\n?͛?ĉ????KmM?])?֏0? ?\n?(??Z??l6??h?M&r??%??AKX???????????s????n????M?3&?d/l?\0?v?i?Z??&[UU?Ci?G?M????4k\n??իy饗?_?>???*?{?F??x??Ç3y?dxÄݱcGq?EP܅?L???p?ԩS?u?]\0?l6?o??s?=??sW6n?H?z????\"\"\"B?*?ݻ?F?\Z???S?~}.^???˗ rz?Fc?ʍ??????̜9S????T~??\'jժ????æM?ػw/:t\0?\n_Dxyy嫗?n?̙3??rY?q??!?;ƓO>ɀp:??7?ٳgK?Jq??C?)??B?????????vLK??`?NN???????+X\\?W?߂???#iQ?VE?>?\n?????Va??H\r?J??j??n?=e?*?@y?????GE[Sf̘????ȗ_~I?~?<^????y??w2d_|??F֮]????,?:4o???¬Y?x饗غu+???\r֍?ӧӥK????={6F??aÆ???X?????s????l?2V?^͋/??ȑ#>|87n?w???????GѵkWQ?)?NG|||?r0`\0??????+̙3???3?<S?r?=z4???s??a???\\?v?ɓ\'s??)^z?%|||?\\???ٳ???[?V$?NGVV???Ѽys???{֯_Oǎy?���M\nX\\=???y??g ?_?~8?N?^?Z??͝J??~\n?;vMg??HIIaժU??a??I?s?o??\"??dx?| \n???ݸ?wojժ??/??\\W??D,L? lظ???v????Ak??L&C??Kpp\r??ۋP?^?|?1?>B?N?w?l?Dܷ??(?ɏ??]?(ee?
|???\Z??ʕ+;v,QQQh?Z?f3??ь9R?IҼysF???o?????i???\Z_~?e??bbb?_?>2???? ???+Z?h!~~???3j?(q?~??G? zȐ!=z??}?DZZ\Z-[?dٲeE?aذa???:????ʕ+3f1m?4N?8!?D???;???_3q?Df͚??` &&F?nڴ)cǎ塇????J?????ɓ?ر#iii??Ջ?? jԨ??ӧ;v,K?,?_?~?t:\r\ZTldRq??????g??? ((??@PP?hݩ\nȜe?\\M͂??b?`?Z1??dgg3??wX?f\rڐ?(?#Qh???\\.?????ؒ?aL8??ό??aC?j??????\\\'(?????????І?>}|C?????????gF????#?[p???d??2?:?\nV??Y?Cq????S?>*?w?f???={?R????vΟ?O||<M?6%44??????0\Z?7??\\?p????9Bdd$gϞ?y?????M?6b??\'NP?^=Ѳ?Jjj*\'O?$,,??????`0`??J?V?g?j5z?^?b???s??eџ?????p??1|||\n?\n??????D?F???d??v._?L?z????0)))??z??EjԨ????????????дiS??????ۓ2+*???n?????j%##?\"ŤBݠ#\n??k?U ?ـ??_d%]b߾}x{{??/??TNN?{?T?M????v???\rd%^?ԩS???????%l????U??搹e?<?ˏ?????????????0z?hF????+WE?y??^#(*3gά??NJJ\n|????O?[o?ł?r??M??DeP???UIqUT?v;9994jԈ?F??=???Y^???y??<??????ς?l?}??$[4????}???Ư?@??[???Adgg?J??c?Z??ʺ?e???@yp?????|>K?~K??d2?gϞ*??x3 \n\nb?̙<??ӄ?????Ϛ5knv?$*?R[TE?}??h4??+??z?^??/H?P?\'????^?z?1????oM???G???????xrO?҇qcLj[B?_?\r??Ted?]\\?b??iw????-?җǿ ?.????SX?V?j?m?d?ٸr?\n?k?F?Rx͵k????& ??kW<V??}??@?ƍ?T³?N?Wk??f}R?f3?ϟ?O????x?^?y??@??_X?|)aaa??r??e?????#?Xh????oh???c?]UUʃ?l }?/?_??????(aH??z?????ҥKHHx?2=??[>V?????ŋц6?O???Fڐe˖c2?0\Z??F?-[?6????B??,Y??`@?ף??Y?d????(?,?>??k狯??϶o^??n?nR\n???C?[?>\Z?:*??ц4dɒ%??f?x?Sy??IHHT<?VT\\?6?l6я`???(?\"*??Uep$6n?`0????????>???.???ŧ?6??Ok?a?vB????Lj ??Z?{?O??dͺ?D?&?_?5A?\'?Z??n?HHHx?R?Qq????Db?u|?t??zV9Z??????(\n?v;?ɉ?Խ???=IUmwA??/?Z??SnR-o\r???!*?&篧po?&Lz?5?????????cv?O??X?f?>ԍ??i_.???;ӽ]#?=~???[??j?nP\0??=L?Fu????G?b?ѳ?hP?w?=??m??Ѷ1a!??f?V?6????la?G?]?????xj@lv;???Mr3?U??????_72鉾???qI?7?YR\"&?IL?&dj?b?v??D)?%$$?O??Z??)?7?`Q?X,賳?kJ3.Q4r??>[\\M?L&?9?w|W?vDY??f?/!?4?S?N-\"?Xm9s?&uC????????}?,=;4???\0\r?m???L\0:4?d????\"?B??^mֻ2????K?VQ?}?4???????D?\n??[Cۦ\r?2??5^D??`??G???`?-\Z??θA??:y?}$?dY?&_M?F?,??ujV?e?z|???ԫG?\Z???\"?\\??E?\'?Z??3??D%Q.?????t:??$S7y^???d)?BW?vDY?bۏ??{a?ҳ?p?r9????K ?-V??Mꆈ?>????p????@?>?QXM?|?\n?kנmú4?_?o??????]\rX0s?n͞??\0?q?f?????\r???1?z??<??߿A?:??{~???}??teڸ???+???[?%???k?[,???0. ?9B????Jl6???QBB?b)??䳨ʊD?`6?????q?SU?]???y???l?Y?&C?v`???Y??uB??Ⓣ???{ %3??lV??͎ޘ?l+r?j?f_3?\'c???W????救??{?ɵ??????w?C????Йܟ?\Z?/Q=Y??@%???*?O?5??ܣ?f???~$$*?R+*?3?{?OI ?%Co?d????U??,?\'?l?J^\n9k?珸??}?)M_??Ꝣ?J??:4?\n#?[Cz??????+???u$?gqw??O?u???\0|?d+????ӛ?DԩQ|n\"ӠV?.]#<O 9y)?????ֳk?h?~?!(*?I?<?#m?HHT??]?g???ҳ-??3??@_lv;??????_9y9???*J??V?r???I4?,?܋????????ޑ????ss??>ƙ?I??X_???ޣ?r?JR??*-ŵ{???r?z\Z)????Ԍ?ç0??v??????ML?dîc???J,6?*>????\\0ȝ\\?9[??V??(??r59??Qa??h???9????ý????=Lx?/?k?k̼??t:9v?\nz??{c?b??Xz?m???;????????θA??n?????p???Y???r?S@Kv]XyR???~$$*??i?fSw_???????+???|?x+~>Z?݊?M 橙???>??#?mIVӿ??K?j~Ԭ??о8q?\n??? )=?????է?\'.!?3W??v??9F?2J?)???ڝ?m$-KOF???l?\r??????]F?T?\'?.?x?W???b?V?ֳ???Z?߭U?????Nid??????Y??,?z?z??y???<tO?F3o}??L????%g??w!????o??`??Y????/????Ԍl??:??ɗ?n\"Mo?H=???w?S~*??\"!Qy?ٙ?u?R??????rW??????????ό?mC?m?$?66?_???L~X??_??=\n??7Gp=%??L?n-8~?\n.\\???)\0<;?+??????dۿ??r??r?:s,W?x????r,VϿ?K?a?3?=̙???,V?E?aĻ?\Z&9???<xw+???3?c???a?vV?>@??0?zv\0?kVc߱,\\???W?X0}4\'.\\??/?\00{? ???b?[_??w8≝ű`?n\0ZG?1?o???????P7(?????\ra?\0?c??Ѿ?a?{????E??????ho\0?ڋ?l\rj?\'??}?\Z>̙0???ѵe4?uk?q?qf??\Z??@???8???X??\0?Z/B?9??b?Q?vgfH?ԓ???5\\??? ??q\0??_`??q4?_?H?(??ug@??\\MJ\'95??????/?`?{?k?`Ԡ???]#??\'?h?.V?>Vl?K?\0O???\r????b^?b??????????\"\"4???4Q???j)o|?T???????篧v?$????O?q?I??m4??7????|?p-?-\\+????????|?;?|?;\0?t?X?vq???zN?f9S?Y^h??>>yڢ\"??HHTe?du5?6\0?d2??C??<wY|???{h???,?~?j:o~}?9?G?e??\'???2g?????fQux??z?%5#?=Z1r`n??A?[????p:??g3?\'??y???aD??wz??hT??????F?a?????x?Ԫ\"?$?jT?m?̝0???Z6\n??q???dT?y??9?hT???^??n-???Q??6?\nx??vhTJT\n9?z??h??QЪ??VZ?χO^{??ƒ??ҫc^~?????????)?d?M?\n?e?zh?*??^?mڀic???G?a̐??4m\0??qӣC.\\Ifp??<z_\'?F?.???j??c?w?G????_o-!??үs?I??N^?????[w\"C?h\Z???\'??O,~:?????>n1-#?y?.???=a>\Z?b?^ھ(\rN??sג˽??!*)?!Moȧ?????ʉ?5?S?l?r;?JHH??2?.]???\Z~:?j%?\'??p|??=????2?????f?jT??x?.??{\0??L????0???S??]#?\Z?????f???_??72g??(?%7뺇9z?U<?????ӟ}??0ɗ???>ʴ?5??Ԧuú??4L???n??????7?9??X??(-\Z?өY&??u;????d?\n+??x\r?J??b?ؾ?$~??ħd????١ K7?ˡs?????G????????6QuY??Dz?k¾????*??{O0???i9?ی2ս?v7????????12ẽ??k?????.?}?n??????????\'5#?WF?G?n??|?J???M?q=5?Kגy???R\r???غ??t\0ޭ???N?????<???5\0$g????D?????E??J!\'@?Eo?^+7??????4?Ҳ?W뿴ߗ???a?ݣ??1?@X?j\\wYٹ??K?{?㥫????@??$??˝??ho/?k??p??s\\]??\0K?\0???0o?\Z@?֍???????s??:??????J?Z??cg[??ւk??|?????n}??VѬ~-|t\Z??ɵ??덜???????喷ݫ??Щ8?}x?W[z?4?ix(\'?J??}???????I?z????X,6??i??s????`??|9e??_??a??ӥn?\'d??QVW?l?O?. \'??iԠm\Z??O????!???D}?R3s????\n???k?i????#N?\0?B|`?]N?U????vi?Z?d?????????\0????vs??SZ?\n?l???Q? x./???\'wRnQ??uY??(q?? ?\0\0HIDAT?=O?6Ѥg?{?<i9?L\\TX?Z?`̌??_;?^?2?Wf????(Q?Y?.\\K?j?S\'OAR+??\n\"??????C?E??????Ӳa8\'?J$?MpM??]/w????????Jz?n?փ??e?nV??M)?@?V<?`W??W???GEB????D\0_???3F??????+????^?;v???γi?q?N\'/<ڋ?1w1??!(r6ci\0??o,??4?7wo?ǯ=N?<??R????㔑?ܒM???rX=ܫ=!???3a??{??? ????t2fpO?i݈y/\r????aȵ??????Bi?$??;???Ne?5?4$ȟG?v???^?}??lۘ??ѪQ8???7??\\?fs`wY?7mP?j???\\?e4???<tO&?ޏ?=?Vs??kɹJH?on?+??0???;?>c????`@??x?Ul??5?z????????r??\n? ??<?0???????и???;\r?GEB??(??R\Z???????J\n9???Ã=?p?j\n?|??c??1??D????)O3?G+??8·+?[???8v&?IO???????$>?%7????۰;L? ~?ޜ/eΖ?[???\rtl?o??@??0?????0?????l;]?4䧙??W+?7>?Ct\n\\???Ȑ?W!?$???|-???e??m???ғ\r???v?1??u?\r???ܰ{???[?h???בc2s?t<\'/\\e?О??ФT????j.\\N??A?1?,?????^?vx?l????ۤA?3oye`??-?Xl?o????_\'6?:˷$[o?WǦ??6?/XOD?\Z???U??Ȇ??Y??h??p??}?&????j??tkg?????GEB???9K?;?V+f?Y<r>++???RSSy??G????B?_??U?&????i7\Z???A\\O?*?vHxp r???????W??T??&?ɨd2?\ra?%!P?%???\r?j??#????y????,?ʺ??7o??>}???E?qIp+u?W??\0.\\O??|?Ԙ???_.?ѻm2?\r?s?\"~Z/v?4??\'.2??????h?;????? ?:WR2??R}Qa?ؽp\Z?O\\?M???2?t?????w-??M0???????B??+?+?>b???T?V???@??????A??????J?*wV??????????+Wz???Q?%?`?,???jw?;????V??E??^??6????&YZ?????????ˏ??Ӱp????Is|JF??[l???ls?V???????n-?w?M\Z???G[?4?{B6JJa?Z\Zn?6é??iU??\r?9%??@_??z??y?ZB8???i?}?,?wk??5F???OnZ??2N???&?З??$J???d2????D?dLl?}???ϰ?Pɣ?n??}???Y??0m?cϑs?=z?=y?]%?vlwEq???j?{?\n????W???\\?ޡ?v(4?????R?8U??U?? ??Cz?*??????ߟ7?\Z?B????=?X??d~8?>?O_?u?z???=}9?gE???,E?\"q8?T7??KHHxIQ???(?O??p????hX?w~??????D$???D???MV?L?CJH?1Ng???;???j??V??ç???i?+?.????????????>???o?G???` ?ˑ?dh?}p??ȵ>?\\U?a?????W?P?????>???.?[?/?_O!=[??Nˑ3?iQG??S???I??[\'*H???\')???D?QfgZ?L?o0?b?@Y?&???n???/\0?\\?\\.??t???????????rjF?S???*?????|??侮S?+?1??j7?O????d?\"O?????%~z??V{?ڶ??t??????вyS?r9\n??\\N˻???}\\U?]?c_x?t??bN<C??mD??]A?????g??)KBB?hJ???AA~?ҥ3??S?͕???N?n6`J8E۶m? J?Pо}{????>???.?/JOn???s???\'?oO?t:?믿x???=R???DєJQq??Q*?(\nT*5k֤M?6ϔ<??D?d]G??ͩY?&j?\Z?J?Z?&88?-???C????? ??(=YG?ѦukBBBıIP?<?C%&&?>}???g?y???Q&???JE?T?T*Q??6?=??[*??U??[Ч?3??A??????Z?F?V3??Ahm?d?a}\\U?]R_????>{?ѡ?T*?%()????F???9y?$˗/??i%$*?R+*???P(P*?†V????e???d?!??%?y???LqК??9s&:??V?F????K|???????ȾC????? ??(=B?i,?̝;?N\'*u?\"?u??,ʅ?ng?ҥ4jԈ?mے???V????HHH???w?y?$\n?????t?p8????l6l6??????S?N$]?\'??F????@&?JZ???p:???X??0^:@f?&\"Cx?????????oooq??C??.:u?Dr??ۮ??j?B???SX??x? ??????ooo??4\Z\r*?*?vPQ8?N???عs\'?~?)?ƍ?ԩS|??g???????L?OO???f???(?F?z=999dff???AVV???dgg??????9s?999?MF??%?*^\Z-:_\ZEGкukBCC???A??????V?\'(?Á?b?`0`0???&\'\'?6????????g??????????~~~????V)A????bҤI,_????hz??????O?&M*?}??ZQ,\'??ٌ^?ͬ?,?????^??h4b6?1????v?v?h?q?u)?q??jrv????4\Z\rZ??N\'NR?\0?R宎?N\'V?5?r???o?>???.?/JOi?L???????燏?>>>h?Z??ʝjm???S)ufZa??}???p8????Ip?U????{?m?0???$\'y?gMm$?UZ???v*?? p?-????#??]?e?eY??x???????????9?;?1?/??????!??l?n?}B?c???K?E????v??:(??G7?y<??U}3?ސ9??^/?1?W?\"?t:??B?????93?@{??1?ڭ??{:??D??j;??ֵn?\'B?Ƹ?q??z51?\\.???|_\n?\nЦM??\'O\Z??<?Ð??4M2??*?ږt볽\Z?%T???i??\0??????bkA?1?u?%Ģ?OccLO?)??ؼ???o?????\n??j?????,??Z??,??1?g!?Q????~F?-ĸ?q??z51ӟCtS?7???\'?z?}O;*?8?<?i?g7??u?<???Psv#?}Ϯ???ΐ?; t??nd?D?9ƽ???X??3-^4>l??Wu?Dz??fZ??-˒~ֿ???\"???y-?q?????K?Ζ????~??^?]B,?m??^?f?}(V?vm.TD?+??־?6t~d????(??ж?5???ޤ???E{-ĸ?q??z{b??6Z\0~?*TD>`^?ؗ???e?????N3?ֶ???Z?q??.!???L?\0ڶ?PQ?`?g~? ?7?Ͽ?|f?OM??g{?A?k?m-ƽ???X?;*f\0?vX??l?9??tL??ĹU??l[?y??U?J?{w ??wt?\0???B%???b??_?to???^?]B,??V?\0???\0\0??X?\0\0nQ?\0\0\0?(T\0\0?[*\0\0?-\n\0\0??\n\0\0p?B\0\0?E?\0\0ܢP\0\0nQ?\0\0\0?(T\0\0?[*\0\0?-\n\0\0??\n\0\0p?B\0\0?E?\0\0??Eȹ??)-\0\0\0\0IEND?B`?','2021-08-22 20:05:34',NULL,1),(3,1,'博客','博客摘要','博客内容','2021-08-21 22:59:07',NULL,1),(4,1,'算法','牛客','算法训练','2021-08-24 22:18:14',NULL,NULL),(5,1,'音乐','人生如戏','撒旦法萨 算法暗室逢灯','2021-08-24 22:18:59',NULL,NULL),(6,1,'游戏',' 的速度 ','第三方三大as','2021-08-24 22:19:22',NULL,NULL),(7,1,'胡超88','超哥超哥超哥超而过',' # 牛逼66666\n\n的都是\n# 地方 ','2021-08-24 22:57:07',NULL,1),(10,1,'面向对象设计七大原则','面向对象设计七大原则','@[TOC](目录)\n\n\n\n## 1. 单一职责原则(Single Responsibility Principle)\n\n每一个类应该专注于做一件事情。\n\n## 2. 里氏替换原则(Liskov Substitution Principle)\n\n超类存在的地方,子类是可以替换的。\n\n## 3. 依赖倒置原则(Dependence Inversion Principle)\n\n实现尽量依赖抽象,不依赖具体实现。\n\n## 4. 接口隔离原则(Interface Segregation Principle)\n\n应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。\n\n## 5. 迪米特法则(Law Of Demeter)\n\n又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用。\n\n## 6. 开闭原则(Open Close Principle)\n\n面向扩展开放,面向修改关闭。\n\n## 7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)\n\n尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象。\n\n# 细则\n## 单一职责原则(Single Responsibility Principle)\n\n**定义**:**一个类,只有一个引起它变化的原因。即:应该只有一个职责。**\n\n每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都需要遵循这一重要原则。 \n\n**问题由来**:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。 \n\n**解决方法**:分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。\n\n\n**因为:**\n\n可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。\n\n**所以:**\n\n从大局上看Android中的Paint和Canvas等类都遵守单一职责原则,Paint和Canvas各司其职。\n\n## 里氏替换原则(Liskov Substitution Principle)\n**定义:子类型必须能够替换掉它们的父类型。注意这里的能够两字。有人也戏称老鼠的儿子会打洞原则。**\n\n**问题由来**:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。 \n\n**解决方法**:类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法 \n\n**小结**:所有引用父类的地方必须能透明地使用其子类的对象。子类可以扩展父类的功能,但不能改变父类原有的功能,即:子类可以实现父类的抽象方法,子类也中可以增加自己特有的方法,但不能覆盖父类的非抽象方法。当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。\n\n**因为:**\n\n里氏替换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。\n\n**所以:**\n\n使用里氏替换原则时需要注意,子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。\n\n从大局看Java的多态就属于这个原则。\n\n## 依赖倒置原则(Dependence Inversion Principle)\n**定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。中心思想是面向接口编程** \n\n**问题由来**:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。 \n\n**解决方法**:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。 \n\n在实际编程中,我们一般需要做到如下3点:\n\n1). 低层模块尽量都要有抽象类或接口,或者两者都有。\n\n2). 变量的声明类型尽量是抽象类或接口。\n\n3). 使用继承时遵循里氏替换原则。 \n\n采用依赖倒置原则尤其给多人合作开发带来了极大的便利,参与协作开发的人越多、项目越庞大,采用依赖导致原则的意义就越重大。 \n\n**小结**:依赖倒置原则就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。 \n\n\n**因为:**\n\n具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类;而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口;这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖。\n\n**所以:**\n\n采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。\n\n从大局看Java的多态就属于这个原则。\n\n## 接口隔离原则(Interface Segregation Principle)\n**定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。** \n\n**问题由来**:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法 \n\n**解决方法**:\n1、 使用委托分离接口。\n2、 使用多重继承分离接口。\n3.将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。 \n\n**举例说明:**\n![在这里插入图片描述](https://img-blog.csdnimg.cn/d252f588896341d89518034d124b92b2.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n下面我们来看张图,一切就一目了然了。\n这个图的意思是:类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现。类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法\n\n修改后:\n![在这里插入图片描述](https://img-blog.csdnimg.cn/ac53c8df9fca4919bde6f183099a2678.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口\n\n**小结**:我们在代码编写过程中,运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。设计接口的时候,只有多花些时间去思考和筹划,就能准确地实践这一原则。 \n\n\n**因为:**\n\n提供尽可能小的单独接口,而不要提供大的总接口。暴露行为让后面的实现类知道的越少越好。譬如类ProgramMonkey通过接口CodeInterface依赖类CodeC,类ProgramMaster通过接口CodeInterface依赖类CodeAndroid,如果接口CodeInterface对于类ProgramMonkey和类CodeC来说不是最小接口,则类CodeC和类CodeAndroid必须去实现他们不需要的方法。将臃肿的接口CodeInterface拆分为独立的几个接口,类ProgramMonkey和类ProgramMaster分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。\n\n**所以:**\n\n建立单一接口,不要建立庞大的接口,尽量细化接口,接口中的方法尽量少。也就是要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的约定,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。\n\n从大局来说Java的接口可以实现多继承就是接口隔离原则的基础保障。\n\n## 迪米特法则(Law Of Demeter)\n**定义:迪米特法则又叫最少知道原则,即:一个对象应该对其他对象保持最少的了解。如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。简单定义为只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。**\n\n**问题由来**:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。\n\n最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。 \n\n**解决方法**:尽量降低类与类之间的耦合。 自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。 \n\n迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系。故过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。 \n\n\n**因为:**\n\n类与类之间的关系越密切,耦合度也就越来越大,只有尽量降低类与类之间的耦合才符合设计模式;对于被依赖的类来说,无论逻辑多复杂都要尽量封装在类的内部;每个对象都会与其他对象有耦合关系,我们称出现成员变量、方法参数、方法返回值中的类为直接的耦合依赖,而出现在局部变量中的类则不是直接耦合依赖,也就是说,不是直接耦合依赖的类最好不要作为局部变量的形式出现在类的内部。\n\n**所以:**\n\n一个对象对另一个对象知道的越少越好,即一个软件实体应当尽可能少的与其他实体发生相互作用,在一个类里能少用多少其他类就少用多少,尤其是局部变量的依赖类,能省略尽量省略。同时如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一方法的话,可以通过第三者转发这个调用。\n\n从大局来说Android App开发中的多Fragment与依赖的Activity间交互通信遵守了这一法则。\n\n## 开闭原则(Open Close Principle)\n**定义:软件实体应当对扩展开放,对修改关闭。这句话说得有点专业,更通俗一点讲,也就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,去扩展新功能。开闭原则中原有“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于代码的修改是封闭的,即不应该修改原有的代码。**\n\n**问题由来**:凡事的产生都有缘由。我们来看看,开闭原则的产生缘由。在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。这就对我们的整个系统的影响特别大,这也充分展现出了系统的耦合性如果太高,会大大的增加后期的扩展,维护。为了解决这个问题,故人们总结出了开闭原则。解决开闭原则的根本其实还是在解耦合。所以,我们面向对象的开发,我们最根本的任务就是解耦合。 \n\n**解决方法**:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。 \n\n**小结**:开闭原则具有理想主义的色彩,说的很抽象,它是面向对象设计的终极目标。其他几条原则,则可以看做是开闭原则的实现。我们要用抽象构建框架,用实现扩展细节。\n\n\n**因为:**\n\n开放封闭原则主要体现在对扩展开放、对修改封闭,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。软件需求总是变化的,世界上没有一个软件的是不变的,因此对软件设计人员来说,必须在不需要对原有系统进行修改的情况下,实现灵活的系统扩展。\n\n**所以:**\n\n可以通过Template Method模式和Strategy模式进行重构,实现对修改封闭,对扩展开放的设计思路。 \n封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,拒绝滥用抽象,只将经常变化的部分进行抽象。\n\n## 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)\n**定义:也有人叫做合成复用原则,及尽量使用合成/聚合,尽量不要使用类继承。换句话说,就是能用合成/聚合的地方,绝不用继承。** \n\n**为什么要尽量使用合成/聚合而不使用类继承?**\n\n> 1. 对象的继承关系在编译时就定义好了,所以无法在运行时改变从父类继承的子类的实现\n> \n> 2. 子类的实现和它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化\n> \n> 3. 当你复用子类的时候,如果继承下来的实现不适合解决新的问题,则父类必须重写或者被其它更适合的类所替换,这种依赖关系限制了灵活性,并最终限制了复用性。\n\n**总结**:这些原则在设计模式中体现的淋淋尽致,设计模式就是实现了这些原则,从而达到了代码复用、增强了系统的扩展性。所以设计模式被很多人奉为经典。我们可以通过好好的研究设计模式,来慢慢的体会这些设计原则。\n\n**因为:**\n\n其实整个设计模式就是在讲如何类与类之间的组合/聚合。在一个新的对象里面通过关联关系(包括组合关系和聚合关系)使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承。\n\n如果为了复用,便使用继承的方式将两个不相干的类联系在一起,违反里氏代换原则,哪是生搬硬套,忽略了继承了缺点。继承复用破坏数据封装性,将基类的实现细节全部暴露给了派生类,基类的内部细节常常对派生类是透明的,白箱复用;虽然简单,但不安全,不能在程序的运行过程中随便改变;基类的实现发生了改变,派生类的实现也不得不改变;从基类继承而来的派生类是静态的,不可能在运行时间内发生改变,因此没有足够的灵活性。\n\n**所以:**\n\n组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。\n\n# 更多相关文章点这里哦\n\n[面向对象基础全总结\n](https://blog.csdn.net/qq_45696377/article/details/112439241)\n\n[【Java全栈】Java全套学习路线及项目资料总结【JavaSE+Web基础+MySQL+JavaEE】](https://blog.csdn.net/qq_45696377/article/details/110575362)\n\n','2021-08-25 10:19:08',NULL,0),(11,1,'666测试标题3333','66摘要333','胡超','2021-08-25 10:21:17','2021-08-30 11:35:33',1),(12,1,'分布式系统唯一ID生成方案汇总','分布式系统唯一ID生成方案汇总','@[TOC](目录)\n> 系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。下面就介绍一些常见的ID生成策略。\n\n# 1. 数据库自增长序列或字段\n\n最常见的方式。利用数据库,全数据库唯一。\n\n**优点:**\n\n1)简单,代码方便,性能可以接受。\n\n2)数字ID天然排序,对分页或者需要排序的结果很有帮助。\n\n \n\n**缺点:**\n\n1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。\n\n2)在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。\n\n3)在性能达不到要求的情况下,比较难于扩展。(不适用于海量高并发)\n\n4)如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦。\n\n5)分表分库的时候会有麻烦。\n\n6)并非一定连续,类似MySQL,当生成新ID的事务回滚,那么后续的事务也不会再用这个ID了。这个在性能和连续性的折中。如果为了保证连续,必须要在事务结束后才能生成ID,那性能就会出现问题。\n\n7)在分布式数据库中,如果采用了自增主键的话,有可能会带来尾部热点。分布式数据库常常使用range的分区方式,在大量新增记录的时候,IO会集中在一个分区上,造成热点数据。\n\n**优化方案:**\n\n1)针对主库单点,如果有多个Master库,则每个Master库设置的起始数字不一样,步长一样,可以是Master的个数。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。这样就可以有效生成集群中的唯一ID,也可以大大降低ID生成数据库操作的负载。\n\n# 2. UUID\n\n常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。UUID是由32个的16进制数字组成,所以每个UUID的长度是128位(16^32 = 2^128)。UUID作为一种广泛使用标准,有多个实现版本,影响它的因素包括时间、网卡MAC地址、自定义Namesapce等等。\n\n**优点:**\n\n1)简单,代码方便。\n\n2)生成ID性能非常好,基本不会有性能问题。\n\n3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。\n\n \n\n**缺点:**\n\n1)没有排序,无法保证趋势递增。\n\n2)UUID往往是使用字符串存储,查询的效率比较低。\n\n3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。\n\n4)传输数据量大\n\n5)不可读。\n\n\n\n# 3. UUID的变种\n\n1)为了解决UUID不可读,可以使用UUID to Int64的方法。及\n\n\n```java\n/// <summary>\n/// 根据GUID获取唯一数字序列\n/// </summary>\npublic static long GuidToInt64()\n{\n byte[] bytes = Guid.NewGuid().ToByteArray();\n return BitConverter.ToInt64(bytes, 0);\n}\n```\n\n \n2)为了解决UUID无序的问题,NHibernate在其主键生成方式中提供了Comb算法(combined guid/timestamp)。保留GUID的10个字节,用另6个字节表示GUID生成的时间(DateTime)。\n\n\n```java\n/// <summary> \n/// Generate a new <see cref=\"Guid\"/> using the comb algorithm. \n/// </summary> \nprivate Guid GenerateComb()\n{\n byte[] guidArray = Guid.NewGuid().ToByteArray();\n \n DateTime baseDate = new DateTime(1900, 1, 1);\n DateTime now = DateTime.Now;\n \n // Get the days and milliseconds which will be used to build \n //the byte string \n TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);\n TimeSpan msecs = now.TimeOfDay;\n \n // Convert to a byte array \n // Note that SQL Server is accurate to 1/300th of a \n // millisecond so we divide by 3.333333 \n byte[] daysArray = BitConverter.GetBytes(days.Days);\n byte[] msecsArray = BitConverter.GetBytes((long)\n (msecs.TotalMilliseconds / 3.333333));\n \n // Reverse the bytes to match SQL Servers ordering \n Array.Reverse(daysArray);\n Array.Reverse(msecsArray);\n \n // Copy the bytes into the guid \n Array.Copy(daysArray, daysArray.Length - 2, guidArray,\n guidArray.Length - 6, 2);\n Array.Copy(msecsArray, msecsArray.Length - 4, guidArray,\n guidArray.Length - 4, 4);\n \n return new Guid(guidArray);\n}\n```\n\n \n用上面的算法测试一下,得到如下的结果:作为比较,前面3个是使用COMB算法得出的结果,最后12个字符串是时间序(统一毫秒生成的3个UUID),过段时间如果再次生成,则12个字符串会比图示的要大。后面3个是直接生成的GUID。\n![在这里插入图片描述](https://img-blog.csdnimg.cn/0f01fff6937f4e488b8d46a6253a3e3c.png)\n\n如果想把时间序放在前面,可以生成后改变12个字符串的位置,也可以修改算法类的最后两个Array.Copy。\n\n# 4. Redis生成ID\n\n当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。\n\n可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:\n\nA:1,6,11,16,21\n\nB:2,7,12,17,22\n\nC:3,8,13,18,23\n\nD:4,9,14,19,24\n\nE:5,10,15,20,25\n\n这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的ID。但是步长和初始值一定需要事先需要了。使用Redis集群也可以方式单点故障的问题。\n\n另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。\n\n \n\n**优点:**\n\n1)不依赖于数据库,灵活方便,且性能优于数据库。\n\n2)数字ID天然排序,对分页或者需要排序的结果很有帮助。\n\n**缺点:**\n\n1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。\n\n2)需要编码和配置的工作量比较大。\n\n# 5. Twitter的snowflake(雪花)算法\n\nsnowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。\n\n雪花算法在工程实现上有单机版本和分布式版本。单机版本如下,分布式版本可以参看\n**美团leaf算法:**\n**https://github.com/Meituan-Dianping/Leaf**\n\n```csharp\n/// <summary>\n /// From: https://github.com/twitter/snowflake\n /// An object that generates IDs.\n /// This is broken into a separate class in case\n /// we ever want to support multiple worker threads\n /// per process\n /// </summary>\n public class IdWorker\n {\n private long workerId;\n private long datacenterId;\n private long sequence = 0L;\n\n private static long twepoch = 1288834974657L;\n\n private static long workerIdBits = 5L;\n private static long datacenterIdBits = 5L;\n private static long maxWorkerId = -1L ^ (-1L << (int)workerIdBits);\n private static long maxDatacenterId = -1L ^ (-1L << (int)datacenterIdBits);\n private static long sequenceBits = 12L;\n\n private long workerIdShift = sequenceBits;\n private long datacenterIdShift = sequenceBits + workerIdBits;\n private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;\n private long sequenceMask = -1L ^ (-1L << (int)sequenceBits);\n\n private long lastTimestamp = -1L;\n private static object syncRoot = new object();\n\n public IdWorker(long workerId, long datacenterId)\n {\n\n // sanity check for workerId\n if (workerId > maxWorkerId || workerId < 0)\n {\n throw new ArgumentException(string.Format(\"worker Id can\'t be greater than %d or less than 0\", maxWorkerId));\n }\n if (datacenterId > maxDatacenterId || datacenterId < 0)\n {\n throw new ArgumentException(string.Format(\"datacenter Id can\'t be greater than %d or less than 0\", maxDatacenterId));\n }\n this.workerId = workerId;\n this.datacenterId = datacenterId;\n }\n\n public long nextId()\n {\n lock (syncRoot)\n {\n long timestamp = timeGen();\n\n if (timestamp < lastTimestamp)\n {\n throw new ApplicationException(string.Format(\"Clock moved backwards. Refusing to generate id for %d milliseconds\", lastTimestamp - timestamp));\n }\n\n if (lastTimestamp == timestamp)\n {\n sequence = (sequence + 1) & sequenceMask;\n if (sequence == 0)\n {\n timestamp = tilNextMillis(lastTimestamp);\n }\n }\n else\n {\n sequence = 0L;\n }\n\n lastTimestamp = timestamp;\n\n return ((timestamp - twepoch) << (int)timestampLeftShift) | (datacenterId << (int)datacenterIdShift) | (workerId << (int)workerIdShift) | sequence;\n }\n }\n\n protected long tilNextMillis(long lastTimestamp)\n {\n long timestamp = timeGen();\n while (timestamp <= lastTimestamp)\n {\n timestamp = timeGen();\n }\n return timestamp;\n }\n\n protected long timeGen()\n {\n return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;\n }\n }\n```\n\n测试代码如下:\n\n```csharp\nprivate static void TestIdWorker()\n {\n HashSet<long> set = new HashSet<long>();\n IdWorker idWorker1 = new IdWorker(0, 0);\n IdWorker idWorker2 = new IdWorker(1, 0);\n Thread t1 = new Thread(() => DoTestIdWoker(idWorker1, set));\n Thread t2 = new Thread(() => DoTestIdWoker(idWorker2, set));\n t1.IsBackground = true;\n t2.IsBackground = true;\n\n t1.Start();\n t2.Start();\n try\n {\n Thread.Sleep(30000);\n t1.Abort();\n t2.Abort();\n }\n catch (Exception e)\n {\n }\n\n Console.WriteLine(\"done\");\n }\n\n private static void DoTestIdWoker(IdWorker idWorker, HashSet<long> set)\n {\n while (true)\n {\n long id = idWorker.nextId();\n if (!set.Add(id))\n {\n Console.WriteLine(\"duplicate:\" + id);\n }\n\n Thread.Sleep(1);\n }\n }\n```\n\n\nsnowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。\n\n**优点:**\n\n1)不依赖于数据库,灵活方便,且性能优于数据库。\n\n2)ID按照时间在单机上是递增的。\n\n**缺点:**\n\n1)在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,在算法上要解决时间回拨的问题。\n\n\n# 6. 利用zookeeper生成唯一ID\n\n \n\n - **zookeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。**\n \n \n - **很少会使用zookeeper来生成唯一ID。主要是由于需要依赖zookeeper,并且是多步调用API,如果在竞争较大的情况下,需要考虑使用分布式锁。因此,性能在高并发的分布式环境下,也不甚理想。**\n\n \n# 7. MongoDB的ObjectId\n\nMongoDB的ObjectId和snowflake算法类似。它设计成轻量型的,不同的机器都能用全局唯一的同种方法方便地生成它。MongoDB 从一开始\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2a5535b9ee3e404ea21567df845e1252.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n前4 个字节是从标准纪元开始的时间戳,单位为秒。时间戳,与随后的5 个字节组合起来,提供了秒级别的唯一性。由于时间戳在前,这意味着ObjectId 大致会按照插入的顺序排列。这对于某些方面很有用,如将其作为索引提高效率。这4 个字节也隐含了文档创建的时间。绝大多数客户端类库都会公开一个方法从ObjectId 获取这个信息。 \n接下来的3 字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的ObjectId,不产生冲突。 \n为了确保在同一台机器上并发的多个进程产生的ObjectId 是唯一的,接下来的两字节来自产生ObjectId 的进程标识符(PID)。 \n前9 字节保证了同一秒钟不同机器不同进程产生的ObjectId 是唯一的。后3 字节就是一个自动增加的计数器,确保相同进程同一秒产生的ObjectId 也是不一样的。同一秒钟最多允许每个进程拥有2563(16 777 216)个不同的ObjectId。\n\n实现的源码可以到MongoDB官方网站下载。\n\n# 8. TiDB的主键\nTiDB默认是支持自增主键的,对未声明主键的表,会提供了一个隐式主键_tidb_rowid,因为这个主键大体上是单调递增的,所以也会出现我们前面说的“尾部热点”问题。\n\nTiDB也提供了UUID函数,而且在4.0版本中还提供了另一种解决方案AutoRandom。TiDB 模仿MySQL的 AutoIncrement,提供了AutoRandom关键字用于生成一个随机ID填充指定列。\n\n# 更多相关文章点点这里\n【Java全栈】Java全栈学习路线及项目全资料总结【JavaSE+Web基础+大前端进阶+SSM+微服务+Linux+JavaEE】\n\n[【Java全栈】Java全栈学习路线及项目全资料总结【JavaSE+Web基础+大前端进阶+SSM+微服务+Linux+JavaEE】](https://blog.csdn.net/qq_45696377/article/details/110575362)\n','2021-08-25 10:22:03',NULL,0),(13,1,'【MyBatisPlus学习】乐观锁 OptimisticLockerInnerInterceptor插件 细解','【MyBatisPlus学习】乐观锁 OptimisticLockerInnerInterceptor插件 细解','@[TOC]\n# 乐观锁 \n\n**OptimisticLockerInnerInterceptor**\n\n> **乐观锁 : 故名思意十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题, 再次更新值测试**\n\n> **悲观锁:故名思意十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!**\n\n我们这里主要讲解 乐观锁机制!\n\n乐观锁实现方式:\n\n当要更新一条记录的时候,希望这条记录没有被别人更新\n乐观锁实现方式:\n\n - 取出记录时,获取当前version\n - 更新时,带上这个version\n - 执行更新时, set version = newVersion where version = oldVersion\n - 如果version不对,就更新失败\n\n\n```sql\n乐观锁:1、先查询,获得版本号 version = 1 \n-- A \nupdate user set name = \"maomao\", \nversion = version + 1 \nwhere id = 2 and version = 1 \n-- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败!\n update user set name = \"maomao\", \n version = version + 1 where id = 2 and version = 1\n```\n\n# 测试一下MP的乐观锁插件\n\n## 1、给数据库中增加version字段!\n![在这里插入图片描述](https://img-blog.csdnimg.cn/087fdb4fa6b64256af0719fbf1704842.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n## 2、我们实体类加对应的字段\n\n```sql\n@Version //乐观锁Version注解 \nprivate Integer version; \n```\n\n## 3、注册组件\n\n```java\n// 扫描我们的 mapper 文件夹\n@MapperScan(\"com.mao.mapper\")\n@EnableTransactionManagement //自动管理事务的注解\n@Configuration // 配置类\npublic class MyBatisPlusConfig {\n\n // 注册乐观锁插件\n @Bean\n public OptimisticLockerInterceptor optimisticLockerInterceptor() {\n return new OptimisticLockerInterceptor();\n }\n```\n\n## 4、测试一下\n\n```java\n// 测试乐观锁成功!\n @Test\n public void testOptimisticLocker(){\n // 1、查询用户信息\n User user = userMapper.selectById(1L);\n // 2、修改用户信息\n user.setName(\"maomao\");\n user.setEmail(\"24736743@qq.com\");\n // 3、执行更新操作\n userMapper.updateById(user);\n }\n```\n![在这里插入图片描述](https://img-blog.csdnimg.cn/5dbbfca5a00340fc99581b30302286dd.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n\n```java\n// 测试乐观锁失败!多线程下\n @Test\n public void testOptimisticLocker2(){\n\n // 线程 1\n User user = userMapper.selectById(1L);\n user.setName(\"maomao111\");\n user.setEmail(\"24736743@qq.com\");\n\n // 模拟另外一个线程执行了插队操作\n User user2 = userMapper.selectById(1L);\n user2.setName(\"maomao222\");\n user2.setEmail(\"24736743@qq.com\");\n userMapper.updateById(user2);\n\n // 自旋锁来多次尝试提交!\n userMapper.updateById(user); // 如果没有乐观锁就会覆盖插队线程的值!\n }\n```\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/6aba9015a9a347ee8b9830b65d6f2c23.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n# 更多相关文章点这里\n\n[【Java全栈】Java全栈学习路线及项目全资料总结【JavaSE+Web基础+大前端进阶+SSM+微服务+Linux+JavaEE】](https://blog.csdn.net/qq_45696377/article/details/110575362)\n\n','2021-08-25 10:23:33',NULL,0),(14,1,'【Springboot学习】Shiro快速入门及与SpringBoot集成','【Springboot学习】Shiro快速入门及与SpringBoot集成','@[TOC](目录)\n# 1、Shiro简介\n\n## 1.1、Shiro 是什么?\n\n- Apache Shiro 是 Java 的一个安全(权限)框架。\n\n- Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。\n\n- Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。\n- 下载地址\n - 官网:[http://shiro.apache.org/](http://shiro.apache.org/)\n - github:[https://github.com/apache/shiro](https://github.com/apache/shiro)\n\n\n\n## 1.2、有哪些功能?\n\n![image-20200729114647110](https://img-blog.csdnimg.cn/img_convert/26c200202f5d03ffd5b52f78bc7c3301.png)\n\n- Authentication:身份认证/登录,验证用户是不是拥有相应的身份\n\n- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限\n\n- Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web 环境的\n\n- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储\n\n- Web Support:Web 支持,可以非常容易的集成到Web 环境\n\n- Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率\n\n- Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去\n\n- Testing:提供测试支持\n\n- \"Run As\":允许一个用户假装为另一个用户(如果他们允许)的身份进行访问\n\n- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了\n\n## 1.3、Shiro架构(外部)\n\n从外部来看Shiro,即从应用程序角度的来观察如何使用Shiro完成工作\n\n![image-20200729114702566](https://img-blog.csdnimg.cn/img_convert/4915e93b9d91979c3532412175ff59fa.png)\n\n- Subject:应用代码直接交互的对象是Subject,也就是说Shiro的对外API 核心就是Subject。Subject 代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;与Subject 的所有交互都会委托给SecurityManager;Subject 其实是一个门面,SecurityManager才是实际的执行者\n\n- SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且其管理着所有Subject;可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色\n\n- Realm:Shiro从Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm 看成DataSource\n\n## 1.4、Shiro架构(内部)\n\n![image-20200729114720578](https://img-blog.csdnimg.cn/img_convert/8d8c268aa1b1b156d8764f484eb12233.png)\n\n- Subject:任何可以与应用交互的“用户”;\n- SecurityManager:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证、授权、会话及缓存的管理。\n- Authenticator:负责Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;\n- Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;\n- Realm:可以有1 个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的Realm;\n- SessionManager:管理Session 生命周期的组件;而Shiro并不仅仅可以用在Web 环境,也可以用在如普通的JavaSE环境\n CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能\n- Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密。\n\n# 2、Hello World\n\n## 2.1、快速实践\n\n- 查看官方文档:http://shiro.apache.org/tutorial.html\n\n- 官方的quickstart : https://github.com/apache/shiro/tree/master/samples/quickstart/\n\n ![image-20200729115148574](https://img-blog.csdnimg.cn/img_convert/0bc7c52506712197f94c9389bff03063.png)\n\n1. 创建一个maven父工程,用来学习Shiro,删掉不必要的部分\n\n2. 创建一个普通的Maven子工程:hell-shiro\n\n ![image-20200729120114648](https://img-blog.csdnimg.cn/img_convert/e94b5b7f06ad4c9bab4e531113c84778.png)\n\n3. 根据[官方文档](https://github.com/apache/shiro/blob/master/samples/quickstart/pom.xml),我们导入Shiro的依赖\n\n ![image-20200729120207730](https://img-blog.csdnimg.cn/img_convert/17a00ec81c5627f08b08b1daea43c579.png)\n\n \n\n [版本号点击这里](https://mvnrepository.com/artifact/org.apache.shiro/shiro-core)\n\n ```xml\n <dependencies>\n <dependency>\n <groupId>org.apache.shiro</groupId>\n <artifactId>shiro-core</artifactId>\n <version>1.5.3</version>\n </dependency>\n \n <!-- configure logging -->\n <dependency>\n <groupId>org.slf4j</groupId>\n <artifactId>jcl-over-slf4j</artifactId>\n <version>1.7.26</version>\n </dependency>\n <dependency>\n <groupId>org.slf4j</groupId>\n <artifactId>slf4j-log4j12</artifactId>\n <version>1.7.26</version>\n </dependency>\n <dependency>\n <groupId>log4j</groupId>\n <artifactId>log4j</artifactId>\n <version>1.2.17</version>\n </dependency>\n </dependencies>\n ```\n\n4. 相关配置文件\n\n - log4j.properties——[官网](https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/log4j.properties)\n\n ```properties\n log4j.rootLogger=INFO, stdout\n \n log4j.appender.stdout=org.apache.log4j.ConsoleAppender\n log4j.appender.stdout.layout=org.apache.log4j.PatternLayout\n log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n\n \n # General Apache libraries\n log4j.logger.org.apache=WARN\n \n # Spring\n log4j.logger.org.springframework=WARN\n \n # Default Shiro logging\n log4j.logger.org.apache.shiro=INFO\n \n # Disable verbose logging\n log4j.logger.org.apache.shiro.util.ThreadContext=WARN\n log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN\n ```\n\n - shiro.ini——[官网](https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/shiro.ini)\n\n ```ini\n [users]\n # user \'root\' with password \'secret\' and the \'admin\' role\n root = secret, admin\n # user \'guest\' with the password \'guest\' and the \'guest\' role\n guest = guest, guest\n # user \'presidentskroob\' with password \'12345\' (\"That\'s the same combination on\n # my luggage!!!\" ;)), and role \'president\'\n presidentskroob = 12345, president\n # user \'darkhelmet\' with password \'ludicrousspeed\' and roles \'darklord\' and \'schwartz\'\n darkhelmet = ludicrousspeed, darklord, schwartz\n # user \'lonestarr\' with password \'vespa\' and roles \'goodguy\' and \'schwartz\'\n lonestarr = vespa, goodguy, schwartz\n \n # -----------------------------------------------------------------------------\n # Roles with assigned permissions\n # \n # Each line conforms to the format defined in the\n # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc\n # -----------------------------------------------------------------------------\n [roles]\n # \'admin\' role has all permissions, indicated by the wildcard \'*\'\n admin = *\n # The \'schwartz\' role can do anything (*) with any lightsaber:\n schwartz = lightsaber:*\n # The \'goodguy\' role is allowed to \'drive\' (action) the winnebago (type) with\n # license plate \'eagle5\' (instance specific id)\n goodguy = winnebago:drive:eagle5\n ```\n\n - 启动类 Quickstart——[官网](https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/java/Quickstart.java)\n\n ```java\n /*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\n \n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.authc.*;\n import org.apache.shiro.config.IniSecurityManagerFactory;\n import org.apache.shiro.mgt.SecurityManager;\n import org.apache.shiro.session.Session;\n import org.apache.shiro.subject.Subject;\n import org.apache.shiro.util.Factory;\n import org.slf4j.Logger;\n import org.slf4j.LoggerFactory;\n \n \n /**\n * Simple Quickstart application showing how to use Shiro\'s API.\n * 简单入门Shiro使用API\n *\n * @since 0.9 RC2\n */\n public class Quickstart {\n \n private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);\n \n \n public static void main(String[] args) {\n \n // The easiest way to create a Shiro SecurityManager with configured\n // realms, users, roles and permissions is to use the simple INI config.\n // We\'ll do that by using a factory that can ingest a .ini file and\n // return a SecurityManager instance:\n \n // Use the shiro.ini file at the root of the classpath\n // (file: and url: prefixes load from files and urls respectively):\n // 读取配置文件:\n Factory<SecurityManager> factory = new IniSecurityManagerFactory(\"classpath:shiro.ini\");\n SecurityManager securityManager = factory.getInstance();\n \n // for this simple example quickstart, make the SecurityManager\n // accessible as a JVM singleton. Most applications wouldn\'t do this\n // and instead rely on their container configuration or web.xml for\n // webapps. That is outside the scope of this simple quickstart, so\n // we\'ll just do the bare minimum so you can continue to get a feel\n // for things.\n SecurityUtils.setSecurityManager(securityManager);\n \n // Now that a simple Shiro environment is set up, let\'s see what you can do:\n \n // get the currently executing user:\n // 获取当前的用户对象 Subject\n Subject currentUser = SecurityUtils.getSubject();\n \n // Do some stuff with a Session (no need for a web or EJB container!!!)\n //通过当前用户拿到Shiro的Session 可以脱离web存值取值\n Session session = currentUser.getSession();\n session.setAttribute(\"someKey\", \"aValue\");\n String value = (String) session.getAttribute(\"someKey\");\n if (value.equals(\"aValue\")) {\n log.info(\"Retrieved the correct value! [\" + value + \"]\");\n }\n \n // let\'s login the current user so we can check against roles and permissions:\n //判断当前的用户是否被认证\n if (!currentUser.isAuthenticated()) {\n //Token 令牌\n UsernamePasswordToken token = new UsernamePasswordToken(\"lonestarr\", \"vespa\");\n //设置记住我\n token.setRememberMe(true);\n try {\n //执行登录操作\n currentUser.login(token);\n } catch (UnknownAccountException uae) {\n log.info(\"There is no user with username of \" + token.getPrincipal());\n } catch (IncorrectCredentialsException ice) {\n log.info(\"Password for account \" + token.getPrincipal() + \" was incorrect!\");\n } catch (LockedAccountException lae) {\n log.info(\"The account for username \" + token.getPrincipal() + \" is locked. \" +\n \"Please contact your administrator to unlock it.\");\n }\n // ... catch more exceptions here (maybe custom ones specific to your application?\n catch (AuthenticationException ae) {\n //unexpected condition? error?\n }\n }\n \n //say who they are:\n //print their identifying principal (in this case, a username):\n log.info(\"User [\" + currentUser.getPrincipal() + \"] logged in successfully.\");\n \n //test a role:\n // 检查角色\n if (currentUser.hasRole(\"schwartz\")) {\n log.info(\"May the Schwartz be with you!\");\n } else {\n log.info(\"Hello, mere mortal.\");\n }\n \n //test a typed permission (not instance-level)\n //粗粒度\n if (currentUser.isPermitted(\"lightsaber:wield\")) {\n log.info(\"You may use a lightsaber ring. Use it wisely.\");\n } else {\n log.info(\"Sorry, lightsaber rings are for schwartz masters only.\");\n }\n \n //a (very powerful) Instance Level permission:\n //细粒度\n if (currentUser.isPermitted(\"winnebago:drive:eagle5\")) {\n log.info(\"You are permitted to \'drive\' the winnebago with license plate (id) \'eagle5\'. \" +\n \"Here are the keys - have fun!\");\n } else {\n log.info(\"Sorry, you aren\'t allowed to drive the \'eagle5\' winnebago!\");\n }\n \n //all done - log out!\n //注销\n currentUser.logout();\n \n //结束\n System.exit(0);\n }\n }\n ```\n\n ![image-20200729130649625](https://img-blog.csdnimg.cn/img_convert/47981bf3bbea556ab1c7a086e85bb024.png)\n\n - Spring Secutrry都有~(只是换了个名字)\n\n ```java\n // 获取当前的用户对象 Subject\n Subject currentUser = SecurityUtils.getSubject();\n Session session = currentUser.getSession();\n currentUser.isAuthenticated()\n currentUser.getPrincipal()\n currentUser.hasRole(\"schwartz\")\n currentUser.isPermitted(\"lightsaber:wield\")\n currentUser.logout();\n ```\n\n\n\n# 3、SpringBoot集成\n\n## 3.1、SpringBoot整合Shiro环境搭建\n\n1. 新建一个项目或模块,勾选依赖\n\n ![image-20200729174715011](https://img-blog.csdnimg.cn/img_convert/abab859655a176b531ff7fca2d2b62cb.png)\n\n pom.xml\n\n ```xml\n <dependencies>\n <!--thymeleaf-->\n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-thymeleaf</artifactId>\n </dependency>\n \n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-web</artifactId>\n </dependency>\n \n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-test</artifactId>\n <scope>test</scope>\n <exclusions>\n <exclusion>\n <groupId>org.junit.vintage</groupId>\n <artifactId>junit-vintage-engine</artifactId>\n </exclusion>\n </exclusions>\n </dependency>\n </dependencies>\n ```\n\n2. 测试环境是否正常\n\n - 新建一个controller页面\n\n ```java\n @Controller\n public class MyController {\n \n @RequestMapping({\"/\",\"/index\"})\n public String toIndex(Model model) {\n model.addAttribute(\"msg\",\"hello,Shiro\");\n return \"index\";\n }\n \n @RequestMapping(\"/user/add\")\n public String add() {\n return \"user/add\";\n }\n \n @RequestMapping(\"/user/update\")\n public String update() {\n return \"user/update\";\n }\n }\n ```\n\n - 新建一个index.html页面\n\n ```html\n <!DOCTYPE html>\n <html lang=\"en\" xmlns:th=\"http://www.thymeleaf.org\">\n <head>\n <meta charset=\"UTF-8\">\n <title>首页</title>\n </head>\n <body>\n <div>\n <h1>首页</h1>\n <p th:text=\"${msg}\"></p>\n \n <hr>\n <a th:href=\"@{/user/add}\">add</a> | <a th:href=\"@{/user/update}\">update</a>\n </div>\n </body>\n </html>\n ```\n\n - 新建一个add.html页面\n\n ```html\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n </head>\n <body>\n <h1>add</h1>\n </body>\n </html>\n ```\n\n - 新建一个update.html页面\n\n ```html\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n </head>\n <body>\n <h1>update</h1>\n </body>\n </html>\n ```\n\n - 项目结构\n\n ![image-20200729190325307](https://img-blog.csdnimg.cn/img_convert/466324d509f6a828585a4be6d4679e90.png)\n\n - 运行截图\n\n ![image-20200729190548307](https://img-blog.csdnimg.cn/img_convert/68373fda638c7763a109a885fc0b5508.png)\n\n3. 导入shiro整合spring的包——[官网](https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring),查看最新版本\n\n ```xml\n <!--\n Subject 用户\n SecurityManager 管理所有用户\n Realm 连接数据库\n -->\n \n <!--shiro整合spring的包-->\n <dependency>\n <groupId>org.apache.shiro</groupId>\n <artifactId>shiro-spring</artifactId>\n <version>1.5.3</version>\n </dependency>\n ```\n\n4. 编写导入配置类\n\n - 编写一个自定义类UserRealm\n\n ```java\n //自定义的UserRealm\n public class UserRealm extends AuthorizingRealm {\n //授权\n @Override\n protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {\n System.out.println(\"执行了=>授权doGetAuthorizationInfo\");\n return null;\n }\n \n //认证\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {\n System.out.println(\"执行了=>认证doGetAuthorizationInfo\");\n return null;\n }\n }\n ```\n\n - 编写配置ShiroConfig\n\n - 创建realm对象,需要自定义类\n - DefaultWebSecurityManager\n - ShiroFilterFactoryBean\n\n ```java\n @Configuration\n public class ShiroConfig {\n \n //3. shiroFilterFactoryBean\n \n @Bean\n public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier(\"getDefaultWebSecurityManager\") DefaultWebSecurityManager defaultWebSecurityManager) {\n ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();\n // 设置安全管理器\n bean.setSecurityManager(defaultWebSecurityManager);\n \n return bean;\n }\n \n //2. DefaultWebSecurityManager\n \n @Bean\n public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier(\"userRealm\") UserRealm userRealm) {\n DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();\n \n // 关联userRealm\n securityManager.setRealm(userRealm);\n return securityManager;\n }\n //1. 创建realm对象,需要自定义类\n \n @Bean\n public UserRealm userRealm() {\n return new UserRealm();\n }\n }\n ```\n\n## 3.2、Shiro实现登录拦截\n\n- 在`ShiroConfig`中的`getShiroFilterFactoryBean`方法中添加如下配置\n\n - anon: 无需认证就可以访问\n - authc: 必须认证了才能访问\n - user: 必须拥有记住我功能才能用\n - perms: 拥有对某个资源的权限才能访问\n - role: 拥有某个角色权限\n\n ```java\n Map<String, String> filterMap = new LinkedHashMap<>();\n filterMap.put(\"/user/add\",\"authc\");\n filterMap.put(\"/user/update\",\"authc\");\n bean.setFilterChainDefinitionMap(filterMap);\n ```\n\n- 点击首页的add或者update之后\n\n ![image-20200729191619576](https://img-blog.csdnimg.cn/img_convert/c9f9fefb4da860b0673c1b429afe13cb.png)\n\n- 添加拦截成功页面\n - 登录页面login.html\n\n ```html\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <title>登录页面</title>\n </head>\n <body>\n <h1>登录</h1>\n <hr>\n\n <form action=\"\">\n <p>用户名:<input type=\"text\" name=\"username\"></p>\n <p>密码:<input type=\"text\" name=\"password\"></p>\n <p>密码:<input type=\"submit\"></p>\n </form>\n </body>\n </html>\n ```\n\n - 在MyConfig中添加\n \n ```java\n @RequestMapping(\"/toLogin\")\n public String toLogin() {\n return \"login\";\n }\n ```\n \n - 在`ShiroConfig`中的`getShiroFilterFactoryBean`方法中添加如下配置\n \n ```java\n //设置登录的请求\n bean.setLoginUrl(\"/toLogin\");\n ```\n \n- 拦截成功页面\n\n ![image-20200729192409085](https://img-blog.csdnimg.cn/img_convert/fbf81af4b24493e6fc3cd0fff1ffe377.png)\n\n## 3.3、Shiro实现用户认证\n\n1. 在`MyController`中编写用户提交表单之后处理\n\n ```java\n @RequestMapping(\"/login\")\n public String login(String username, String password, Model model) {\n //获取一个用户\n Subject subject = SecurityUtils.getSubject();\n // 封装用户的登录数据\n UsernamePasswordToken token = new UsernamePasswordToken(username, password);\n \n try {\n subject.login(token);//执行登录的方法,如果没有异常就说明ok了\n return \"index\";\n } catch (UnknownAccountException e) {//用户名不存在\n model.addAttribute(\"msg\",\"用户名错误\");\n return \"login\";\n } catch (IncorrectCredentialsException e) {//密码不存在\n model.addAttribute(\"msg\",\"密码错误\");\n return \"login\";\n }\n \n }\n ```\n\n2. login.html的修改\n\n ```html\n <!DOCTYPE html>\n <html lang=\"en\" xmlns:th=\"http://www.thymeleaf.org\">\n <head>\n <meta charset=\"UTF-8\">\n <title>登录页面</title>\n </head>\n <body>\n <h1>登录</h1>\n <hr>\n \n <p th:text=\"${msg}\" style=\"color: red;\"></p>\n <form th:action=\"@{/login}\">\n <p>用户名:<input type=\"text\" name=\"username\"></p>\n <p>密码:<input type=\"text\" name=\"password\"></p>\n <p>密码:<input type=\"submit\"></p>\n </form>\n </body>\n </html>\n ```\n\n3. 用户输入登录信息\n\n - 页面\n\n ![image-20200729220647520](https://img-blog.csdnimg.cn/img_convert/31a223e1775bfcd4097a680c30b5a0e6.png)\n\n - 控制台\n\n ![image-20200729220926500](https://img-blog.csdnimg.cn/img_convert/5c3203b9c675c2fb6c121bd4cd33ab7c.png)\n\n4. 用户认证编写`UserRealm`中的认证(doGetAuthenticationInfo)\n\n ```java\n //认证\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n System.out.println(\"执行了=>认证doGetAuthorizationInfo\");\n // 用户名、密码, 数据中取\n String name = \"root\";\n String password = \"123456\";\n \n UsernamePasswordToken userToken = (UsernamePasswordToken) token;\n \n if (!userToken.getUsername().equals(name)) {\n return null;//抛出异常 UnknownAccountException\n }\n \n // 密码认证,shiro做\n return new SimpleAuthenticationInfo(\"\",password,\"\");\n }\n ```\n\n## 3.4、Shiro整合Mybatis\n\n1. 导入依赖\n\n ```xml\n <dependency>\n <groupId>org.projectlombok</groupId>\n <artifactId>lombok</artifactId>\n </dependency>\n <dependency>\n <groupId>mysql</groupId>\n <artifactId>mysql-connector-java</artifactId>\n </dependency>\n \n <dependency>\n <groupId>log4j</groupId>\n <artifactId>log4j</artifactId>\n <version>1.2.17</version>\n </dependency>\n \n <dependency>\n <groupId>com.alibaba</groupId>\n <artifactId>druid</artifactId>\n <version>1.1.23</version>\n </dependency>\n \n <!--引入mybatis,这是MyBatis官方提供的适配spring Boot的,而不是spring Boot自己的-->\n <dependency>\n <groupId>org.mybatis.spring.boot</groupId>\n <artifactId>mybatis-spring-boot-starter</artifactId>\n <version>2.1.3</version>\n </dependency>\n ```\n \n2. 配置文件application.yml的编写\n\n ```yml\n spring:\n datasource:\n username: root\n password: admin\n #?serverTimezone=UTC解决时区的报错\n url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8\n driver-class-name: com.mysql.cj.jdbc.Driver\n type: com.alibaba.druid.pool.DruidDataSource\n \n #Spring Boot 默认是不注入这些属性值的,需要自己绑定\n #druid 数据源专有配置\n initialSize: 5\n minIdle: 5\n maxActive: 20\n maxWait: 60000\n timeBetweenEvictionRunsMillis: 60000\n minEvictableIdleTimeMillis: 300000\n validationQuery: SELECT 1 FROM DUAL\n testWhileIdle: true\n testOnBorrow: false\n testOnReturn: false\n poolPreparedStatements: true\n \n #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入\n #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority\n #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j\n filters: stat,wall,log4j\n maxPoolPreparedStatementPerConnectionSize: 20\n useGlobalDataSourceStat: true\n connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500\n \n mybatis:\n type-aliases-package: nuc.ss.pojo\n mapper-locations: classpath:mapper/*.xml\n ```\n\n3. User类的编写\n\n ```java\n @Data\n @AllArgsConstructor\n @NoArgsConstructor\n public class User {\n private int id;\n private String name;\n private String pwd;\n }\n ```\n\n4. UserMapper.xml映射\n\n ```xml\n <?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n <!DOCTYPE mapper\n PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n <!--namespace=绑定一个对应的Dao/Mapper接口-->\n <mapper namespace=\"nuc.ss.mapper.UserMapper\">\n \n <select id=\"queryUserList\" resultType=\"User\">\n select * from mybatis.user;\n </select>\n \n <select id=\"queryUserById\" resultType=\"User\">\n select * from mybatis.user where id = #{id};\n </select>\n \n <insert id=\"addUser\" parameterType=\"User\">\n insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd});\n </insert>\n \n <update id=\"updateUser\" parameterType=\"User\">\n update mybatis.user set name=#{name},pwd = #{pwd} where id = #{id};\n </update>\n \n <delete id=\"deleteUser\" parameterType=\"int\">\n delete from mybatis.user where id = #{id}\n </delete>\n </mapper>\n ```\n \n5. UserService接口实现\n\n ```java\n public interface UserService {\n \n public User queryUserByName(String name);\n }\n ```\n\n6. UserServiceImpl业务逻辑\n\n ```java\n @Service\n public class UserServiceImpl implements UserService {\n \n @Autowired\n UserMapper userMapper;\n @Override\n public User queryUserByName(String name) {\n return userMapper.queryUserByName(name);\n }\n }\n \n ```\n\n7. 测试环境\n\n ```java\n @SpringBootTest\n class ShiroSpringbootApplicationTests {\n \n @Autowired\n UserService userService;\n @Test\n void contextLoads() {\n System.out.println(userService.queryUserByName(\"狂神\"));\n }\n \n }\n ```\n\n ![image-20200730121720922](https://img-blog.csdnimg.cn/img_convert/95b7fe783a1a224b2f82ea424def887b.png)\n\n8. `UserRealm`连接真实数据库\n\n ```java\n //认证\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n System.out.println(\"执行了=>认证doGetAuthorizationInfo\");\n \n UsernamePasswordToken userToken = (UsernamePasswordToken) token;\n \n // 真实数据库 用户名、密码, 数据中取\n User user = userService.queryUserByName(userToken.getUsername());\n \n if (user == null) {//没有这个人\n return null;\n }\n \n // 密码认证,shiro做\n return new SimpleAuthenticationInfo(\"\",user.getPwd(),\"\");\n }\n ```\n\n ![image-20200730180019861](https://img-blog.csdnimg.cn/img_convert/1f6e567f6f2143870ee5bc047f6d0fd3.png)\n \n9. 断点测试密码加密类型\n\n - 打断点Debug\n\n ![image-20200730182621912](https://img-blog.csdnimg.cn/img_convert/b9a727fd4f1b0e7bfc485eaae5e1f594.png)\n\n - 默认是`SimpleCredentialsMatcher`加密\n\n ![image-20200730181814293](https://img-blog.csdnimg.cn/img_convert/2f884afd2f7a9727a4a69453c7ea40be.png)\n\n - MD5加密——[测试](http://tool.chinaz.com/tools/md5.aspx)\n\n 123456——E10ADC3949BA59ABBE56E057F20F883E\n\n - MD5盐值加密\n\n - 所有加密\n\n ![image-20200730181944253](https://img-blog.csdnimg.cn/img_convert/ea500d77637cda2e17e3beecbc0fb125.png)\n\n## 3.5、Shiro实现用户授权\n\n1. `ShiroConfig`中的`getShiroFilterFactoryBean`方法添加认证代码\n\n ```java\n //授权,正常情况下,没有授权会跳转到为授权页面\n filterMap.put(\"/user/add\",\"perms[user:add]\");\n filterMap.put(\"/user/update\",\"perms[user:update]\");\n ```\n\n2. 登录之后点击add按钮会弹出如下页面\n\n ![image-20200730195133631](https://img-blog.csdnimg.cn/img_convert/9c0a17b27a9796d80ceb3aacbfdf7edc.png)\n\n3. 添加为授权页面\n\n - MyController\n\n ```java\n @RequestMapping(\"/noauto\")\n @ResponseBody\n public String unauthorized() {\n return \"未经授权,无法访问此页面\";\n }\n ```\n\n - `ShiroConfig`中的`getShiroFilterFactoryBean`方法中添加\n\n ```java\n //为授权页面\n bean.setUnauthorizedUrl(\"/noauto\");\n ```\n\n4. 再次测试\n\n ![image-20200730195807437](https://img-blog.csdnimg.cn/img_convert/998b16a316fa4c567276186b356b6bb1.png)\n\n ![image-20200730195946692](https://img-blog.csdnimg.cn/img_convert/a9d57b4328c47a05adbe24bd7db51789.png)\n\n 所以需要在UserRealm中为用户进行真正授权\n\n5. UserRealm类的修改\n\n ```java\n //自定义的UserRealm\n public class UserRealm extends AuthorizingRealm {\n \n @Autowired\n UserService userService;\n //授权\n @Override\n protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {\n System.out.println(\"执行了=>授权doGetAuthorizationInfo\");\n \n SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();\n \n //拿到当前登录的这个对象\n Subject subject = SecurityUtils.getSubject();\n User currentUser = (User)subject.getPrincipal();//拿到user对象\n \n //设置当前用户的权限\n info.addStringPermission(currentUser.getPerms());\n \n return info;\n }\n \n //认证\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n ......\n // 密码认证,shiro做\n return new SimpleAuthenticationInfo(user,user.getPwd(),\"\");\n }\n }\n \n ```\n\n6. 再次测试\n\n ![image-20200730202810034](https://img-blog.csdnimg.cn/img_convert/7e6190a52afeab6cace319c39eb5eba5.png)\n\n## 3.6、Shiro整合Thymeleaf\n\n1. shiro-thymeleaf整合包导入——[官网](https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro)\n\n ```xml\n <!--shiro-thymeleaf整合-->\n <dependency>\n <groupId>com.github.theborakompanioni</groupId>\n <artifactId>thymeleaf-extras-shiro</artifactId>\n <version>2.0.0</version>\n </dependency>\n ```\n\n2. 在ShiroConfig中整合ShiroDialect\n\n ```java\n // 整合ShiroDialect: 用来整合 Shiro thymeleaf\n @Bean\n public ShiroDialect getShiroDialect() {\n return new ShiroDialect();\n }\n ```\n\n3. index.html页面\n\n ```html\n <!DOCTYPE html>\n <html lang=\"en\" xmlns:th=\"http://www.thymeleaf.org\"\n xmlns:shiro=\"http://www.thymeleaf.org/thymeleaf-extras-shiro\">\n <head>\n <meta charset=\"UTF-8\">\n <title>首页</title>\n </head>\n <body>\n \n <div>\n <h1>首页</h1>\n <p th:text=\"${msg}\"></p>\n \n <!--用session实现,配合UserRealm中的session实现-->\n <!--<div th:if=\"${session.loginUser==null}\">\n <a th:href=\"@{/toLogin}\">登录</a>\n </div>-->\n \n <div shiro:notAuthenticated>\n <a th:href=\"@{/toLogin}\">登录</a>\n </div>\n \n <hr>\n \n <div shiro:hasPermission=\"user:add\">\n <a th:href=\"@{/user/add}\">add</a>\n </div>\n \n <div shiro:hasPermission=\"user:update\">\n <a th:href=\"@{/user/update}\">update</a>\n </div>\n \n </div>\n </body>\n </html>\n ```\n\n4. 页面显示\n\n ![image-20200730205736153](https://img-blog.csdnimg.cn/img_convert/cefe790cf1badf59f036710875bd2855.png)\n\n## 3.7、所有代码\n\n- ShiroConfig\n\n ```java\n package nuc.ss.config;\n \n import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;\n import org.apache.shiro.spring.web.ShiroFilterFactoryBean;\n import org.apache.shiro.web.mgt.DefaultWebSecurityManager;\n import org.springframework.beans.factory.annotation.Qualifier;\n import org.springframework.context.annotation.Bean;\n import org.springframework.context.annotation.Configuration;\n \n import java.util.LinkedHashMap;\n import java.util.Map;\n \n @Configuration\n public class ShiroConfig {\n \n //shiroFilterFactoryBean\n \n @Bean\n public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier(\"getDefaultWebSecurityManager\") DefaultWebSecurityManager defaultWebSecurityManager) {\n ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();\n // 设置安全管理器\n bean.setSecurityManager(defaultWebSecurityManager);\n \n // 添加shiro的内置过滤器\n /*\n anon: 无需认证就可以访问\n authc: 必须认证了才能访问\n user: 必须拥有记住我功能才能用\n perms: 拥有对某个资源的权限才能访问\n role: 拥有某个角色权限\n */\n \n //拦截\n Map<String, String> filterMap = new LinkedHashMap<>();\n //filterMap.put(\"/user/add\",\"authc\");\n //filterMap.put(\"/user/update\",\"authc\");\n \n \n //授权,正常情况下,没有授权会跳转到为授权页面\n filterMap.put(\"/user/add\",\"perms[user:add]\");\n filterMap.put(\"/user/update\",\"perms[user:update]\");\n \n filterMap.put(\"/user/*\",\"authc\");\n \n bean.setFilterChainDefinitionMap(filterMap);\n \n //设置登录的请求\n bean.setLoginUrl(\"/toLogin\");\n \n //为授权页面\n bean.setUnauthorizedUrl(\"/noauto\");\n \n return bean;\n }\n \n //DefaultWebSecurityManager\n \n @Bean\n public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier(\"userRealm\") UserRealm userRealm) {\n DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();\n \n // 关联userRealm\n securityManager.setRealm(userRealm);\n return securityManager;\n }\n //创建realm对象,需要自定义类\n \n @Bean\n public UserRealm userRealm() {\n return new UserRealm();\n }\n \n // 整合ShiroDialect: 用来整合 Shiro thymeleaf\n @Bean\n public ShiroDialect getShiroDialect() {\n return new ShiroDialect();\n }\n }\n ```\n\n- UserRealm\n\n ```java\n package nuc.ss.config;\n \n import nuc.ss.pojo.User;\n import nuc.ss.service.UserService;\n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.authc.*;\n import org.apache.shiro.authz.AuthorizationInfo;\n import org.apache.shiro.authz.SimpleAuthorizationInfo;\n import org.apache.shiro.realm.AuthorizingRealm;\n import org.apache.shiro.session.Session;\n import org.apache.shiro.subject.PrincipalCollection;\n import org.apache.shiro.subject.Subject;\n import org.springframework.beans.factory.annotation.Autowired;\n \n //自定义的UserRealm\n public class UserRealm extends AuthorizingRealm {\n \n @Autowired\n UserService userService;\n //授权\n @Override\n protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {\n System.out.println(\"执行了=>授权doGetAuthorizationInfo\");\n \n SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();\n \n //info.addStringPermission(\"user:add\");\n \n //拿到当前登录的这个对象\n Subject subject = SecurityUtils.getSubject();\n User currentUser = (User)subject.getPrincipal();//拿到user对象\n \n //设置当前用户的权限\n info.addStringPermission(currentUser.getPerms());\n \n return info;\n }\n \n //认证\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n System.out.println(\"执行了=>认证doGetAuthorizationInfo\");\n \n UsernamePasswordToken userToken = (UsernamePasswordToken) token;\n \n // 虚拟用户\n //String name = \"root\";\n //String password = \"123456\";\n //if (!userToken.getUsername().equals(name)) {\n // return null;//抛出异常 UnknownAccountException\n //}\n \n // 真实数据库 用户名、密码, 数据中取\n User user = userService.queryUserByName(userToken.getUsername());\n \n if (user == null) {//没有这个人\n return null;\n }\n \n //首页\n //Subject currentSubject = SecurityUtils.getSubject();\n //Session session = currentSubject.getSession();\n //session.setAttribute(\"loginUser\",user);\n \n \n // 密码认证,shiro做\n return new SimpleAuthenticationInfo(user,user.getPwd(),\"\");\n }\n }\n ```\n\n- MyController\n\n ```java\n package nuc.ss.controller;\n \n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.authc.IncorrectCredentialsException;\n import org.apache.shiro.authc.UnknownAccountException;\n import org.apache.shiro.authc.UsernamePasswordToken;\n import org.apache.shiro.subject.Subject;\n import org.springframework.stereotype.Controller;\n import org.springframework.ui.Model;\n import org.springframework.web.bind.annotation.RequestMapping;\n import org.springframework.web.bind.annotation.ResponseBody;\n \n @Controller\n public class MyController {\n \n @RequestMapping({\"/\",\"/index\"})\n public String toIndex(Model model) {\n model.addAttribute(\"msg\",\"hello,Shiro\");\n return \"index\";\n }\n \n @RequestMapping(\"/user/add\")\n public String add() {\n return \"user/add\";\n }\n \n @RequestMapping(\"/user/update\")\n public String update() {\n return \"user/update\";\n }\n \n @RequestMapping(\"/toLogin\")\n public String toLogin() {\n return \"login\";\n }\n \n @RequestMapping(\"/login\")\n public String login(String username, String password, Model model) {\n //获取一个用户\n Subject subject = SecurityUtils.getSubject();\n // 封装用户的登录数据\n UsernamePasswordToken token = new UsernamePasswordToken(username, password);\n \n try {\n subject.login(token);//执行登录的方法,如果没有异常就说明ok了\n return \"index\";\n } catch (UnknownAccountException e) {//用户名不存在\n model.addAttribute(\"msg\",\"用户名错误\");\n return \"login\";\n } catch (IncorrectCredentialsException e) {//密码不存在\n model.addAttribute(\"msg\",\"密码错误\");\n return \"login\";\n }\n }\n \n @RequestMapping(\"/noauto\")\n @ResponseBody\n public String unauthorized() {\n return \"未经授权,无法访问此页面\";\n }\n }\n \n ```\n\n- pom依赖\n\n ```xml\n <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n <modelVersion>4.0.0</modelVersion>\n <groupId>nuc.ss</groupId>\n <artifactId>shiro-springboot</artifactId>\n <version>0.0.1-SNAPSHOT</version>\n <name>shiro-springboot</name>\n <description>Demo project for Spring Boot</description>\n \n <properties>\n <java.version>1.8</java.version>\n <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n <spring-boot.version>2.3.0.RELEASE</spring-boot.version>\n </properties>\n \n <dependencies>\n \n <!--\n Subject 用户\n SecurityManager 管理所有用户\n Realm 连接数据库\n -->\n \n <!--shiro-thymeleaf整合-->\n <!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->\n <dependency>\n <groupId>com.github.theborakompanioni</groupId>\n <artifactId>thymeleaf-extras-shiro</artifactId>\n <version>2.0.0</version>\n </dependency>\n \n <dependency>\n <groupId>org.projectlombok</groupId>\n <artifactId>lombok</artifactId>\n </dependency>\n <dependency>\n <groupId>mysql</groupId>\n <artifactId>mysql-connector-java</artifactId>\n </dependency>\n \n <dependency>\n <groupId>log4j</groupId>\n <artifactId>log4j</artifactId>\n <version>1.2.17</version>\n </dependency>\n \n <dependency>\n <groupId>com.alibaba</groupId>\n <artifactId>druid</artifactId>\n <version>1.1.23</version>\n </dependency>\n \n <!--引入mybatis,这是MyBatis官方提供的适配spring Boot的,而不是spring Boot自己的-->\n <dependency>\n <groupId>org.mybatis.spring.boot</groupId>\n <artifactId>mybatis-spring-boot-starter</artifactId>\n <version>2.1.3</version>\n </dependency>\n \n <!--shiro整合spring的包-->\n <dependency>\n <groupId>org.apache.shiro</groupId>\n <artifactId>shiro-spring</artifactId>\n <version>1.5.3</version>\n </dependency>\n <!--thymeleaf-->\n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-thymeleaf</artifactId>\n </dependency>\n \n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-web</artifactId>\n </dependency>\n \n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-test</artifactId>\n <scope>test</scope>\n <exclusions>\n <exclusion>\n <groupId>org.junit.vintage</groupId>\n <artifactId>junit-vintage-engine</artifactId>\n </exclusion>\n </exclusions>\n </dependency>\n </dependencies>\n \n <dependencyManagement>\n <dependencies>\n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-dependencies</artifactId>\n <version>${spring-boot.version}</version>\n <type>pom</type>\n <scope>import</scope>\n </dependency>\n </dependencies>\n </dependencyManagement>\n \n <build>\n <plugins>\n <plugin>\n <groupId>org.apache.maven.plugins</groupId>\n <artifactId>maven-compiler-plugin</artifactId>\n <configuration>\n <source>1.8</source>\n <target>1.8</target>\n <encoding>UTF-8</encoding>\n </configuration>\n </plugin>\n <plugin>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-maven-plugin</artifactId>\n </plugin>\n </plugins>\n </build>\n \n </project>\n \n ```\n\n \n\n# 4、完美的解释\n\n[让 Apache Shiro 保护你的应用](https://www.infoq.cn/article/apache-shiro/?itm_source=infoq_en&itm_medium=link_on_en_item&itm_campaign=item_in_other_langs)','2021-08-25 10:24:37',NULL,0),(15,1,'【Springboot学习】SpringBoot集成Shiro前后端分离使用redis做缓存【个人博客搭建】','【Springboot学习】SpringBoot集成Shiro前后端分离使用redis做缓存【个人博客搭建】','# shiro-redis\n@[TOC](目录)\n\n[ alexxiyang / shiro-redis](https://travis-ci.org/github/alexxiyang/shiro-redis)\n\nshiro 只提供 ehcache 和 concurrentHashMap 的支持。这里有一个 shiro 可以使用的 redis 缓存实现。希望它会帮助你!\n\n# 下载\n\n您可以使用以下 2 种方式之一包含`shiro-redis`到您的项目中\n\n- 用于`git clone https://github.com/alexxiyang/shiro-redis.git`将项目克隆到本地工作区并自行构建 jar 文件\n- 添加maven依赖\n\n```\n<dependency>\n <groupId>org.crazycake</groupId>\n <artifactId>shiro-redis</artifactId>\n <version>3.3.1</version>\n</dependency>\n```\n\n> **注意:**\n> 3.3.0 错误地在java11 中编译。请使用java8编译的3.3.1\n\n## shiro-core/jedis 版本对比图\n\n| shiro-redis | **shiro** | **jedis** |\n| :------------: | :-------: | :-------: |\n| 3.2.3 | 1.3.2 | 2.9.0 |\n| 3.3.0 (java11) | 1.6.0 | 3.3.0 |\n| 3.3.1 (java8) | 1.6.0 | 3.3.0 |\n\n# 使用前\n\n这是您需要了解的第一件事。Shiro-redis 需要一个 id 字段来标识您在 Redis 中的授权对象。所以请确保你的主类有一个字段,你可以得到这个对象的唯一 id。请通过以下方式设置此 ID 字段名称`cacheManager.principalIdFieldName = <your id field name of principal object>`\n\n例如:\n\n如果你这样创建`SimpleAuthenticationInfo`:\n\n```\n@Override\nprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;\n UserInfo userInfo = new UserInfo();\n userInfo.setUsername(usernamePasswordToken.getUsername());\n return new SimpleAuthenticationInfo(userInfo, \"123456\", getName());\n}\n```\n\n那么`userInfo`对象就是你的主要对象。您需要确保`UserInfo`Redis 有一个唯一的字段来识别它。以`userId`为例:\n\n```\npublic class UserInfo implements Serializable{\n\n private Integer userId\n\n private String username;\n\n public String getUsername() {\n return username;\n }\n\n public void setUsername(String username) {\n this.username = username;\n }\n\n public Integer getUserId() {\n return this.userId;\n }\n}\n```\n\n将 userId 作为 的值`cacheManager.principalIdFieldName`,如下所示:\n\n```\ncacheManager.principalIdFieldName = userId\n```\n\n如果你使用的是 Spring,配置应该是\n\n```\n<property name=\"principalIdFieldName\" value=\"userId\" />\n```\n\n然后`shiro-redis`将调用`userInfo.getUserId()`获取 id 以保存 Redis 对象。\n\n# 如何配置?\n\n您可以配置`shiro-redis`in`shiro.ini`或 in`spring-*.xml`\n\n## 设置文件\n\n下面是 shiro.ini 的配置示例。\n\n### Redis 独立\n\n如果您在独立模式下运行 Redis\n\n```\n[main]\n#====================================\n# shiro-redis configuration [start]\n#====================================\n\n#===================================\n# Redis Manager [start]\n#===================================\n\n# Create redisManager\nredisManager = org.crazycake.shiro.RedisManager\n\n# Redis host. If you don\'t specify host the default value is 127.0.0.1:6379\nredisManager.host = 127.0.0.1:6379\n\n#===================================\n# Redis Manager [end]\n#===================================\n\n#=========================================\n# Redis session DAO [start]\n#=========================================\n\n# Create redisSessionDAO\nredisSessionDAO = org.crazycake.shiro.RedisSessionDAO\n\n# Use redisManager as cache manager\nredisSessionDAO.redisManager = $redisManager\n\nsessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager\n\nsessionManager.sessionDAO = $redisSessionDAO\n\nsecurityManager.sessionManager = $sessionManager\n\n#=========================================\n# Redis session DAO [end]\n#=========================================\n\n#==========================================\n# Redis cache manager [start]\n#==========================================\n\n# Create cacheManager\ncacheManager = org.crazycake.shiro.RedisCacheManager\n\n# Principal id field name. The field which you can get unique id to identify this principal.\n# For example, if you use UserInfo as Principal class, the id field maybe `id`, `userId`, `email`, etc.\n# Remember to add getter to this id field. For example, `getId()`, `getUserId()`, `getEmail()`, etc.\n# Default value is id, that means your principal object must has a method called `getId()`\ncacheManager.principalIdFieldName = id\n\n# Use redisManager as cache manager\ncacheManager.redisManager = $redisManager\n\nsecurityManager.cacheManager = $cacheManager\n\n#==========================================\n# Redis cache manager [end]\n#==========================================\n\n#=================================\n# shiro-redis configuration [end]\n#=================================\n```\n\n有关完整的可配置选项列表,请检查[Configurable Options](http://alexxiyang.github.io/shiro-redis/#configurable-options)。\n\n这是一个[教程项目,](https://github.com/alexxiyang/shiro-redis-tutorial)让您了解如何`shiro-redis`在`shiro.ini`.\n\n### Redis哨兵\n\n如果您使用的是Redis Sentinel,请将`redisManager`独立版本的配置替换为以下内容:\n\n```\n#===================================\n# Redis Manager [start]\n#===================================\n\n# Create redisManager\nredisManager = org.crazycake.shiro.RedisSentinelManager\n\n# Sentinel host. If you don\'t specify host the default value is 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381\nredisManager.host = 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381\n\n# Sentinel master name\nredisManager.masterName = mymaster\n\n#===================================\n# Redis Manager [end]\n#===================================\n```\n\n有关完整的可配置选项列表,请检查[Configurable Options](http://alexxiyang.github.io/shiro-redis/#configurable-options)。\n\n### Redis 集群\n\n如果您使用的是redis集群,请将`redisManager`独立版本的配置替换为以下内容:\n\n```\n#===================================\n# Redis Manager [start]\n#===================================\n\n# Create redisManager\nredisManager = org.crazycake.shiro.RedisClusterManager\n\n# Redis host and port list\nredisManager.host = 192.168.21.3:7000,192.168.21.3:7001,192.168.21.3:7002,192.168.21.3:7003,192.168.21.3:7004,192.168.21.3:7005\n\n#===================================\n# Redis Manager [end]\n#===================================\n```\n\n有关完整的可配置选项列表,请检查[Configurable Options](http://alexxiyang.github.io/shiro-redis/#configurable-options)。\n\n## Spring\n\n如果您使用的是 Spring\n\n### Redis 独立\n\n如果您在独立模式下运行 Redis\n\n```\n<!-- shiro-redis configuration [start] -->\n\n<!-- Redis Manager [start] -->\n<bean id=\"redisManager\" class=\"org.crazycake.shiro.RedisManager\">\n <property name=\"host\" value=\"127.0.0.1:6379\"/>\n</bean>\n<!-- Redis Manager [end] -->\n\n<!-- Redis session DAO [start] -->\n<bean id=\"redisSessionDAO\" class=\"org.crazycake.shiro.RedisSessionDAO\">\n <property name=\"redisManager\" ref=\"redisManager\" />\n</bean>\n<bean id=\"sessionManager\" class=\"org.apache.shiro.web.session.mgt.DefaultWebSessionManager\">\n <property name=\"sessionDAO\" ref=\"redisSessionDAO\" />\n</bean>\n<!-- Redis session DAO [end] -->\n\n<!-- Redis cache manager [start] -->\n<bean id=\"cacheManager\" class=\"org.crazycake.shiro.RedisCacheManager\">\n <property name=\"redisManager\" ref=\"redisManager\" />\n</bean>\n<!-- Redis cache manager [end] -->\n\n<bean id=\"securityManager\" class=\"org.apache.shiro.web.mgt.DefaultWebSecurityManager\">\n <property name=\"sessionManager\" ref=\"sessionManager\" />\n <property name=\"cacheManager\" ref=\"cacheManager\" />\n\n <!-- other configurations -->\n <property name=\"realm\" ref=\"exampleRealm\"/>\n <property name=\"rememberMeManager.cipherKey\" value=\"kPH+bIxk5D2deZiIxcaaaA==\" />\n</bean>\n\n<!-- shiro-redis configuration [end] -->\n```\n\n有关完整的可配置选项列表,请检查[Configurable Options](http://alexxiyang.github.io/shiro-redis/#configurable-options)。\n\n这里有一个[教程项目,](https://github.com/alexxiyang/shiro-redis-spring-tutorial)让你了解如何`shiro-redis`在spring配置文件中进行配置。\n\n### Redis哨兵\n\n如果使用redis sentinel,请将`redisManager`单机版的配置改为如下:\n\n```\n<!-- shiro-redis configuration [start] -->\n<!-- shiro redisManager -->\n<bean id=\"redisManager\" class=\"org.crazycake.shiro.RedisSentinelManager\">\n <property name=\"host\" value=\"127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381\"/>\n <property name=\"masterName\" value=\"mymaster\"/>\n</bean>\n```\n\n有关完整的可配置选项列表,请检查[Configurable Options](http://alexxiyang.github.io/shiro-redis/#configurable-options)。\n\n### Redis 集群\n\n如果使用redis集群,请将`redisManager`standalone版本的配置改为如下:\n\n```\n<!-- shiro-redis configuration [start] -->\n<!-- shiro redisManager -->\n<bean id=\"redisManager\" class=\"org.crazycake.shiro.RedisClusterManager\">\n <property name=\"host\" value=\"192.168.21.3:7000,192.168.21.3:7001,192.168.21.3:7002,192.168.21.3:7003,192.168.21.3:7004,192.168.21.3:7005\"/>\n</bean>\n```\n\n有关完整的可配置选项列表,请检查[Configurable Options](http://alexxiyang.github.io/shiro-redis/#configurable-options)。\n\n## 序列化器 Serializer\n\n由于 redis 只接受`byte[]`,就会出现序列化问题。Shiro-redis`StringSerializer`用作键序列化器和`ObjectSerializer`值序列化器。你可以使用你自己的自定义序列化器,只要这个自定义序列化器实现`org.crazycake.shiro.serializer.RedisSerializer`\n\n例如,我们可以像这样更改 keySerializer 的字符集\n\n```\n# If you want change charset of keySerializer or use your own custom serializer, you need to define serializer first\n#\n# cacheManagerKeySerializer = org.crazycake.shiro.serializer.StringSerializer\n\n# Supported encodings refer to https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html\n# UTF-8, UTF-16, UTF-32, ISO-8859-1, GBK, Big5, etc\n#\n# cacheManagerKeySerializer.charset = UTF-8\n\n# cacheManager.keySerializer = $cacheManagerKeySerializer\n```\n\n您可以使用自定义序列化程序替换这 4 个选项:\n\n- cacheManager.keySerializer\n- cacheManager.valueSerializer\n- redisSessionDAO.keySerializer\n- redisSessionDAO.valueSerializer\n\n## 可配置选项 Configurable Options\n\n以下是您可以在`shiro-redis`配置文件中使用的所有可用选项。\n\n### Redis管理器\n\n| **Title** | **Default** | **Description** |\n| :-------------- | :------------------------------------------ | :----------------------------------------------------------- |\n| host | `127.0.0.1:6379` | Redis 主机。如果您不指定主机,则默认值为`127.0.0.1:6379`. 如果你在哨兵模式或集群模式下运行 redis,用逗号分隔主机名,如`127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381` |\n| masterName | `mymaster` | **仅用于哨兵模式** Redis哨兵模式的主节点 |\n| timeout | `2000` | Redis 连接超时。jedis 尝试连接到 redis 服务器超时(以毫秒为单位) |\n| soTimeout | `2000` | **仅用于哨兵模式或集群模式** jedis尝试从redis服务器读取数据的超时时间 |\n| maxAttempts | `3` | **仅用于集群模式** 最大尝试连接到服务器 |\n| password | | Redis密码 |\n| database | `0` | Redis 数据库。默认值为 0 |\n| jedisPoolConfig | `new redis.clients.jedis.JedisPoolConfig()` | JedisPoolConfig. 您可以创建自己的 JedisPoolConfig 实例并根据需要设置属性 大多数情况下,您不需要设置 jedisPoolConfig 这里是一个示例。 `jedisPoolConfig = redis.clients.jedis.JedisPoolConfig` `jedisPoolConfig.testWhileIdle = false` `redisManager.jedisPoolConfig = jedisPoolConfig` |\n| count | `100` | 扫描计数。Shiro-redis 使用 Scan 来获取键,因此您可以定义每次迭代返回的元素数量。 |\n| jedisPool | `null` | **仅用于哨兵模式或单模式** 您可以创建自己的 JedisPool 实例并根据需要设置属性 |\n\n### RedisSessionDAO\n\n| **Title** | **Default** | **Description** |\n| :--------------------- | :------------------------------------------------ | :----------------------------------------------------------- |\n| redisManager | | 您刚刚在上面配置的RedisManager(必需) |\n| expire | `-2` | Redis 缓存键/值过期时间。过期时间以秒为单位。 特殊值: `-1`: no expire `-2`:与会话超时相同 默认值:`-2` **注意**:确保过期时间长于会话超时。 |\n| keyPrefix | `shiro:session:` | 为会话管理自定义您的 redis 密钥前缀 **注意**:请记住在前缀末尾添加冒号。 |\n| sessionInMemoryTimeout | `1000` | 当我们登录时,`doReadSession(sessionId)`会被 shiro 调用大约 10 次。所以shiro-redis将Session保存在ThreadLocal中来缓解这个问题。sessionInMemoryTimeout 是 ThreadLocal 中 Session 的到期时间。 大多数情况下,您不需要更改它。 |\n| sessionInMemoryEnabled | `true` | 是否在 ThreadLocal 中启用临时保存会话 |\n| keySerializer | `org.crazycake.shiro.serializer.StringSerializer` | 缓存管理器的key serializer 你可以改变key serializer 的实现或者StringSerializer 的编码。 支持的编码是指[支持的编码](https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html)。如`UTF-8`, `UTF-16`, `UTF-32`, `ISO-8859-1`, `GBK`, `Big5`, 等 更多细节请查看[Serializer](http://alexxiyang.github.io/shiro-redis/#serializer) |\n| valueSerializer | `org.crazycake.shiro.serializer.ObjectSerializer` | 缓存管理器的值序列化器 您可以更改值序列化器的实现 有关更多详细信息,请查看[Serializer](http://alexxiyang.github.io/shiro-redis/#serializer) |\n\n### CacheManager 缓存管理器\n\n| **Title** | **Default** | **Description** |\n| :------------------- | :------------------------------------------------ | :----------------------------------------------------------- |\n| redisManager | | 您刚刚在上面配置的RedisManager(必需) |\n| principalIdFieldName | `id` | 主体 ID 字段名称。您可以获得唯一 ID 来标识此主体的字段。 例如,如果您使用 UserInfo 作为 Principal 类,则 id 字段可能`id`是`userId`、`email`、 等。 请记住将 getter 添加到此 id 字段。例如,`getId()`, `getUserId(`),`getEmail()`等。 默认值是`id`,这意味着您的主体对象必须有一个方法调用`getId()` |\n| expire | `1800` | Redis 缓存键/值过期时间。 过期时间以秒为单位。 |\n| keyPrefix | `shiro:cache:` | 自定义您的 redis 键前缀以进行缓存管理 **注意**:请记住在前缀末尾添加冒号。 |\n| keySerializer | `org.crazycake.shiro.serializer.StringSerializer` | 缓存管理器的key serializer 你可以改变key serializer 的实现或者StringSerializer 的编码。 支持的编码是指[支持的编码](https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html)。如`UTF-8`, `UTF-16`, `UTF-32`, `ISO-8859-1`, `GBK`, `Big5`, 等 更多细节请查看[Serializer](http://alexxiyang.github.io/shiro-redis/#serializer) |\n| valueSerializer | `org.crazycake.shiro.serializer.ObjectSerializer` | 缓存管理器的值序列化器 您可以更改值序列化器的实现 有关更多详细信息,请查看[Serializer](http://alexxiyang.github.io/shiro-redis/#serializer) |\n\n# 弹簧启动器 Spring boot starter\n\n使用`Spring-Boot`集成是集成`shiro-redis`到基于 Spring 的应用程序的最简单方法。\n\n> 注意:`shiro-redis-spring-boot-starter`版本`3.2.1`基于`shiro-spring-boot-web-starter`版本`1.4.0-RC2`\n\n首先`shiro-redis`在您的应用程序类路径中包含Spring boot starter 依赖项\n\n```\n<dependency>\n <groupId>org.crazycake</groupId>\n <artifactId>shiro-redis-spring-boot-starter</artifactId>\n <version>3.3.1</version>\n</dependency>\n```\n\n下一步取决于您是创建了自己的`SessionManager`还是`SessionsSecurityManager`.\n\n## 如果您还没有创建自己的`SessionManager`或`SessionsSecurityManager`\n\n如果您没有自己的`SessionManager`或`SessionsSecurityManager`在您的配置中,`shiro-redis-spring-boot-starter`将创建`RedisSessionDAO`并`RedisCacheManager`为您。然后将它们注入`SessionManager`并`SessionsSecurityManager`自动注入。所以,你都准备好了。享受吧!\n\n## 如果您已经创建了自己的`SessionManager`或`SessionsSecurityManager`\n\n如果您创建了自己的`SessionManager`或`SessionsSecurityManager`喜欢这样的:\n\n```\n@Bean\npublic SessionsSecurityManager securityManager(List<Realm> realms) {\n DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realms);\n \n // other stuff...\n \n return securityManager;\n}\n```\n\n然后注入`redisSessionDAO`和已经`redisCacheManager`创建的`shiro-redis-spring-boot-starter`\n\n```\n@Autowired\nRedisSessionDAO redisSessionDAO;\n\n@Autowired\nRedisCacheManager redisCacheManager;\n```\n\n将它们注入您自己的`SessionManager`和`SessionsSecurityManager`\n\n```\n@Bean\npublic SessionManager sessionManager() {\n DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();\n\n // inject redisSessionDAO\n sessionManager.setSessionDAO(redisSessionDAO);\n \n // other stuff...\n \n return sessionManager;\n}\n\n@Bean\npublic SessionsSecurityManager securityManager(List<Realm> realms, SessionManager sessionManager) {\n DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realms);\n\n //inject sessionManager\n securityManager.setSessionManager(sessionManager);\n\n // inject redisCacheManager\n securityManager.setCacheManager(redisCacheManager);\n \n // other stuff...\n \n return securityManager;\n}\n```\n\n有关完整示例,请参阅[shiro-redis-spring-boot-tutorial](https://github.com/alexxiyang/shiro-redis-spring-boot-tutorial)\n\n### 配置属性 Configuration Properties\n\n以下是您可以在 Spring-boot 启动器配置中使用的所有可用选项\n\n| 标题 **Title** | 默认 **Default** | 说明 **Description** |\n| :------------------------------------------------ | :--------------- | :----------------------------------------------------------- |\n| shiro-redis.enabled | `true` | 启用 shiro-redis 的 Spring 模块 |\n| shiro-redis.redis-manager.deploy-mode | `standalone` | Redis 部署模式。选项: `standalone`, `sentinel`, \'集群\' |\n| shiro-redis.redis-manager.host | `127.0.0.1:6379` | Redis 主机。如果您不指定主机,则默认值为`127.0.0.1:6379`. 如果你在哨兵模式或集群模式下运行 redis,用逗号分隔主机名,如`127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381` |\n| shiro-redis.redis-manager.master-name | `mymaster` | **仅用于哨兵模式** Redis哨兵模式的主节点 |\n| shiro-redis.redis-manager.timeout | `2000` | Redis 连接超时。jedis 尝试连接到 redis 服务器超时(以毫秒为单位) |\n| shiro-redis.redis-manager.so-timeout | `2000` | **仅用于哨兵模式或集群模式** jedis尝试从redis服务器读取数据的超时时间 |\n| shiro-redis.redis-manager.max-attempts | `3` | **仅用于集群模式** 最大尝试连接到服务器 |\n| shiro-redis.redis-manager.password | | Redis密码 |\n| shiro-redis.redis-manager.database | `0` | Redis 数据库。默认值为 0 |\n| shiro-redis.redis-manager.count | `100` | 扫描计数。Shiro-redis 使用 Scan 来获取键,因此您可以定义每次迭代返回的元素数量。 |\n| shiro-redis.session-dao.expire | `-2` | Redis 缓存键/值过期时间。过期时间以秒为单位。 特殊值: `-1`: no expire `-2`:与会话超时相同 默认值:`-2` **注意**:确保过期时间长于会话超时。 |\n| shiro-redis.session-dao.key-prefix | `shiro:session:` | 为会话管理自定义您的 redis 密钥前缀 **注意**:请记住在前缀末尾添加冒号。 |\n| shiro-redis.session-dao.session-in-memory-timeout | `1000` | 当我们登录时,`doReadSession(sessionId)`会被 shiro 调用大约 10 次。所以shiro-redis将Session保存在ThreadLocal中来缓解这个问题。sessionInMemoryTimeout 是 ThreadLocal 中 Session 的到期时间。 大多数情况下,您不需要更改它。 |\n| shiro-redis.session-dao.session-in-memory-enabled | `true` | 是否在 ThreadLocal 中启用临时保存会话 |\n| shiro-redis.cache-manager.principal-id-field-name | `id` | 主体 ID 字段名称。您可以获得唯一 ID 来标识此主体的字段。 例如,如果您使用 UserInfo 作为 Principal 类,则 id 字段可能`id`是`userId`、`email`、 等。 请记住将 getter 添加到此 id 字段。例如,`getId()`, `getUserId(`),`getEmail()`等。 默认值是`id`,这意味着您的主体对象必须有一个方法调用`getId()` |\n| shiro-redis.cache-manager.expire | `1800` | Redis 缓存键/值过期时间。 过期时间以秒为单位。 |\n| shiro-redis.cache-manager.key-prefix | `shiro:cache:` | 自定义您的 redis 键前缀以进行缓存管理 **注意**:请记住在前缀末尾添加冒号。 |\n\n## Working with `spring-boot-devtools`\n\n如果您使用`shiro-redis`与`spring-boot-devtools`. 请将此行添加到`resources/META-INF/spring-devtools.properties`(如果没有此文件,则创建它):\n\n```\nrestart.include.shiro-redis=/shiro-[\\\\w-\\\\.]+jar\n```\n\n# 如果您发现任何错误\n**欢迎留言**\n\n**如果您在学习这篇文章之前对Shiro不了解或者掌握程度较低,建议先阅读博主我这篇文章**\n\n[【Springboot学习】Shiro快速入门及与SpringBoot集成](https://blog.csdn.net/qq_45696377/article/details/119818633)\n\n**如果您觉得学有余力,欢迎继续阅读如下文章**\n\n[【Java全栈】Java全栈学习路线及项目全资料总结【JavaSE+Web基础+大前端进阶+SSM+微服务+Linux+JavaEE】](https://blog.csdn.net/qq_45696377/article/details/110575362)\n\n**可爱的人给一个三连吧**','2021-08-25 10:25:32','2021-09-01 09:41:25',0),(16,1,'ewr ew ','werew r','werwer ','2021-08-25 22:38:29',NULL,1),(17,1,'双方都双方都','三大范式发','而我','2021-08-30 10:26:42',NULL,1),(18,1,'三大范式','撒旦法撒旦法','撒旦法','2021-08-30 10:39:55','2021-08-30 10:39:55',1),(19,1,'666测试标题3333','66摘要333','胡超','2021-08-30 11:36:29','2021-08-30 11:36:29',1),(20,1,'666测试标题3333','66摘要333','胡超','2021-08-30 11:36:30','2021-08-30 11:36:30',1),(21,1,'666测试标题3333','66摘要333','胡超888','2021-08-30 11:36:40','2021-08-30 11:37:32',1),(22,1,'胜多负少','士大夫','大师傅','2021-08-30 16:34:22','2021-08-31 22:48:54',1),(23,1,'士大夫但是 ','撒旦发射点发',' 双方都是都是','2021-09-01 09:26:47','2021-09-01 09:26:47',1),(24,1,'去问我去饿完全','请问','我去饿 ','2021-09-01 09:35:37','2021-09-01 09:35:37',1),(25,1,'士大夫','士大夫 ','士大夫','2021-09-01 09:37:20','2021-09-01 09:37:20',1),(26,1,'士大夫士大夫','胜多负少的 ','撒旦发射点','2021-09-01 09:39:21','2021-09-01 09:39:21',1),(27,1,'士大夫','士大夫',' 胜多负少','2021-09-01 09:47:01','2021-09-01 09:47:01',1),(28,1,'Mybatis作用域(Scope)和生命周期','理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。','@[TOC](目录)\n\n\n**理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。**\n画一个流程图,分析一下Mybatis的执行过程!\n\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/b13890d3c42e4e97a2aee7bfb4e227b3.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n\n**提示:对象生命周期和依赖注入框架**\n\n依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。 如果对如何通过依赖注入框架使用 MyBatis 感兴趣,可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。\n\n# SqlSessionFactoryBuilder\n\n> 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此\n> SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用\n> SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory\n> 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。\n\n - 一旦创建了SqlSessionFactory,就不再需要它了\n - 局部变量\n\n# SqlSessionFactory\n\n> SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用\n> SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory\n> 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。\n> 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。\n\n - 说白了就可以想象为:数据库连接池\n - SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建一个实例。\n - 因此SqlSessionFactory的最佳作用域是应用作用域(ApplicationContext)。\n - 最简单的就是使用单例模式或静态单例模式。\n\n# SqlSession\n\n> 每个线程都应该有它自己的 SqlSession 实例。SqlSession\n> 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession\n> 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如\n> Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP\n> 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。\n> 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保\n> SqlSession 关闭的标准模式:\n\n```java\ntry (SqlSession session = sqlSessionFactory.openSession()) {\n // 你的应用逻辑代码\n}\n```\n\n在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。\n\n - 连接到连接池的一个请求\n - SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。\n - 用完之后需要赶紧关闭,否则资源被占用!\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/99e8c5677529467282bad509fa7e4f6e.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n这里的每一个Mapper就代表一个具体的业务\n\n\n# 映射器实例\n\n> 映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession\n> 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession\n> 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。\n> 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像\n> SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:\n\n```java\ntry (SqlSession session = sqlSessionFactory.openSession()) {\n BlogMapper mapper = session.getMapper(BlogMapper.class);\n // 你的应用逻辑代码\n}\n```\n\n\n# 作用域理解\n\n - SqlSessionFactoryBuilder 的作用在于创建\n SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建\n SqlSessionFactory 的方法中,而不要让其长期存在。因此 **SqlSessionFactoryBuilder\n 实例的最佳作用域是方法作用域**(也就是局部方法变量)。\n \n - SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis\n 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis\n 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为\n SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。\n - 因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。**所以说\n SqlSessionFactory 的最佳作用域是应用作用域。**\n \n - 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection\n 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback\n 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给\n SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try...catch...finally...\n 语句来保证其正确关闭。\n \n - **所以 SqlSession 的最佳的作用域是请求或方法作用域。**\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/16472120b1ef45d8a3324c166880b691.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n# 更多相关文章点这里哦\n\n[【Java全栈】Java全套学习路线及项目资料总结【JavaSE+Web基础+MySQL+JavaEE】](https://blog.csdn.net/qq_45696377/article/details/110575362)','2021-09-01 11:28:51','2021-09-01 11:28:51',0),(29,1,'2020蓝桥杯救命稻草--之救命15题','第一阶段:基础知识(对标省三)\nStep1:基本语法(流程,控制,循环)\n(半小时内独立解决“打印图形”&“数的分解”可以跳过此Step)\n(拓展:用有限变量解决“斐波那契”问题。)\nStep2:JavaAPI(排序,容器,字符串,大数)\n(半小时内独立解决“日志统计”&“复数幂”可以跳过此Step)\n(拓展:解决“人物相关性分析”部分问题。)','## 2020蓝桥杯救命稻草\n\n**第一阶段:基础知识(对标省三)**\n ***Step1:基本语法(流程,控制,循环)***\n (半小时内独立解决“打印图形”&“数的分解”可以跳过此Step)\n (拓展:用有限变量解决“斐波那契”问题。)\n ***Step2:JavaAPI(排序,容器,字符串,大数)***\n(半小时内独立解决“日志统计”&“复数幂”可以跳过此Step)\n(拓展:解决“人物相关性分析”部分问题。)\n**第二阶段:能力提升(对标省二)**\n ***Step3:数组(前缀和,差分,简单DP)***\n (一小时内独立解决“最大下降矩阵”&“校门外的树”可以跳过此Step)\n (拓展:解决“人物相关性分析”问题。)\n ***Step4:函数(质数筛,汉诺塔,快速幂)***\n (一小时内独立解决“哥德巴赫猜想”&“大数运算”可以跳过此Step)(39级台阶)\n (拓展:解决“经典汉诺塔”问题。)\n****第三阶段:暴力美学(对标省一)****\n ***Step5:暴力破解一(全排列,二分)***\n(一小时内独立解决“瓜分种数”&“分巧克力”可以跳过此Step) \n ***Step6:暴力破解二(DFS,BFS)***\n(一小时内独立解决“全球变暖”&“迷宫求解”可以跳过此Step)\n**第四阶段:考试技巧(提升大概1~2个等级)**\n ***Step7:技巧篇(无)***\n\n - [ ] 注:精选蓝桥思维算法15道,且有对标水准,几乎涵盖所有会用到的思想和算法,后边题目难度较大,恳请对代码指正优化\n\n## Step1:基本语法(流程,控制,循环)\n\n\n\n\n## **1.“打印图形”**\n\n```python\n打印图形\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n小明刚学习完条件语句和循环语句,并且也打印了许多图形,比如菱形或者三角形。然后他突发奇想要打印一个六芒星,果然,他用了半小时就把六芒星给打印出来了,你能比他更快吗?小明为你加油哦!\n【输入格式】\n输入一行包含一个整数 n。\n【输出格式】\n输出一个六芒星,表示满足题意的图形。\n【样例输入】\n3\n4\n【样例输出】\n \n\n \n \n【评测用例规模与约定】 \n对于所有评测用例,1 ≤ n ≤ 20。 \n```\n\n```java\nimport java.util.Scanner;\npublic class A_PrintSixPointStar {\n public static void main(String[] args) {\n Scanner sc = new Scanner(System.in);\n int n = sc.nextInt();\n if(n == 1)\n System.out.print(\'*\');\n else if (n > 1 && n <= 20) {\n int i = 4 * n - 3;\n int j = 6 * n - 5;\n char arr[][] = new char[i][j];\n for(int a = 0; a < arr.length; a ++) {\n for(int b = 0; b < arr[1].length; b ++) { \n if (a < n - 1) {\n arr[a][(3 * n - 3) - a] = \'*\';\n arr[a][(3 * n - 3) + a] = \'*\';\n }\n else if (a == n - 1 && b % 2 == 0)\n arr[a][b] = \'*\';\n else if (a > n - 1 && a <= 2 * n - 2) {\n arr[a][a - n + 1] = \'*\';\n arr[a][3 * n - 3 - a] = \'*\';\n arr[a][3 * n - 3 + a] = \'*\';\n arr[a][7 * n - 7 - a] = \'*\'; \n } \n else if(a > 2 * n - 2)\n arr[a][b] = arr[4 * n - a - 4][b];\n else\n arr[a][b] = \' \'; \n }\n for (int k = 0; k < arr.length; k++) {\n for (int k2 = 0; k2 < arr[1].length; k2++)\n System.out.print(arr[k][k2]);\n System.out.println();\n } \n }\n }\n}\n```\n\n\n\n\n## 2.“数的分解”\n\n```python\n 数的分解\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n把 n分解成 3 个各不相同的正整数之和,并且要求每个正整数都不包\n含数字 2和 4,一共有多少种不同的分解方法?\n注意交换 3 个整数的顺序被视为同一种方法,例如 1999+3+18 和\n1999+18+3 被视为同一种。\n【输入格式】\n输入一行包含一个整数 n。\n【输出格式】\n输出一行,包含一个整数,表示满足条件的分解方法种数。\n【样例输入】\n2019\n【样例输出】\n40785\n【评测用例规模与约定】\n对于所有评测用例,100 ≤ n ≤ 2500。\n```\n\n```java\nimport java.util.Scanner;\n\npublic class B_TheNumberOfDecomposition {\n\n private static boolean judge(int s) {\n while( s > 0) {\n \n int t = s % 10;\n if(t== 2 || t== 4)\n return false;\n s /= 10; \n }\n return true;\n }\n \n public static void main(String[] args) {\n \n Scanner sc = new Scanner(System.in);\n int n = sc.nextInt();\n int sum = 0;\n \n if(n >= 100 && n <= 2500){\n for (int i = 1; i <= n / 3 ; i++){ \n for (int j = 1; j < n; j++) {\n int k = n - i - j;\n if(k > j && i < j) {\n if (judge(i) && judge(j) && judge(k))\n sum ++;\n }\n }\n } \n }\n System.out.println(sum); \n }\n}\n```\n\n\n\n## 3.“斐波那契”\n\n```python\n \n 斐波那契\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\nFibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1。\n当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少。\n<此题禁止使用数组容器等数据结构>\n \n【输入格式】\n输入一行包含一个整数 n。\n【输出格式】\n输出一行,包含一个整数,表示满足条件的数的和。\n【样例输入】\n22\n【样例输出】\n7704\n【评测用例规模与约定】\n对于所有评测用例,1 ≤ n ≤ 1,000,000。\n```\n\n```java\nimport java.util.Scanner;\n\npublic class C_FibonacciNumber {\n\n public static void main(String[] args) {\n Scanner sc = new Scanner(System.in);\n long n = sc.nextLong();\n long a =1;\n long b = 1;\n long t = 1;\n for (long i = 2; i < n; i++) {\n t = a;\n a += b;\n a %= 10007;\n b = t;\n }\n long result = a;\n System.out.println(result); \n }\n}\n```\n\n\n\n\n## Step2:JavaAPI(排序,容器,字符串,大数)\n\n## 1.“日志统计”\n\n```python\n 日志统计\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n小明维护着一个程序员论坛。现在他收集了一份\"点赞\"日志,日志共有N行。其中每一行的格式是:\nts id \n表示在ts时刻编号id的帖子收到一个\"赞\"。 \n \n现在小明想统计有哪些帖子曾经是\"热帖\"。如果一个帖子曾在任意一个长度为D的时间段内收到不少于K个赞,小明就认为这个帖子曾是\"热帖\"。 \n具体来说,如果存在某个时刻T满足该帖在[T, T+D)这段时间内(注意是左闭右开区间)收到不少于K个赞,该帖就曾是\"热帖\"。 \n \n给定日志,请你帮助小明统计出所有曾是\"热帖\"的帖子编号。 \n【输入格式】\n第一行包含三个整数N、D和K。 \n以下N行每行一条日志,包含两个整数ts和id。 \n \n对于50%的数据,1 <= K <= N <= 1000 \n对于100%的数据,1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000 \n \n【输出格式】\n按从小到大的顺序输出热帖id。每个id一行。 \n \n【输入样例】\n7 10 2 \n0 1 \n0 10 \n10 10 \n10 1 \n9 1\n100 3 \n100 3 \n \n【输出样例】\n1 \n3 \n```\n\n```java\npackage 精选思维15题;\n\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.Scanner;\n\npublic class 日志统计step2 {\n\n public static void main(String[] args) {\n // TODO Auto-generated method stub\n Scanner sc = new Scanner(System.in);\n int N = sc.nextInt();\n int D = sc.nextInt();\n int K = sc.nextInt();\n \n int arr[][] = new int [N][5];\n for (int i = 0; i < N; i++) {\n arr[i][0] = sc.nextInt();\n arr[i][1] = sc.nextInt();\n \n }\n for (int i = 0; i < N; i++) {\n int first =1;\n for (int j = i+1; j < N; j++) {\n if (arr[j][1]==arr[i][1]) \n first++;\n if (j==N-1) \n arr[i][2]=first;\n }\n }\n for (int i = 0; i < N; i++) {\n if (arr[i][2]>=K)\n arr[i][3]=1;\n }\n \n for (int i = 0; i < N; i++) {\n for (int j = i+1; j < N; j++) {\n if (arr[i][3]==1&&arr[i][1]==arr[j][1]&&arr[j][0]-arr[i][0]<D) \n arr[i][4]=1;\n }\n }\n HashSet<Integer> ar= new HashSet<Integer>(); \n for (int i = 0; i < N; i++) {\n if (arr[i][4]==1) {\n ar.add(arr[i][1]);\n }\n }\n \n Iterator<Integer> result = ar.iterator();\n while (result.hasNext()) {\n \n System.out.println(result.next());\n } \n }\n}\n```\n\n\n\n\n\n## 2.“复数幂”\n\n```python\n复数幂\n【问题描述】\n设i为虚数单位。对于任意正整数n,(2+3i)^n 的实部和虚部都是整数。\n求 (2+3i)^123456 等于多少? 即(2+3i)的123456次幂,这个数字很大,要求精确表示。\n \n答案写成 \"实部±虚部i\" 的形式,实部和虚部都是整数(不能用科学计数法表示),中间任何地方都不加空格,实部为正时前面不加正号。\n(2+3i)^2 写成: -5+12i,\n(2+3i)^5 的写成: 122-597i\n \n \n注意:需要提交的是一个很庞大的复数,不要填写任何多余内容。\n```\n\n```java\npackage 精选思维15题;\n\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.math.BigInteger;\n\npublic class 复数幂step2 {\n\n public static void main(String[] args) throws IOException {\n // TODO Auto-generated method stub\n int mi = 123456;\n BigInteger a = new BigInteger(\"2\");\n BigInteger b = new BigInteger(\"3\");\n BigInteger bigA;\n BigInteger bigB;\n \n for (int i = 0; i < mi-1; i++) {\n bigA =a.multiply(BigInteger.valueOf(2)).subtract(b.multiply(BigInteger.valueOf(3)));\n bigB =a.multiply(BigInteger.valueOf(3)).add(b.multiply(BigInteger.valueOf(2)));\n a = bigA;\n b = bigB;\n }\n \n FileWriter writer = new FileWriter(\"第十一届蓝桥/精选思维15题/bi.text\"); \n writer.write(b.compareTo(BigInteger.ZERO) == 1 ? a + \"+\" + b + \"i\" : a + \"\" + b + \"i\");\n writer.flush();\n }\n}\n```\n\n\n\n## 3.“人物相关性分析”\n\n \n\n```python\n 人物相关性分析\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n小明正在分析一本小说中的人物相关性。他想知道在小说中 Alice 和 Bob\n有多少次同时出现。\n更准确的说,小明定义 Alice 和 Bob“同时出现”的意思是:在小说文本\n中 Alice 和 Bob 之间不超过 K 个字符。\n例如以下文本:\nThis is a story about Alice and Bob. Alice wants to send a private message to Bob.\n假设 K = 20,则 Alice 和 Bob 同时出现了 2 次,分别是”Alice and Bob”\n和”Bob. Alice”。前者 Alice 和 Bob 之间有 5 个字符,后者有 2 个字符。\n注意:\n1. Alice 和 Bob 是大小写敏感的,alice 或 bob 等并不计算在内。\n2. Alice 和 Bob 应为单独的单词,前后可以有标点符号和空格,但是不能\n有字母。例如 Bobbi 並不算出现了 Bob。\n【输入格式】\n第一行包含一个整数 K。\n第二行包含一行字符串,只包含大小写字母、标点符号和空格。长度不超\n过 1000000。\n【输出格式】\n输出一个整数,表示 Alice 和 Bob 同时出现的次数。\n【样例输入】\n20\nThis is a story about Alice and Bob. Alice wants to send a private message to Bob.\n【样例输出】\n2\n【评测用例规模与约定】\n对于所有评测用例,1 ≤ K ≤ 1000000。\n```\n\n \n```java\nimport java.util.Scanner;\n\npublic class F_TheCharacterCorrelationAnalysis {\n\n public static void main(String[] args) {\n \n Scanner sc = new Scanner(System.in);\n int K = sc.nextInt();\n int num = 0;\n sc.nextLine();\n String paragraph = sc.nextLine();\n \n String[] stringList ;\n stringList = paragraph.split(\" \"); \n \n for (int i = 0; i < stringList.length - 1; i++) {\n int length = 0;\n if(stringList[i].equals(\"Alice\")){\n for (int j = i + 1; j < stringList.length; j++) {\n length += 1 + stringList[j].length();\n if(length <= K + 3 && stringList[j].equals(\"Bob\")) {\n num ++;\n break;\n }\n else if (length <= K + 4 && stringList[j].equals(\"Bob.\")) {\n num ++;\n break;\n }\n else if(length > K)\n break; \n }\n }\n \n else if(stringList[i].equals(\"Bob\")) {\n for (int j = i + 1; j < stringList.length; j++) {\n length += 1 + stringList[j].length();\n if(length <= K + 5 && stringList[j].equals(\"Alice\")) {\n num ++;\n break;\n }\n else if(length <= K + 6 && stringList[j].equals(\"Alice.\")) {\n num ++;\n break;\n }\n else if(length > K)\n break;\n }\n }\n else if(stringList[i].equals(\"Alice.\")){\n for (int j = i + 1; j < stringList.length; j++) {\n length += 1 + stringList[j].length();\n if(length <= K + 3 && stringList[j].equals(\"Bob\")) {\n num ++;\n break;\n }\n else if(length <= K + 4 && stringList[j].equals(\"Bob.\")) {\n num ++;\n break;\n }\n else if(length > K)\n break; \n }\n }\n \n else if(stringList[i].equals(\"Bob.\")) {\n for (int j = i + 1; j < stringList.length; j++) {\n length += 1 + stringList[j].length();\n if(length <= K + 5 && stringList[j].equals(\"Alice\")) {\n num ++;\n break;\n }\n if(length <= K + 6 && stringList[j].equals(\"Alice.\")) {\n num ++;\n break;\n }\n else if(length > K)\n break;\n }\n }\n }\n \n \n System.out.println(num); \n }\n}\n```\n\n\n\n\n```java\n 2020/08/28 14:31 \npackage 精选思维15题;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Scanner;\n\npublic class 人物相关性分析 {\n\n private static String getTemplateContent() throws Exception {\n File file = new File(\"C:\\\\Users\\\\王小明\\\\Desktop\\\\算法蓝桥\\\\step2评测标准\\\\人物相关性分析测评标准\\\\data8.in\");\n if(!file.exists())\n return null;\n FileInputStream inputStream = new FileInputStream(file);\n int length = inputStream.available();\n byte bytes[] = new byte[length];\n inputStream.read(bytes);\n inputStream.close();\n String str = new String(bytes, StandardCharsets.UTF_8);\n return str;\n }\n \n public static void main(String[] args) throws Exception {\n \n Scanner sc = new Scanner(System.in);\n int K = sc.nextInt();\n int num = 0;\n sc.nextLine();\n String first_paragraph = getTemplateContent();\n long start_time = System.currentTimeMillis();\n StringBuilder paragraph = new StringBuilder(first_paragraph);\n for (int i = 0; i < paragraph.length(); i++) {\n char temporary = paragraph.charAt(i);\n if(temporary == \'.\' || temporary == \',\' || temporary == \'?\' || temporary == \'!\' || temporary == \':\' || temporary == \';\') {\n {\n paragraph.insert(i, \" \");\n paragraph.insert(i + 2, \" \");\n i += 3; \n }\n }\n }\n String[] stringList ;\n stringList = paragraph.toString().split(\" \");\n\n for (int i = 0; i < stringList.length - 1; i++) {\n int length = 0;\n if(stringList[i].equals(\"Alice\")){\n for (int j = i + 1; j < stringList.length; j++) {\n length += 1 + stringList[j].length();\n if(length <= K + 3 && stringList[j].equals(\"Bob\")) {\n num ++;\n break;\n }\n else if(length > K)\n break; \n }\n }\n \n else if(stringList[i].equals(\"Bob\")) {\n for (int j = i + 1; j < stringList.length; j++) {\n length += 1 + stringList[j].length();\n if(length <= K + 5 && stringList[j].equals(\"Alice\")) {\n num ++;\n break;\n }\n else if(length > K)\n break;\n }\n }\n }\n long end_time = System.currentTimeMillis();\n long times = (end_time - start_time) / 1000;\n System.out.println(num);\n System.out.println(times);\n \n }\n}\n```\n\n\n\n\n\n## 第二阶段:能力提升(对标省二)\n\n**Step3:数组(前缀和,差分,简单DP)** \n\n\n## 1.“最大下降矩阵”\n\n\n```python\n最大下降矩阵\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n我们称一个矩阵是下降矩阵,当且仅当,矩阵的每一列都是严格下降的。很显然,这个要求很苛刻,大多数矩阵都无法满足。但是显然如果消去一些行,一定可以使得这个矩阵变成下降矩阵。\n\n 现在给出一个n行m列的矩阵,请你求出最少消去多少行,可以使得这个矩阵变为下降矩阵。\n【输入格式】\n输入第一行包含两个正整数n,m分别表示矩阵的行数和列数。(1<=n,m<=300)\n接下来n行,每行有m个数,中间用空格隔开,每个数都小于2^31.\n【输出格式】\n输出一行,包含一个整数,表示最少消去的行数。\n【样例输入1】\n1 3\n1 2 3\n【样例输出1】\n0\n【样例输入2】\n3 1\n3\n1\n2\n【样例输出2】\n1\n```\n\n```java\nimport java.util.Scanner;\n\npublic class G_TheBiggestDeclineMatrix {\n\n public static void main(String[] args) {\n\n Scanner sc = new Scanner(System.in);\n int n = sc.nextInt();\n int m = sc.nextInt();\n sc.nextLine();\n int[][] matrix = new int[n][m];\n int[] decline = new int [n];\n for(int i = 0; i < n; i ++) {\n decline[i] = 1;\n for(int j = 0; j < m; j ++)\n matrix[i][j] = sc.nextInt();\n sc.nextLine();\n }\n \n for(int i = 1; i < n; i ++) {\n for(int j = i - 1; j > 0; j --) {\n if(decline[i] > decline[j])\n continue;\n boolean flag = true;\n for(int k = 0; k < m; k ++) {\n if(matrix[i][k] < matrix[j][k]) {\n flag = false;\n break;\n } \n }\n if(flag)\n decline[i] = decline[j] + 1;\n }\n }\n \n int max_decline = 0;\n for(int i = 0; i < n; i ++)\n max_decline = Math.max(max_decline, decline[i]);\n int result = n - max_decline;\n System.out.println(result);\n }\n}\n```\n\n\n\n\n## 2.“校门外的树”\n\n```python\n校门外的树\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。\n由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。\n【输入格式】\n第一行有两个整数:L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。【输出格式】\n输出一行,包含一个整数,表示马路上剩余的树的数目。\n【样例输入】\n500 3\n150 300\n100 200\n470 471\n【样例输出】\n298\n【评测用例规模与约定】\n对于 20% 的评测用例,区域之间没有重合的部分。\n对于所有评测用例,区域之间有重合的情况。\n```\n\n```java\nimport java.util.Scanner;\n\npublic class H_TreesOutsideTheSchoolGate {\n\n public static void main(String[] args) {\n Scanner sc = new Scanner(System.in);\n int L = sc.nextInt();\n int M = sc.nextInt();\n sc.nextLine();\n \n int[][] area = new int[M][2];\n for (int i = 0; i < M; i++) {\n for (int j = 0; j < 2; j++)\n area[i][j] = sc.nextInt();\n sc.nextLine();\n }\n \n int[][] length = new int[L + 1][2];\n for (int i = 0; i <= L; i++) {\n length[i][0] = i;\n length[i][1] = 0;\n }\n \n for (int i = 0; i < M; i++)\n for (int k = area[i][0]; k <= area[i][1]; k++)\n length[k][1] = 1;\n \n int result = 0;\n for (int i = 0; i <= L; i++) {\n if(length[i][1] == 0)\n result ++;\n }\n System.out.println(result);\n }\n}\n```\n\n\n\n\n\n## **Step4:函数(质数筛,汉诺塔,快速幂)**\n\n\n## 1.“哥德巴赫猜想”\n\n```python\n哥德巴赫猜想\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n哥德巴赫1742年给欧拉的信中哥德巴赫提出了以下猜想:任何一个≥6的偶数,都可以表示成两个奇质数之和.。但是哥德巴赫自己无法证明它,于是就写信请教赫赫有名的大数学家欧拉帮忙证明,但是一直到死,欧拉也无法证明。\n \n现在你的朋友坤坤告诉你,哥德巴赫的猜想是错误的,现在坤坤告诉你一个≥6的偶数,请你告诉他组成该偶数的两个奇数。\n【输入格式】\n输入一行包含一个偶数 n。\n【输出格式】\n输出一行,包含两个整数,表示满足条件的奇数。\n【样例输入】\n6\n【样例输出】\n3 3\n【评测用例规模与约定】\n对于所有评测用例,6 ≤ n ≤ 1,000,000。\n```\n\n# 方法1\n\n```java\nimport java.util.Scanner;\n\npublic class I_GoldbachConjecture {\n\n public static void main(String[] args) {\n \n Scanner sc = new Scanner(System.in);\n int n = sc.nextInt();\n \n int middle = n / 2;\n \n int[][] arr = new int[middle][3];\n for (int i = 3; i <= middle; i++) {\n arr[i - 3][0] = i;\n arr[i - 3][1] = n - i;\n }\n for (int i = 0; i < arr.length; i++) {\n for (int j = 2; j < arr[i][0]; j++) {\n if(arr[i][0] % j != 0 && arr[i][0] - j == 1) {\n for (int k = 2; k < arr[i][1]; k++) \n if(arr[i][1] % k != 0 && arr[i][1] - k == 1)\n arr[i][2] = 1;\n }\n }\n }\n \n for (int i = 0; i < arr.length; i++) {\n if(arr[i][2] == 1)\n System.out.println(arr[i][0] + \" \" + arr[i][1]);\n break;\n }\n }\n}\n```\n\n\n\n# 方法2(最标准)\n\n```java\npublic static void main(String[] args) {\n Scanner sc = new Scanner(System.in);\n int a = sc.nextInt();\n \n for (int i = 3; i < a / 2; i += 2) {\n int j = a - i;\n if (isPrime(i) && isPrime(j)) {\n System.out.println(i + \" \" + j);\n break;\n }\n }\n }\n\n public static boolean isPrime(int i) {\n for (int j = 2; j < i - 1; j++)\n if (i % j == 0)\n return false;\n return true;\n }\n```\n\n\n\n## 2.“大数运算”\n\n```python\n大数运算\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n坤坤刚刚得知你学会了Java的大数运算,所以坤坤决定考考你,坤坤告诉你三个正整数。X,Y,Z,表示X的Y次方对Z取模。\n【输入格式】\n输入一行包含三个整数 X Y Z。\n【输出格式】\n输出一行,包含一个整数,运算后取模的结果。\n【样例输入】\n2 3 1000\n【样例输出】\n8\n【评测用例规模与约定】\n对于所有评测用例,1 ≤ X,Y,Z ≤ 1,000,000。\n```\n\n```java\nimport java.math.BigInteger;\nimport java.util.Scanner;\n\npublic class J_LargeNumberOperation {\n\n public static void main(String[] args) {\n Scanner sc = new Scanner(System.in);\n BigInteger X = sc.nextBigInteger();\n int Y = sc.nextInt();\n BigInteger Z = sc.nextBigInteger();\n BigInteger result = X;\n \n for (int i = 0; i < Y - 1; i++) {\n result = (result.multiply(X).mod(Z));\n }\n System.out.println(result);\n }\n}\n```\n\n\n\n## 3.“经典汉诺塔”\n\n```python\n经典汉诺塔\n【问题描述】\n汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?\n\n\n\n【输入格式】\n输入一行包含一个整数 n,表示圆盘的层数。\n【输出格式】\n输出圆盘的移动路径。\n【样例输入】\n3\n【样例输出】\na->c\na->b\nc->b\na->c\nb->a\nb->c\na->c\n```\n\n```java\nimport java.util.Scanner;\n\npublic class K_TheClassicHanoi {\n\n private static void move(char A, char C) {\n System.out.println(A + \"->\" + C);\n }\n \n private static void hanoi(int n, char A, char B, char C) {\n if(n == 1)\n move(A, C);\n else {\n hanoi(n - 1, A, C, B);\n move(A, C);\n hanoi(n - 1, B, A, C);\n }\n }\n \n public static void main(String[] args) {\n \n Scanner sc = new Scanner(System.in);\n int n = sc.nextInt();\n char A = \'A\';\n char B = \'B\';\n char C = \'C\';\n \n hanoi(n, A, B, C);\n }\n}\n```\n\n\n\n\n## 第三阶段:暴力美学(对标省一)\n\n## Step5:暴力破解一(全排列,二分)\n\n\n## 1.“瓜分种数”\n\n```python\n 瓜分种数\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n有n个糖果,要分给n个小朋友,请问有多少种分配方案,输出每种分配方案。\n【输入格式】\n输入一行包含一个整数 n。\n【输出格式】\n输出一行,包含一个整数,表示满足条件的方法种数。\n输出每种分配方案。\n【样例输入】\n3\n【样例输出】\n6\n1 2 3\n1 3 2\n2 1 3\n2 3 1\n3 1 2\n3 2 1\n【评测用例规模与约定】\n对于所有评测用例,1 ≤ n ≤ 12。\n```\n\n```java\nimport java.util.Scanner;\n\npublic class L_DivideSpecies {\n\n public static int[] arr;\n static long sum;\n private static void nercous(int n) {\n if(n == arr.length - 1) {\n for (int i = 0; i <= n; i++)\n System.out.print(arr[i] + (i == n ? \"\" : \" \"));\n System.out.println();\n }else {\n for (int i = n; i < arr.length; i++) {\n int t = arr[i]; \n arr[i] = arr[n];\n arr[n] = t;\n nercous(n + 1);\n t = arr[i];\n arr[i] = arr[n];\n arr[n] = t;\n }\n } \n }\n \n public static void main(String[] args) {\n Scanner sc = new Scanner(System.in);\n int n = sc.nextInt();\n arr = new int[n];\n for (int i = 0; i < n; i++)\n arr[i] = sc.nextInt();\n for (int i = 0; i < n; i++)\n sum += i + 1;\n System.out.println(sum);\n nercous(0);\n }\n}\n```\n\n\n\n## 2.“分巧克力”\n\n```python\n分巧克力\n时间限制: 1.0s 内存限制: 512.0MB\n【问题描述】\n 儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。\n 小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。\n \n 为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:\n \n 1. 形状是正方形,边长是整数 \n 2. 大小相同 \n \n例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。\n \n当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?\n \n【输入格式】\n第一行包含两个整数N和K。(1 <= N, K <= 100000) \n以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)\n输入保证每位小朋友至少能获得一块1x1的巧克力。 \n \n【输出格式】\n输出切出的正方形巧克力最大可能的边长。\n \n【样例输入】\n2 10 \n6 5 \n5 6 \n \n【样例输出】\n2\n```\n\n\n```java\nimport java.util.Scanner;\n\npublic class M_PointsOfChocolate {\n\n private static void segmentation(int left, int right, int N, Integer K, int[][] Chocolate) {\n while(left <= right) {\n int middle = (left + right) / 2;\n int sum = 0;\n for (int i = 0; i < N; i++)\n sum += (Chocolate[i][0] / middle) * (Chocolate[i][1] / middle);\n if(right - left == 1) {\n System.out.println(left);\n break;\n }else if(sum < K) \n right = middle;\n else if(sum > K)\n left = middle; \n }\n }\n \n public static void main(String[] args) {\n Scanner sc = new Scanner(System.in);\n int N = sc.nextInt();\n int K = sc.nextInt();\n int[][] chocolate = new int[N][2];\n \n int Max_length = 0;\n for (int i = 0; i < N; i++) {\n chocolate[i][0] = sc.nextInt();\n chocolate[i][1] = sc.nextInt();\n sc.nextLine();\n int Max_middle = Math.max(chocolate[i][0], chocolate[i][1]);\n Max_length = Math.max(Max_length, Max_middle);\n } \n \n int left = 1;\n int right = Max_length;\n segmentation(left, right, N, K, chocolate); \n }\n}\n```\n\n\n\n\n\n\n## Step6:暴力破解二(DFS,BFS)\n\n\n\n## 1.“全球变暖”\n\n \n\n```python\n全球变暖\n【问题描述】\n你有一张某海域NxN像素的照片,\".\"表示海洋、\"#\"表示陆地,如下所示:\n \n.......\n.##....\n.##....\n....##.\n..####.\n...###.\n.......\n \n其中\"上下左右\"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。 \n \n由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。 \n \n例如上图中的海域未来会变成如下样子:\n \n....... \n.......\n.......\n.......\n....#..\n.......\n.......\n \n请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。 \n \n【输入格式】\n第一行包含一个整数N。 (1 <= N <= 1000) \n以下N行N列代表一张海域照片。 \n \n照片保证第1行、第1列、第N行、第N列的像素都是海洋。 \n \n【输出格式】\n一个整数表示答案。\n \n【输入样例】\n7\n.......\n.##....\n.##....\n....##.\n..####.\n...###.\n....... \n \n【输出样例】\n1 \n \n \n \n资源约定:\n峰值内存消耗(含虚拟机) < 256M\nCPU消耗 < 1000ms\n \n \n请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。\n \n所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。\n不要使用package语句。不要使用jdk1.7及以上版本的特性。\n主类的名字必须是:Main,否则按无效代码处理。\n```\n\n```java\nimport java.util.Scanner;\n\npublic class N_GlobalWarming {\n\n public static int[][][] world;\n public static int[][][] new_world;\n public static int N;\n \n public static boolean DFS(int i, int j) {\n boolean flag = true;\n if(new_world[i][j][0] == 1)\n flag = false;\n \n world[i][j][0] = 1;\n \n for(int o = -1; o < 2; o ++) {\n for(int p = -1; p < 2; p ++) {\n if(Math.abs(o + p) != 1)\n continue;\n if(i + o < 1 || i + o > N || j + p < 1 || j + p > N || world[i + o][j + p][0] == 1)\n continue;\n flag = DFS(i + o, j + p) || flag; \n }\n }\n \n return flag;\n }\n \n public static void main(String[] args) {\n \n Scanner sc = new Scanner(System.in);\n N = sc.nextInt();\n sc.nextLine();\n world = new int[N + 2][N + 2][1];\n new_world = new int[N + 2][N + 2][1];\n \n for(int i = 0; i < N; i ++) {\n String temporary = sc.nextLine();\n for(int j = 0; j < N; j ++) {\n if(temporary.charAt(j) == \'.\') {\n world[i + 1][j + 1][0] = 1;\n new_world[i + 1][j + 1][0] = 1;\n }\n }\n }\n \n// 0表示陆地 1表示海洋 2表示陆转海 \n for(int i = 1; i <= N; i ++) {\n for(int j = 1; j <= N; j ++) {\n if(world[i][j][0] == 0)\n if(world[i][j - 1][0] == 1 || world[i][j + 1][0] == 1 || world[i - 1][j][0] == 1 || world[i + 1][j][0] == 1)\n new_world[i][j][0] = 1;\n }\n }\n \n int sum = 0;\n for(int i = 1; i <= N; i ++) {\n for(int j = 1; j <= N; j ++) {\n if(world[i][j][0] == 0)\n if(!DFS(i, j))\n sum ++;\n }\n }\n System.out.println(sum);\n }\n}\n```\n\n\n\n\n\n\n## 2.“迷宫求解”\n此题在做\n\n - [ ] List item\n\n## 迷宫求解\n\n```python\n【问题描述】\n下图给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可\n以通行的地方。\n010000\n000100\n001001\n110000\n迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这\n个它的上、下、左、右四个方向之一。\n对于上面的迷宫,从入口开始,可以按DRRURRDDDR 的顺序通过迷宫,\n一共 10 步。其中 D、U、L、R 分别表示向下、向上、向左、向右走。\n对于下面这个更复杂的迷宫(30 行 50 列),请找出一种通过迷宫的方式,\n其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。\n请注意在字典序中D<L<R<U。(如果你把以下文字复制到文本文件中,请务\n必检查复制的内容是否与文档中的一致)\n01010101001011001001010110010110100100001000101010\n00001000100000101010010000100000001001100110100101\n01111011010010001000001101001011100011000000010000\n01000000001010100011010000101000001010101011001011\n00011111000000101000010010100010100000101100000000\n11001000110101000010101100011010011010101011110111\n00011011010101001001001010000001000101001110000000\n10100000101000100110101010111110011000010000111010\n00111000001010100001100010000001000101001100001001\n11000110100001110010001001010101010101010001101000\n00010000100100000101001010101110100010101010000101\n11100100101001001000010000010101010100100100010100\n00000010000000101011001111010001100000101010100011\n10101010011100001000011000010110011110110100001000\n10101010100001101010100101000010100000111011101001\n10000000101100010000101100101101001011100000000100\n10101001000000010100100001000100000100011110101001\n00101001010101101001010100011010101101110000110101\n11001010000100001100000010100101000001000111000010\n00001000110000110101101000000100101001001000011101\n10100101000101000000001110110010110101101010100001\n00101000010000110101010000100010001001000100010101\n10100001000110010001000010101001010101011111010010\n00000100101000000110010100101001000001000000000010\n11010000001001110111001001000011101001011011101000\n00000110100010001000100000001000011101000000110011\n10101000101000100010001111100010101001010000001000\n10000010100101001010110000000100101010001011101000\n00111100001000010000000110111000000001000000001011\n10000001100111010111010001000110111010101101111000\n【答案提交】\n这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一\n个字符串,包含四种字母 D、U、L、R,在提交答案时只填写这个字符串,填\n写多余的内容将无法得分。\n```\n\n -\n','2021-09-01 16:00:55','2021-09-01 16:00:55',0),(30,1,'【MySQL学习】数据库问题及着重点汇总','1、字符函数 concat 拼接 substr 截取子串(索引从1开始) upper转换成大写 lower转换成小写 trim 去前后指定的空格和字符 ltrim 去左边空格 rtrim去右边空格 replace 替换 lpad 左填充 rpad 右填充 instr 返回子串第一次出现的索引 length 获取字节个数','@[TOC](目录)\n# SQL的语言分类\n**DQL(Data Query Language):数据查询语言 select** \n\n**DML(Data Manipulate Language):数据操作语言 insert 、update、delete** \n\n**DDL(Data Define Language):数据定义语言 create、drop、alter** \n\n**TCL(Transaction Control Language):事务控制语言 commit、rollback **\n\n\n## DQL语言\n**DQL( Data Query Language 数据查询语言 )**\n\n - 查询数据库数据 , **如SELECT**语句\n - 简单的单表查询或多表的复杂查询和嵌套查询\n - 是数据库语言中最核心,最重要的语句\n - 使用频率最高的语句\n\n> SELECT语法\n\n```sql\nSELECT [ALL | DISTINCT]\n{* | table.* | [table.field1[as alias1][,table.field2[as alias2]][,...]]}\nFROM table_name [as table_alias]\n [left | right | inner join table_name2] -- 联合查询\n [WHERE ...] -- 指定结果需满足的条件\n [GROUP BY ...] -- 指定结果按照哪几个字段来分组\n [HAVING] -- 过滤分组的记录必须满足的次要条件\n [ORDER BY ...] -- 指定查询记录按一个或多个条件排序\n [LIMIT {[offset,]row_count | row_countOFFSET offset}];\n -- 指定查询的记录从哪条至哪条\n# 注意 : [ ] 括号代表可选的 , { }括号代表必选得\n```\n\n### 1:基础查询\n\n \n\n```sql\n语法: SELECT 要查询的东西 【FROM 表名】;\n```\n\n### 进阶2:条件查询\n\n\n```sql\n条件查询:根据条件过滤原始表的数据,查询到想要的数据 \n语法: select 要查询的字段|表达式|常量值|函数 from 表 where 条件 ;\n```\n\n### 3:排序查询\n\n\n```sql\n语法:\nselect\n 要查询的东西\nfrom\n 表\nwhere \n 条件\n\norder by 排序的字段|表达式|函数|别名 【asc(升序)|desc(降序)】\n```\n\n### 4:常见函数\n\n##### 一、单行函数\n\n **1、字符函数** concat 拼接 substr 截取子串(索引从1开始) upper转换成大写 lower转换成小写 trim 去前后指定的空格和字符 ltrim 去左边空格 rtrim去右边空格 replace 替换 lpad 左填充 rpad 右填充 instr 返回子串第一次出现的索引 length 获取字节个数\n\n **2、数学函数** round 四舍五入 rand 随机数 floor 向下取整 ceil 向上取整 mod 取余 truncate 截断\n\n **3、日期函数 ** now 当前系统日期+时间 curdate 当前系统日期 curtime当前系统时间 str_to_date 将字符转换成日期 date_format 将日期转换成字符\n\n **4、流程控制函数** if 处理双分支 case语句 处理多分支 情况1:处理等值判断 情况2:处理条件判断 5、其他函数 version版本 database当前库 user当前连接用户\n\n##### 二、分组函数\n\n sum 求和 max 最大值 min 最小值 avg 平均值 count 计数 特点: 1、以上五个分组函数都忽略null值,除了count(*) 2、sum和avg一般用于处理数值型 max、min、count可以处理任何数据类型 3、都可以搭配distinct使用,用于统计去重后的结果 4、count的参数可以支持: 字段、常量值,一般放1\n\n建议使用 count(*)\n\n\n### 5:分组查询\n\n \n\n```sql\n语法: select 查询的字段,分组函数 from 表 group by 分组的字段 \n```\n\n> 特点: 1、可以按单个字段分组 \n> 2、和分组函数一同查询的字段最好是分组后的字段 \n> 3、分组筛选 \n> 针对的表 位置 关键字\n> 分组前筛选: 原始表 group by的前面 where 分组后筛选: \n> 分组后的结果集 group by的后面 having\n> \n> 4、可以按多个字段分组,字段之间用逗号隔开 \n> 5、可以支持排序 \n> 6、having后可以支持别名\n\n### 6:多表连接查询\n**JOIN 对比**\n![在这里插入图片描述](https://img-blog.csdnimg.cn/81ca976dbb334fd99585b39477895019.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n**七种Join:**\n![在这里插入图片描述](https://img-blog.csdnimg.cn/9dcc3d8d7eb44ce482272c4b56c7ed43.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n```sql\n\n**语法:**\n\nselect 字段,...\nfrom 表1\n【inner|left outer|right outer|cross】join 表2 on 连接条件\n【inner|left outer|right outer|cross】join 表3 on 连接条件\n【where 筛选条件】\n【group by 分组字段】\n【having 分组后的筛选条件】\n【order by 排序的字段或表达式】\n\n好处:语句上,连接条件和筛选条件实现了分离,简洁明了!\n \n \n```\n\n\n```sql\n/*\n连接查询\n 如需要多张数据表的数据进行查询,则可通过连接运算符实现多个查询\n内连接 inner join\n 查询两个表中的结果集中的交集\n外连接 outer join\n 左外连接 left join\n (以左表作为基准,右边表来一一匹配,匹配不上的,返回左表的记录,右表以NULL填充)\n 右外连接 right join\n (以右表作为基准,左边表来一一匹配,匹配不上的,返回右表的记录,左表以NULL填充)\n \n等值连接和非等值连接\n\n自连接\n*/\n\n-- 查询参加了考试的同学信息(学号,学生姓名,科目编号,分数)\nSELECT * FROM student;\nSELECT * FROM result;\n\n/*思路:\n(1):分析需求,确定查询的列来源于两个类,student result,连接查询\n(2):确定使用哪种连接查询?(内连接)\n*/\nSELECT s.studentno,studentname,subjectno,StudentResult\nFROM student s\nINNER JOIN result r\nON r.studentno = s.studentno\n\n-- 右连接(也可实现)\nSELECT s.studentno,studentname,subjectno,StudentResult\nFROM student s\nRIGHT JOIN result r\nON r.studentno = s.studentno\n\n-- 等值连接\nSELECT s.studentno,studentname,subjectno,StudentResult\nFROM student s , result r\nWHERE r.studentno = s.studentno\n\n-- 左连接 (查询了所有同学,不考试的也会查出来)\nSELECT s.studentno,studentname,subjectno,StudentResult\nFROM student s\nLEFT JOIN result r\nON r.studentno = s.studentno\n```\n\n**自连接**\n\n案例:查询员工名和直接上级的名称\n\n\n```sql\nSELECT e.last_name,m.last_name\nFROM employees e\nJOIN employees m ON e.`manager_id`=m.`employee_id`;\n```\n\n\n```sql\nSELECT e.last_name,m.last_name\nFROM employees e,employees m \nWHERE e.`manager_id`=m.`employee_id`;\n```\n### 7、子查询\n**含义:**\n\n一条查询语句中又嵌套了另一条完整的select语句,其中被嵌套的select语句,称为子查询或内查询\n在外面的查询语句,称为主查询或外查询\n\n```sql\n/*============== 子查询 ================\n什么是子查询?\n 在查询语句中的WHERE条件子句中,又嵌套了另一个查询语句\n 嵌套查询可由多个子查询组成,求解的方式是由里及外;\n 子查询返回的结果一般都是集合,故而建议使用IN关键字;\n*/\n\n-- 查询 数据库结构-1 的所有考试结果(学号,科目编号,成绩),并且成绩降序排列\n-- 方法一:使用连接查询\nSELECT studentno,r.subjectno,StudentResult\nFROM result r\nINNER JOIN `subject` sub\nON r.`SubjectNo`=sub.`SubjectNo`\nWHERE subjectname = \'数据库结构-1\'\nORDER BY studentresult DESC;\n\n-- 方法二:使用子查询(执行顺序:由里及外)\nSELECT studentno,subjectno,StudentResult\nFROM result\nWHERE subjectno=(\n SELECT subjectno FROM `subject`\n WHERE subjectname = \'数据库结构-1\'\n)\nORDER BY studentresult DESC;\n\n-- 查询课程为 高等数学-2 且分数不小于80分的学生的学号和姓名\n-- 方法一:使用连接查询\nSELECT s.studentno,studentname\nFROM student s\nINNER JOIN result r\nON s.`StudentNo` = r.`StudentNo`\nINNER JOIN `subject` sub\nON sub.`SubjectNo` = r.`SubjectNo`\nWHERE subjectname = \'高等数学-2\' AND StudentResult>=80\n\n-- 方法二:使用连接查询+子查询\n-- 分数不小于80分的学生的学号和姓名\nSELECT r.studentno,studentname FROM student s\nINNER JOIN result r ON s.`StudentNo`=r.`StudentNo`\nWHERE StudentResult>=80\n\n-- 在上面SQL基础上,添加需求:课程为 高等数学-2\nSELECT r.studentno,studentname FROM student s\nINNER JOIN result r ON s.`StudentNo`=r.`StudentNo`\nWHERE StudentResult>=80 AND subjectno=(\n SELECT subjectno FROM `subject`\n WHERE subjectname = \'高等数学-2\'\n)\n\n-- 方法三:使用子查询\n-- 分步写简单sql语句,然后将其嵌套起来\nSELECT studentno,studentname FROM student WHERE studentno IN(\n SELECT studentno FROM result WHERE StudentResult>=80 AND subjectno=(\n SELECT subjectno FROM `subject` WHERE subjectname = \'高等数学-2\'\n )\n)\n```\n### 8、分页查询\n语法:\n\n```sql\nselect 字段|表达式,...\nfrom 表\n【where 条件】\n【group by 分组字段】\n【having 条件】\n【order by 排序的字段】\nlimit 【起始的条目索引,】条目数;\n```\n**公式:select * from 表 limit (page-1)*sizePerPage,sizePerPage**\n\n假如:\n\n**每页显示条目数sizePerPage**\n\n**要显示的页数 page**\n\n### 9、联合查询\n\n引入: union 联合、合并\n\n语法:\n\n```sql\nselect 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】\nselect 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】\nselect 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】\n.....\nselect 字段|常量|表达式|函数 【from 表】 【where 条件】\n```\n\n## DML语言(数据操作语言)\n\n### 插入\n\n语法:\n\n> 方式一: insert into 表名(字段名,...) values(值1,...);\n>\n> \n> 方式二:insert into 表名 set 字段1=值1,字段2=值2;\n\n**方式一支持多行查询和子查询,方法二不支持**\n\n**特点**:\n\n> 1、字段类型和值类型一致或兼容,而且一一对应 \n> 2、可以为空的字段,可以不用插入值,或用null填充 \n> 3、不可以为空的字段,必须插入值\n> 4、字段个数和值的个数必须一致 5、字段可以省略,但默认所有字段,并且顺序和表中的存储顺序一致 \n\n### 修改\n\n**修改单表语法:**\n\n```sql\nupdate 表名 set 字段=新值,字段=新值\n【where 条件】\n```\n\n**修改多表语法:**\n\n```sql\n语法\nupdate 表1 别名1,表2 别名2\nset 字段=新值,字段=新值\nwhere 连接条件\nand 筛选条件\n\n语法\nupdate 表1 别名1\n连接类型(inner\\left\\right\\full) join 表2 别名2\non 连接条件\nset 字段=新值,字段=新值\nwhere 筛选条件\n```\n\n### 删除\n\n> 方式1 :delete语句\n> \n> 单表的删除: ★\n\n \n\n```sql\ndelete from 表名 【where 筛选条件】\n```\n\n**多表的删除:**\n\n\n\n```sql\n语法\ndelete 别名1,别名2\nfrom 表1 别名1,表2 别名2\nwhere 连接条件\nand 筛选条件;\n\n语法\n\ndelete 别名1,别名2\nfrom 表1 别名1\n连接类型 join 表2 别名2\non 连接条件\nwhere 筛选条件;\n```\n\n**方式2 :truncate语句**\n\n```sql\ntruncate table 表名\n```\n\n**两种方式的区别【面试题】**\n\n```sql\n#1.truncate不能加where条件,而delete可以加where条件\n\n#2.truncate的效率高一丢丢\n\n#3.truncate 删除带自增长的列的表后,如果再插入数据,数据从1开始\n#delete 删除带自增长列的表后,如果再插入数据,数据从上一次的断点处开始\n#4.truncate删除没有返回值,delete删除有返回值\n#5.truncate删除不能回滚,delete删除可以回滚\n```\n\n\n\n\n\n\n\n\n\n\n\n\n## DDL语句(数据定义语言)\n### 1.库和表的管理\n\n#### 库的管理:\n\n```sql\n一、创建库\ncreate database (if not exists)库名\n二、删除库\ndrop database (if exists) 库名\n三、修改库\nrename database 旧库名 to 新库名(已经废弃了,因为不够安全)\n可以更改库的字符集\nalter database 库名 character set utf8\\gbk等\n```\n#### 表的管理:\n\n1.创建表\n\n\n\n> CREATE TABLE IF NOT EXISTS 表名(\n> \n> 列名 列的类型 【(长度)约束】,\n> \n> 列名 列的类型 【(长度)约束】,\n> \n> ......\n> \n> )\n\n```sql\nCREATE TABLE IF NOT EXISTS stuinfo(\n stuId INT,\n stuName VARCHAR(20),\n gender CHAR,\n bornDate DATETIME\n );\n DESC studentinfo;//查询表结构\n```\n\n### 2.修改表 alter\n\n \n\n```sql\n语法:ALTER TABLE 表名 ADD|MODIFY|DROP|CHANGE COLUMN 字段名 【字段类型】; \n\n#①修改字段名\nALTER TABLE studentinfo CHANGE COLUMN sex gender CHAR;\n\n#②修改表名\nALTER TABLE stuinfo RENAME [TO] studentinfo;\n\n#③修改字段类型和列级约束\nALTER TABLE studentinfo MODIFY COLUMN borndate DATE ;\n\n#④添加字段\nALTER TABLE studentinfo ADD COLUMN email VARCHAR(20) first;\n\n#⑤删除字段\nALTER TABLE studentinfo DROP COLUMN email;\n```\n\n### 3.删除表\n\n \n\n```sql\nDROP TABLE [IF EXISTS] studentinfo;\n```\n\n### 4.复制表\n\n - 仅仅复制表的结构-------------create table 表名 like 要复制的表名\n - 复制表的结构+数据\n\ncreate table 表名\n\nselect * from 要复制的表名\n\n - 只复制部分数据\n\ncreate table 表名\n\nselect 数据名 from 要复制的表名 where 筛选条件\n\n - 只复制部分结构\n\ncreate table 表名\n\nselect 数据名 from 要复制的表名 where 不成立的条件\n\n\n### 常见类型\n\n```sql\n数值型:\n\n(1)整型: \ntinyint smallint mediumint int/integer bigint\n1 2 3 4 8\n设置无符号:undesign\n如果插入数值超过范围,则报错 out of range,会插入临界值\n长度代表显示结果的最大宽度,不够的话搭配zerofill用零填充\n\n(2)小数:\n 浮点型 float double\n 定点型 DEC(M,D) DECIMAL(M,D)\n M 整数和小数共同位数 D小数点后位数\n M,D都可以省略\n DEC(M,D) M默认为10,D默认为0\n原则:使用的类型越简单越好,能保存的数值越小越好\n\n(3)字符型:\n 较短的文本 char(M) 固定长度 \n varchar(M) 可变长度 M最大的字符数 \n char(M)空间耗费多,效率高,M可省略,默认为1 \n varchar(M)空间耗费少,效率低,M不可省略\n \n(4)其他:\n Bit 位类型 Bit(1)~bit(8)\n binary和varbinary类型\n 说明:类似于char和varchar,不同的是它们包含二进制字符串而不包含非二进制字符串\n enum枚举 set不区分大小写\n 区别:SET类型一次可以选取多个成员,而Enum只能选一个。据成员个数不同,存储所占的字节也不同\n 较长的文本 text blob(较大的二进制)\n 日期型:date datetime timestamp time year\n 4 8 4 3 1\n```\n### 常见约束\n\n```sql\n– NOT NULL 非空约束,规定某个字段不能为空\n– UNIQUE 唯一约束,规定某个字段在整个表中是唯一的,可以为空\n– PRIMARY KEY 主键(非空且唯一)查、外键 \n查看索引:show index from 表名\n主键和唯一的对比:\n 保证唯一性 是否允许为空 一个表中可以有多少个 是否可以组合 \n主键 √ × 至多有一个 可以,但不推荐\n独特 √ √ 可以有多个 可以,但不推荐\n\n外键:\n 1.要求在从表设置外键关系\n 2.从表的外键列的类型和主表的关联列的类型要求一致或者兼容,名称无要求\n 3.主表中的关联列必须是一个key(一般是主键或独特)\n 4.插入数据时,先插入主表,在插入从表\n 删除数据时,先删除从表,再删除主表。\n加入多个约束时直接添加,没有顺序。\n一、创建表时添加约束\n\n1.添加列级约束\n\n直接在字段名和类型后面追加 约束类型即可。\n只支持:默认、非空、主键、唯一\n\n2.添加表级约束\n\n语法:在各个字段的最下面\n 【constraint 约束名】 约束类型(字段名) \nCREATE TABLE IF NOT EXISTS stuinfo(\n id INT PRIMARY KEY,\n stuname VARCHAR(20),\n sex CHAR(1),\n age INT DEFAULT 18,\n seat INT UNIQUE,\n majorid INT,\n CONSTRAINT fk_stuinfo_major FOREIGN KEY(majorid) REFERENCES major(id)外键\n\n);\n二、修改表时添加约束\n1、添加列级约束\nalter table 表名 modify column 字段名 字段类型 新约束;\n\n2、添加表级约束\nalter table 表名 add 【constraint 约束名】 约束类型(字段名) 【外键的引用】;\n 位置 支持的约束类型 是否可以起约束名\n列级约束:列的后面 语法都支持,但外键没有效果 不可以\n表级约束:所有列的下面 默认和非空不支持,其他支持 可以(主键没有效果)\n```\n### 标识列\n\n**含义**\n\n标识列,又称为自增长列。可以不用手动的插入值,系统提供默认的序列值\n\n**特点**\n\n> 1、标识列必须和主键搭配吗?不一定,但要求是一个key \n> 2、一个表可以有几个标识列?至多一个! \n> 3、标识列的类型只能是数值型 \n> 4、标识列可以通过 SET auto_increment_increment=3;设置步长 可以通过 手动插入值,设置起始值\n\n**创建表时设置标识列**\n\n```sql\nDROP TABLE IF EXISTS tab_identity;\nCREATE TABLE tab_identity(\n id INT ,\n NAME FLOAT UNIQUE AUTO_INCREMENT,\n seat INT \n);\nTRUNCATE TABLE tab_identity;\n\n\nINSERT INTO tab_identity(id,NAME) VALUES(NULL,\'john\');\nINSERT INTO tab_identity(NAME) VALUES(\'lucy\');\nSELECT * FROM tab_identity;\n\n\nSHOW VARIABLES LIKE \'%auto_increment%\';\n\n\nSET auto_increment_increment=3;\n```\n\n# 事务\n## 什么是事务\n\n - 事务就是将一组SQL语句放在同一批次内去执行\n - 如果一个SQL语句出错,则该批次内的所有SQL都将被取消执行\n - MySQL事务处理只支持InnoDB和BDB数据表类型\n\n## 事务的ACID原则 \n\n### 原子性(Atomic)\n\n> 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(ROLLBACK)到事务开始前的状态,就像这个事务从来没有执行过一样。\n\n### 一致性(Consist)\n\n> 一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。也就是说:如果事务是并发多个,系统也必须如同串行事务一样操作。其主要特征是保护性和不变性(Preserving anInvariant),以转账案例为例,假设有五个账户,每个账户余额是100元,那么五个账户总额是500元,如果在这个5个账户之间同时发生多个转账,无论并发多少个,比如在A与B账户之间转账5元,在C与D账户之间转账10元,在B与E之间转账15元,五个账户总额也应该还是500元,这就是保护性和不变性。\n\n### 隔离性(Isolated)\n\n> 隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。\n\n### 持久性(Durable)\n\n> 在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。\n\n### 基本语法\n\n```sql\n-- 使用set语句来改变自动提交模式\nSET autocommit = 0; /*关闭*/\nSET autocommit = 1; /*开启*/\n\n-- 注意:\n--- 1.MySQL中默认是自动提交\n--- 2.使用事务时应先关闭自动提交\n\n-- 开始一个事务,标记事务的起始点\nSTART TRANSACTION \n\n-- 提交一个事务给数据库\nCOMMIT\n\n-- 将事务回滚,数据回到本次事务的初始状态\nROLLBACK\n\n-- 还原MySQL数据库的自动提交\nSET autocommit =1;\n\n-- 保存点\nSAVEPOINT 保存点名称 -- 设置一个事务保存点\nROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点\nRELEASE SAVEPOINT 保存点名称 -- 删除保存点\n```\n\n\n# ER图\n## 概念\n**E-R图也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。**\n\n> 它是描述现实世界关系概念模型的有效方法。是表示概念关系模型的一种方式。用“矩形框”表示实体型,矩形框内写明实体名称;用“椭圆图框”或圆角矩形表示实体的属性,并用“实心线段”将其与相应关系的“实体型”连接起来;\n> 用”菱形框“表示实体型之间的联系成因,在菱形框内写明联系名,并用”实心线段“分别与有关实体型连接起来,同时在”实心线段“旁标上联系的类型(1:1,1:n或m:n)。\n\n\n**ER图分为实体、属性、关系三个核心部分。实体是长方形体现,而属性则是椭圆形,关系为菱形。**\n## 实体\n\n> ER图的实体(entity)即数据模型中的数据对象,例如人、学生、音乐都可以作为一个数据对象,用长方体来表示,每个实体都有自己的实体成员(entitymember)或者说实体对象(entity instance),例如学生实体里包括张三、李四等,实体成员(entitymember)/实体实例(entity instance) 不需要出现在ER图中。\n> \n> \n## 属性\n> ER图的属性(attribute)即数据对象所具有的属性,例如学生具有姓名、学号、年级等属性,用椭圆形表示,属性分为唯一属性( uniqueattribute)和非唯一属性,唯一属性指的是唯一可用来标识该实体实例或者成员的属性,用下划线表示,一般来讲实体都至少有一个唯一属性。\n> \n> \n> \n\n## 关系\n> ER图的关系(relationship)用来表现数据对象与数据对象之间的联系,例如学生的实体和成绩表的实体之间有一定的联系,每个学生都有自己的成绩表,这就是一种关系,关系用菱形来表示。\n\n## 关联关系\n**ER图中关联关系有三种:**\n\n> 1对1(1:1):\n> 1对1关系是指对于实体集A与实体集B,A中的每一个实体至多与B中一个实体有关系;反之,在实体集B中的每个实体至多与实体集A中一个实体有关系。\n\n> 1对多(1:N):1对多关系是指实体集A与实体集B中至少有N(N>0)个实体有关系;并且实体集B中每一个实体至多与实体集A中一个实体有关系。\n> \n\n> 多对多(M:N):\n> 多对多关系是指实体集A中的每一个实体与实体集B中至少有M(M>0)个实体有关系,并且实体集B中的每一个实体与实体集A中的至少N(N>0)个实体有关系。\n\n## 实例\n**ER图实例**\n假设教学管理规定:\n一个学生可选修多门课,一门课有若干学生选修;\n一个教师可讲授多门课,一门课只有一个教师讲授;\n一个学生选修一门课,仅有一个成绩。\n学生的属性有学号、学生姓名;教师的属性有教师编号,教师姓名;课程的属性有课程号、课程名。\n\n要求:根据上述语义画出ER 图,要求在图中画出实体的属性并注明联系的类型。\n![在这里插入图片描述](https://img-blog.csdnimg.cn/6a8c28cad7a2404fb191a2f158122c97.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/1285294193e142b1bf9936df69d8f7e3.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n**图书管理系统ER图**\n\n\n\n# 表与表之间的关系\n## 一、表关系的概念\n 现实生活中,实体与实体之间肯定是有关系的,如:学生和老师,学生和课程,部门和员工,每个人和自己的身份证号码等。\n\n 在设计表的时候,就应该体现出来表与表之间的这种关系。\n\n **表与表之间的三种关系:**\n\n - 一对多:最常用的关系,如部门和员工\n\n \n\n - 多对多:学生选课表和学生表,一门课程可以有多个学生选择,一个学生选择多门课程\n\n \n\n - 一对一:相对使用比较少,员工表,公民表,护照表\n\n## 二、一对多\n **一对多(1:n)**\n\n 例如:部门和员工,客户和订单,分类和商品。\n\n **一对多建表原则**:在从表(多方)创建一个字段,字段作为**外键**指向主表(一方)的主键\n\n## 三、多对多\n **多对多(m:n)**\n\n 例如:老师和学生,学生和课程,用户和角色\n\n **多对多关系建表原则:**需要创建第三张表,中间表至少两个字段,这两个字段分别作为外键指向各自一方的主键。\n\n 多对多关系示意图:\n![在这里插入图片描述](https://img-blog.csdnimg.cn/122028413c3c4262adede893988b9a73.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n## 四、一对一\n 一对一(1:1)\n\n 在实际开发应用不多,因为一对一可以创建成一张表。\n\n **两种建表原则:**\n ![在这里插入图片描述](https://img-blog.csdnimg.cn/267ad3ff103249739bf440b824e737eb.png)\n**关系示意图:**\n![在这里插入图片描述](https://img-blog.csdnimg.cn/431de2250219427889c60090bd0e2812.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n![在这里插入图片描述](https://img-blog.csdnimg.cn/c824d7c260fc4235902e111302fc98f4.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n## 五、表与表之间的关系总结\n![在这里插入图片描述](https://img-blog.csdnimg.cn/a7a88052f5ce46ce8bcf49ae80f42a2e.png)\n\n\n\n# 数据库设计三大范式\n\n## 1.第一范式(1NF):确保每列保持原子性即列不可分\n\n比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成为一个数据库表的字段就行,但是如果系统经常访问“地址”属性中的“城市”部分,那么一定要把“地址”这个属性重新拆分为省份、城市、详细地址等多个部分来进行存储,这样对地址中某一个部分操作的时候将非常方便 。\n\n\n\n## 2.第二范式(2NF):属性完全依赖于主键(属性都是该对象拥有的)\n\n在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。\n\n比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下图:\n![在这里插入图片描述](https://img-blog.csdnimg.cn/afa7f10eaec44b39a2367939567e5b98.png)\n\n\n\n这里产生一个问题:这个表中是以订单编号和商品编号作为联合主键,这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品的编号相关,所以在这里违反了第二范式的设计原则。\n\n**利用对象之间的关系设计表,确认表的个数:**而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了,如下图。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/b21f965713b449fda35b2f2aaf4b2c29.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n\n这里这样设计,在很大程度上减小了数据库的冗余,如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。\n\n## 3.第三范式(3NF):属性和主键不能间接相关(减少数据冗余,这样就可以通过主外键进行表之间连接)\n\n第三范式:数据不能存在传递关系,**即每个属性都跟主键有直接关系而不是间接关系。**像:a-->b-->c 属性之间含有这样的关系,是不符合第三范式的。第三范式是对第二范式的细化。\n\n比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系,而不可以在订单表中添加关于客户其他信息(比如姓名、所属公司)的字段,如下面这两个表所示的设计就是一个满足第三范式的数据库表。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2f96e3736dac428a993ef795e46b635c.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n\n这样在查询订单信息的时候,就可以使用客户编号来引用客户信息表中的记录,也不必再订单信息表中多次输入客户信息的内容,减小了数据冗余。\n\n例子:儿子的玩具车与玩具枪和爸爸是间接关系,应该拆为 两个表,通过儿子将两个表关联起来:\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/3e2f737948564fb19b3c1758badfbf7f.png)\n\n\n因为上表的玩具车与玩具枪属于儿子,因此不符合第三范式,对上表进行拆分\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/83f1220c96804d89818b698835a9bb0c.png)\n\n\n\n# MVC\n\n**经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。**\n\n\n> MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图、饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。\n> 模型-视图-控制器(MVC)是Xerox\n> PARC在二十世纪八十年代为编程语言Smalltalk-80发明的一种软件设计模式,已被广泛使用。后来被推荐为Oracle旗下Sun公司Java\n> EE平台的设计模式,并且受到越来越多的使用ColdFusion和PHP的开发者的欢迎。模型-视图-控制器模式是一个有用的工具箱,它有很多好处,但也有一些缺点\n\n\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/465556e65d0848d183e3dd9cbb806ed1.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n## 一、MVC 开发模式的优点\n\n> 提高 开发的代码复用性,避免了重复性开发 \n> 有效帮助开发人员实现业务开发中【事务管理】\n\n## 二、MVC开发角色组成\n\n**C: controller--控制层**\n\n**M: model--业务模型层,完成业务处理**\n\n**service层:调用dao层来完成业务实现,负责管理所调用的dao层的【业务管理】**\n\n**dao层:直接操作数据库**\n\n**V: view--视图层:将处理结果写入到响应包 JSP**\n\n\n\n## 三、Dao层作用:\n\n在实际的业务处理过程中,往往需要进行多次的数据库访问。这些访问性质往往是相同的,采用Dao层可以将对数据库访问进行封装,避免及进行重复性的数据库访问开发操作,同时降低维护的成本。\n\n## 四、Dao层实现\n\n**Dao层角色:\nDao接口层:声明Dao接口\nDao实现层:声明Dao接口实现类\nDao层命名规则\nDao接口层:com.bjpowernode.dao [接口]:表Dao\nDao实现层 com.bjowernode.daoImpl [实现类] :表DaoImpl**\n\n## 五、独立使用Dao层处理业务存在问题\n\n无法实现【业务的复用】\n\n无法将参与同一业务的sql命令放入同一事务管理。\n\n```java\ncon.setAutoCommit(false)\n\nPreparedStatement ps1=con . preparedStatement ( sql1 )\n\nPreparedStatement ps2=con .preparedStatement( sql2 )\n\nps1 .execute();\n\nps2 .execute();\n\nsql_1与sql_2是储存在同一事务中\n```\n\n## 六、service层作用\n\n**封装的是一个具体业务的实现方案,来提高业务实现的复用性。\n负责将参与本次业务实现的Dao层中事务进行管理。**\n\n## 七、service层实现\n\n**角色**\n\nservice接口层\nservice实现层\n\n**命名**\nservice接口层:com.biowernode.service [接口] 如果本次业务之和一张表关联 --表名Service ;\n如果本次业务与多张表关联 --业务名称Service\nservice实现层:com.biowernode.serviceImpl [接口] 如果本次业务之和一张表关联 ---表名ServiceImpl ;\n如果本次业务与多张表关联 ---业务名称ServiceImpl\n\n## 八、service层的简化开发:\n\nservice层需要负责本次业务中的事务管理\n\n```java\ntry{\n\n con.setAutoCommit(false)\n\n dao...\n\n dao...\n\n con.commit\n\n}catch(Exception ex){\n\n con.rollback()\n\n}finally{\n\n con.close()\n\n}\n```\n\n## 九、三层架构\n**三层架构就是为了符合“高内聚,低耦合”思想,把各个功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构,各层之间采用接口相互访问,并通过对象模型的实体类(Model)作为数据传递的载体,不同的对象模型的实体类一般对应于数据库的不同表,实体类的属性与数据库表的字段名一致。** \n\n**三层架构区分层次的目的是为了 “高内聚,低耦合”。开发人员分工更明确,将精力更专注于应用系统核心业务逻辑的分析、设计和开发,加快项目的进度,提高了开发效率,有利于项目的更新和维护工作。** \n\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/ae81a2a35aff4a92be8ca19c37dd04c7.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n### 例子\n在后厨中,小小的餐厅厨房,却是厨师们的一番战场。都说艺术来源于生活,同样,技术也是如此。其实,三层架构就可以用后厨这个场景来抽象理解。\n![在这里插入图片描述](https://img-blog.csdnimg.cn/861da18869d54597acbb1d585f07777e.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n - 服务员:负责接待客人和传菜\n - 主厨:后厨的头头,后处里主厨说了算。可以理解为架构师,负责后厨和服务员的对接\n - 小厨:每个小厨有自己特定的工作,各司其职\n \n### 三层架构每层之间的逻辑关系:\n![在这里插入图片描述](https://img-blog.csdnimg.cn/a7b3cd67551243e3b5db700bdcf95de6.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n\n\n### 三层优缺点\n**【优点】**\n\n 1. 开发人员可以只关注整个结构中的其中某一层;\n 2. 可维护性高,可扩展性高\n 3. 可以降低层与层之间的依赖;\n 4. 有利于标准化;\n 5. 利于各层逻辑的复用\n\n**【缺点】**\n\n 1. 降低了系统的性能。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成\n 2. 有时会导致级联的修改,这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码\n 3. 增加了开发成本\n\n### 为什么使用三层\n使用三层架构的目的:解耦!!!\n\n同样拿上面饭店的例子来讲:\n\n**服务员(UI层)请假——另找服务员;**\n\n**主厨(BLL层)辞职——招聘另一个主厨;**\n\n**小厨(DAL)辞职——招聘另一个小厨;**\n\n【顾客反映】\n\n**你们店服务态度不好——服务员的问题。开除服务员;**\n\n**你们店菜里有虫子——主厨的问题。换厨师;**\n\n**任何一层发生变化都不会影响到另外一层!!!**\n\n\n### MVC与三层架构的区别\n![在这里插入图片描述](https://img-blog.csdnimg.cn/a5688826136b41ad891305b7c6eeb18b.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n**无论是MVC还是三层架构,都是一种规范,都是奔着\"高耦合,低内聚\"的思想来设计的。三层中的UI和Servlet来分别对应MVC中的View和Controller,业务逻辑层是来组合数据访问层的原子性功能的。**\n\n\n**在三层中,业务逻辑层和数据访问层要遵循面型接口编程的。这种接口定义和具体实现逻辑的分开,非常有利于后续扩展和维护!**\n\n[为什么我们要面向接口编程?!](https://blog.csdn.net/wangshuaiwsws95/article/details/104689831)\n\n\n# 更多相关文章点这里哦\n[【Java全栈】Java全套学习路线及项目资料总结【JavaSE+Web基础+MySQL+JavaEE】](https://blog.csdn.net/qq_45696377/article/details/110575362)\n\n[【Mysql学习】基于Swing+jdbc+mysql的Java图书管理系统【保姆级教程】](https://blog.csdn.net/qq_45696377/article/details/119282412)\n\n\n\n\n\n\n\n\n','2021-09-01 16:02:27','2021-09-01 16:02:27',0),(31,1,'【Mysql问题】解决Jdbc插入中文到数据库中出现 问号?乱码','之所以会出现乱码,就是编码方式不一致导致的,我们应该首先确定\n - IDEA和mysql数据库中的编码方式是否一致\n - 在链接数据时,有没有在url里面加上 `characterEncoding=utf8` ,也就是下图这样','# 之所以会出现乱码,就是编码方式不一致导致的\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2b623bd7e34e4008a6b2b964643cbcd9.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n## 我们应该首先确定\n\n - IDEA和mysql数据库中的编码方式是否一致\n - 在链接数据时,有没有在url里面加上 `characterEncoding=utf8` ,也就是下图这样\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/47ccd8a33fe04ef7bbd568d18fc200a5.png)\n在dbUrl配置后面加上\n![在这里插入图片描述](https://img-blog.csdnimg.cn/055dfda93b2648eab4e0fe56a6c69e03.png)\n 然后基本就没有问题了\n ![在这里插入图片描述](https://img-blog.csdnimg.cn/8b166781f07a4f94bef98c7a55472590.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n# 更多相关文章点这里哦\n[【mysql安装问题】mysqld: Can‘t create directory ‘E: oft\\mysql\\mysql-5.7.19-winx64\\data\\‘ 两种解决办法](https://blog.csdn.net/qq_45696377/article/details/118890805)\n\n[【Java全栈】Java全套学习路线及项目资料总结【JavaSE+Web基础+MySQL+JavaEE】](https://blog.csdn.net/qq_45696377/article/details/110575362)','2021-09-01 16:04:09','2021-09-01 16:04:09',0),(32,1,'【mysql安装问题】mysqld: Can‘t create directory ‘E: oft\\mysql\\mysql-5.7.19-winx64\\data\\‘ 两种解决办法','安装完mysql之后,要初始化数据文件,输入\n\nmysqld --initialize-insecure --user=mysql然后可能会出现如下错误**:\n\nmysqld: Can’t create directory ‘E: oft\\mysql\\mysql-5.7.19-winx64\\data’ (Errcode: 2 - No such file or directory)','安装完mysql之后,要初始化数据文件,输入 \n\n```sql\nmysqld --initialize-insecure --user=mysql\n```\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719104443684.png)\n**然后可能会出现如下错误****:\n\n**mysqld: Can\'t create directory \'E: oft\\mysql\\mysql-5.7.19-winx64\\data\\\' (Errcode: 2 - No such file or directory)**\n\n\n**原因一**:my.ini文件中的basedir(设置mysql的安装目录)、datadir(设置mysql数据库的数据的存放目录)与MySQL解压后的路径不一致\n\n\n**解决办法:**\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719104824202.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n检查mysql安装目录 my.ini 目录\n\n将basedir=E:\\Software\\mysql-5.7.24-winx64 改为 basedir=D:\\Software\\mysql-5.7.24-winx64 (改为MySQL解压后的路径)\n\n将datadir=E:\\Software\\mysql-5.7.24-winx64\\data 改为 datadir=D:\\Software\\mysql-5.7.24-winx64\\data (改为MySQL解压后的路径\\data)\n\n\n**原因二:**\n看到很多人说:my.ini文件中的basedir(设置mysql的安装目录)、datadir(设置mysql数据库的数据的存放目录)与MySQL解压后的路径不一致\n\n但是我检查了几遍都没问题,执行命令\n\n```sql\nmysqld --initialize-insecure --user=mysql \n```\n\n一直报这个错\n\n**解决办法:**\n\n把basedir和datadir中的 \\ 改成 \\\\就可以正常执行了,我的系统是win10 20H2\n\n**很奇怪,但亲测有效**\n\n将\n\n```sql\nbasedir=E:\\soft\\mysql\\mysql-5.7.19-winx64\\ \n```\n\n改为\n \n\n```sql\nbasedir=E:\\\\soft\\\\mysql\\\\mysql-5.7.19-winx64\\\\ (改为MySQL解压后的路径)\n```\n\n将\n\n```sql\ndatadir=E:\\soft\\mysql\\mysql-5.7.19-winx64\\data\\ \n```\n\n改为 \n\n```sql\ndatadir=E:\\\\soft\\\\mysql\\\\mysql-5.7.19-winx64\\\\data\\\\ (改为MySQL解压后的路径\\data)\n```\n\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719110936576.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Njk2Mzc3,size_16,color_FFFFFF,t_70)\n\n成功后就会生成一个data存储数据文件夹\n\n\n\n# 更多相关文章点这里哦\n[【Java全栈】Java全套学习路线及项目资料总结【JavaSE+Web基础+MySQL+JavaEE】](https://blog.csdn.net/qq_45696377/article/details/110575362)','2021-09-01 16:06:09','2021-09-06 20:53:26',0),(33,1,'委屈恶气呃全额问问去额趣味无穷','去问我去饿我去饿去问我去饿请问','去问我去饿我去饿请问','2021-09-01 16:13:55','2021-09-01 16:17:47',1),(34,1,'全俄文全额委屈额为','去问我去饿我','去问去问我去饿','2021-09-01 16:18:07','2021-09-01 16:18:07',1),(35,1,'sd ','sdv ','sd ','2021-09-01 22:05:03','2021-09-01 22:05:03',1),(36,1,'【Vue+SpringBoot】超详细!一周开发一个SpringBoot + Vue+MybatisPlus+Shiro+JWT+Redis前后端分离个人博客项目!!!【项目完结】','从零开始搭建一个项目骨架,最好选择合适,熟悉的技术,并且在未来易拓展,适合微服务化体系等。所以一般以Springboot作为我们的框架基础,这是离不开的了。\n\n然后数据层,我们常用的是Mybatis,易上手,方便维护。但是单表操作比较困难,特别是添加字段或减少字段的时候,比较繁琐,所以这里我推荐使用Mybatis Plus(mp.baomidou.com/),为简化开发而生,只… CRUD 操作,从而节省大量时间。','@[TOC](项目目录)\n\n# 资源准备\n\n> 作者:\n> **后端:**@胡超 \n> **前端:**@胡超、@詹圆圆\n> \n> vueblog项目文档:\n> \n> vueblog项目视频:正在录制\n> \n> vueblog代码仓库:\n\n## 前后端分离项目\n\n文章总体分为2大部分,Java后端接口和vue前端页面,比较长,因为不想分开发布,真正想你4小时学会,哈哈。\n\n先看效果:\n\n项目演示:\n![在这里插入图片描述](https://img-blog.csdnimg.cn/987f0d36992c4d2391723c596c8a60d3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2cea15f0fe4f4d50851f9462c13f50af.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n\n\n不多说,开始敲代码。\n\n\n\n# 技术栈\n![在这里插入图片描述](https://img-blog.csdnimg.cn/bf1e7e0adac84d4fb1f0a2e7f39361ad.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n\n\n# Java后端接口开发\n\n### 1、前言\n\n从零开始搭建一个项目骨架,最好选择合适,熟悉的技术,并且在未来易拓展,适合微服务化体系等。所以一般以Springboot作为我们的框架基础,这是离不开的了。\n\n然后数据层,我们常用的是Mybatis,易上手,方便维护。但是单表操作比较困难,特别是添加字段或减少字段的时候,比较繁琐,所以这里我推荐使用Mybatis Plus([mp.baomidou.com/),为简化开发而生,只…](https://link.juejin.cn?target=https%3A%2F%2Fmp.baomidou.com%2F%EF%BC%89%EF%BC%8C%E4%B8%BA%E7%AE%80%E5%8C%96%E5%BC%80%E5%8F%91%E8%80%8C%E7%94%9F%EF%BC%8C%E5%8F%AA%E9%9C%80%E7%AE%80%E5%8D%95%E9%85%8D%E7%BD%AE%EF%BC%8C%E5%8D%B3%E5%8F%AF%E5%BF%AB%E9%80%9F%E8%BF%9B%E8%A1%8C) CRUD 操作,从而节省大量时间。\n\n作为一个项目骨架,权限也是我们不能忽略的,Shiro配置简单,使用也简单,所以使用Shiro作为我们的的权限。\n\n考虑到项目可能需要部署多台,这时候我们的会话等信息需要共享,Redis是现在主流的缓存中间件,也适合我们的项目。\n\n然后因为前后端分离,所以我们使用jwt作为我们用户身份凭证。\n\nok,我们现在就开始搭建我们的项目脚手架!\n\n技术栈:\n\n- SpringBoot\n- mybatis plus\n- shiro\n- lombok\n- redis\n- hibernate validatior\n- jwt\n\n\n\n### 2、新建Springboot项目\n\n这里,我们使用IDEA来开发我们项目,新建步骤比较简单,我们就不截图了。\n\n开发工具与环境:\n\n- idea\n- mysql\n- jdk 8\n- maven3.3.9\n\n新建好的项目结构如下,SpringBoot版本使用的目前最新的2.2.6.RELEASE版本\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/c1186ebad2cf4b739c0136f481bdbef9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n\npom的jar包导入如下:\n\n```\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-web</artifactId>\n</dependency>\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-devtools</artifactId>\n <scope>runtime</scope>\n <optional>true</optional>\n</dependency>\n<dependency>\n <groupId>org.projectlombok</groupId>\n <artifactId>lombok</artifactId>\n <optional>true</optional>\n</dependency>\n复制代码\n```\n\n- devtools:项目的热加载重启插件\n- lombok:简化代码的工具\n\n### 3、整合mybatis plus\n\n接下来,我们来整合mybatis plus,让项目能完成基本的增删改查操作。步骤很简单:可以去官网看看:[mp.baomidou.com/guide/insta…](https://link.juejin.cn?target=https%3A%2F%2Fmp.baomidou.com%2Fguide%2Finstall.html)\n\n**第一步:导入jar包**\n\npom中导入mybatis plus的jar包,因为后面会涉及到代码生成,所以我们还需要导入页面模板引擎,这里我们用的是freemarker。\n\n```\n<!--mp-->\n<dependency>\n <groupId>com.baomidou</groupId>\n <artifactId>mybatis-plus-boot-starter</artifactId>\n <version>3.2.0</version>\n</dependency>\n<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-freemarker</artifactId>\n</dependency>\n<dependency>\n <groupId>mysql</groupId>\n <artifactId>mysql-connector-java</artifactId>\n <scope>runtime</scope>\n</dependency>\n<!--mp代码生成器-->\n<dependency>\n <groupId>com.baomidou</groupId>\n <artifactId>mybatis-plus-generator</artifactId>\n <version>3.2.0</version>\n</dependency>\n复制代码\n```\n\n**第二步:然后去写配置文件**\n\n```\n# DataSource Config\nspring:\n datasource:\n driver-class-name: com.mysql.cj.jdbc.Driver\n url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai\n username: root\n password: admin\nmybatis-plus:\n mapper-locations: classpath*:/mapper/**Mapper.xml\n复制代码\n```\n\n上面除了配置数据库的信息,还配置了myabtis plus的mapper的xml文件的扫描路径,这一步不要忘记了。 **第三步:开启mapper接口扫描,添加分页插件**\n\n新建一个包:通过@mapperScan注解指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类。PaginationInterceptor是一个分页插件。\n\n- com.markerhub.config.MybatisPlusConfig\n\n```\n@Configuration\n@EnableTransactionManagement\n@MapperScan(\"com.markerhub.mapper\")\npublic class MybatisPlusConfig {\n @Bean\n public PaginationInterceptor paginationInterceptor() {\n PaginationInterceptor paginationInterceptor = new PaginationInterceptor();\n return paginationInterceptor;\n }\n}\n复制代码\n```\n\n**第四步:代码生成**\n\n如果你没再用其他插件,那么现在就已经可以使用mybatis plus了,官方给我们提供了一个代码生成器,然后我写上自己的参数之后,就可以直接根据数据库表信息生成entity、service、mapper等接口和实现类。\n\n- com.mao.CodeGenerator\n\n因为代码比较长,就不贴出来了,在代码仓库上看哈!\n\n首先我在数据库中新建了一个user表:\n\n```sql\nCREATE TABLE `m_user` (\n `id` bigint(20) NOT NULL AUTO_INCREMENT,\n `username` varchar(64) DEFAULT NULL,\n `avatar` varchar(255) DEFAULT NULL,\n `email` varchar(64) DEFAULT NULL,\n `password` varchar(64) DEFAULT NULL,\n `status` int(5) NOT NULL,\n `created` datetime DEFAULT NULL,\n `last_login` datetime DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `UK_USERNAME` (`username`) USING BTREE\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nCREATE TABLE `m_blog` (\n `id` bigint(20) NOT NULL AUTO_INCREMENT,\n `user_id` bigint(20) NOT NULL,\n `title` varchar(255) NOT NULL,\n `description` varchar(255) NOT NULL,\n `content` longtext,\n `created` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP,\n `status` tinyint(4) DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;\nINSERT INTO `vueblog`.`m_user` (`id`, `username`, `avatar`, `email`, `password`, `status`, `created`, `last_login`) VALUES (\'1\', \'markerhub\', \'https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg\', NULL, \'96e79218965eb72c92a549dd5a330112\', \'0\', \'2020-04-20 10:44:01\', NULL);\n复制代码\n```\n\n运行CodeGenerator的main方法,输入表名:m_user,生成结果如下: ![图片](https://img-blog.csdnimg.cn/img_convert/4ca1076ea700654792ea04ff2ff64423.webp?x-oss-process=image/format,png)\n\n得到:\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/bf0642e57afc4527a07dad435208c10f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_13,color_FFFFFF,t_70,g_se,x_16)\n\n\n简洁!方便!经过上面的步骤,基本上我们已经把mybatis plus框架集成到项目中了。\n\nps:额,注意一下m_blog表的代码也生成一下哈。\n\n在UserController中写个测试:\n\n```\n@RestController\n@RequestMapping(\"/user\")\npublic class UserController {\n @Autowired\n UserService userService;\n @GetMapping(\"/{id}\")\n public Object test(@PathVariable(\"id\") Long id) {\n return userService.getById(id);\n }\n}\n复制代码\n```\n\n访问:[http://localhost:8080/user/1](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080%2Fuser%2F1) 获得结果如下,整合成功! ![图片](https://img-blog.csdnimg.cn/img_convert/0b10e3930d6e6fa46c268b8f405163be.webp?x-oss-process=image/format,png)\n\n### 3、统一结果封装\n\n这里我们用到了一个Result的类,这个用于我们的异步统一返回的结果封装。一般来说,结果里面有几个要素必要的\n\n- 是否成功,可用code表示(如200表示成功,400表示异常)\n- 结果消息\n- 结果数据\n\n所以可得到封装如下:\n\n- com.mao.common.lang.Result\n\n```\n@Data\npublic class Result implements Serializable {\n private String code;\n private String msg;\n private Object data;\n public static Result succ(Object data) {\n Result m = new Result();\n m.setCode(\"0\");\n m.setData(data);\n m.setMsg(\"操作成功\");\n return m;\n }\n public static Result succ(String mess, Object data) {\n Result m = new Result();\n m.setCode(\"0\");\n m.setData(data);\n m.setMsg(mess);\n return m;\n }\n public static Result fail(String mess) {\n Result m = new Result();\n m.setCode(\"-1\");\n m.setData(null);\n m.setMsg(mess);\n return m;\n }\n public static Result fail(String mess, Object data) {\n Result m = new Result();\n m.setCode(\"-1\");\n m.setData(data);\n m.setMsg(mess);\n return m;\n }\n}\n复制代码\n```\n\n### 4、整合shiro+jwt,并会话共享\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/45ef34b6224e4221a4a146f8275efa12.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n\n考虑到后面可能需要做集群、负载均衡等,所以就需要会话共享,而shiro的缓存和会话信息,我们一般考虑使用redis来存储这些数据,所以,我们不仅仅需要整合shiro,同时也需要整合redis。在开源的项目中,我们找到了一个starter可以快速整合shiro-redis,配置简单,这里也推荐大家使用。\n\n而因为我们需要做的是前后端分离项目的骨架,所以一般我们会采用token或者jwt作为跨域身份验证解决方案。所以整合shiro的过程中,我们需要引入jwt的身份验证过程。\n\n那么我们就开始整合:\n\n我们使用一个shiro-redis-spring-boot-starter的jar包,具体教程可以看官方文档:[github.com/alexxiyang/…](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Falexxiyang%2Fshiro-redis%2Fblob%2Fmaster%2Fdocs%2FREADME.md%23spring-boot-starter)\n\n第一步:导入shiro-redis的starter包:还有jwt的工具包,以及为了简化开发,我引入了hutool工具包。\n\n```\n<dependency>\n <groupId>org.crazycake</groupId>\n <artifactId>shiro-redis-spring-boot-starter</artifactId>\n <version>3.2.1</version>\n</dependency>\n<!-- hutool工具类-->\n<dependency>\n <groupId>cn.hutool</groupId>\n <artifactId>hutool-all</artifactId>\n <version>5.3.3</version>\n</dependency>\n<!-- jwt -->\n<dependency>\n <groupId>io.jsonwebtoken</groupId>\n <artifactId>jjwt</artifactId>\n <version>0.9.1</version>\n</dependency>\n复制代码\n```\n\n第二步:编写配置:\n\n#### ShiroConfig\n\n- com.mao.config.ShiroConfig\n\n```\n/**\n * shiro启用注解拦截控制器\n */\n@Configuration\npublic class ShiroConfig {\n @Autowired\n JwtFilter jwtFilter;\n @Bean\n public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {\n DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();\n sessionManager.setSessionDAO(redisSessionDAO);\n return sessionManager;\n }\n @Bean\n public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,\n SessionManager sessionManager,\n RedisCacheManager redisCacheManager) {\n DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);\n securityManager.setSessionManager(sessionManager);\n securityManager.setCacheManager(redisCacheManager);\n /*\n * 关闭shiro自带的session,详情见文档\n */\n DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();\n DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();\n defaultSessionStorageEvaluator.setSessionStorageEnabled(false);\n subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);\n securityManager.setSubjectDAO(subjectDAO);\n return securityManager;\n }\n @Bean\n public ShiroFilterChainDefinition shiroFilterChainDefinition() {\n DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();\n Map<String, String> filterMap = new LinkedHashMap<>();\n filterMap.put(\"/**\", \"jwt\"); // 主要通过注解方式校验权限\n chainDefinition.addPathDefinitions(filterMap);\n return chainDefinition;\n }\n @Bean(\"shiroFilterFactoryBean\")\n public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,\n ShiroFilterChainDefinition shiroFilterChainDefinition) {\n ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();\n shiroFilter.setSecurityManager(securityManager);\n Map<String, Filter> filters = new HashMap<>();\n filters.put(\"jwt\", jwtFilter);\n shiroFilter.setFilters(filters);\n Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();\n shiroFilter.setFilterChainDefinitionMap(filterMap);\n return shiroFilter;\n }\n\n // 开启注解代理(默认好像已经开启,可以不要)\n @Bean\n public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){\n AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();\n authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);\n return authorizationAttributeSourceAdvisor;\n }\n @Bean\n public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {\n DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();\n return creator;\n }\n}\n复制代码\n```\n\n上面ShiroConfig,我们主要做了几件事情:\n\n1. 引入RedisSessionDAO和RedisCacheManager,为了解决shiro的权限数据和会话信息能保存到redis中,实现会话共享。\n2. 重写了SessionManager和DefaultWebSecurityManager,同时在DefaultWebSecurityManager中为了关闭shiro自带的session方式,我们需要设置为false,这样用户就不再能通过session方式登录shiro。后面将采用jwt凭证登录。\n3. 在ShiroFilterChainDefinition中,我们不再通过编码形式拦截Controller访问路径,而是所有的路由都需要经过JwtFilter这个过滤器,然后判断请求头中是否含有jwt的信息,有就登录,没有就跳过。跳过之后,有Controller中的shiro注解进行再次拦截,比如@RequiresAuthentication,这样控制权限访问。\n\n 那么,接下来,我们聊聊ShiroConfig中出现的AccountRealm,还有JwtFilter。\n\n#### AccountRealm\n\nAccountRealm是shiro进行登录或者权限校验的逻辑所在,算是核心了,我们需要重写3个方法,分别是\n\n- supports:为了让realm支持jwt的凭证校验\n- doGetAuthorizationInfo:权限校验\n- doGetAuthenticationInfo:登录认证校验\n\n我们先来总体看看AccountRealm的代码,然后逐个分析:\n\n- com.mao.shiro.AccountRealm\n\n```\n@Slf4j\n@Component\npublic class AccountRealm extends AuthorizingRealm {\n @Autowired\n JwtUtils jwtUtils;\n @Autowired\n UserService userService;\n @Override\n public boolean supports(AuthenticationToken token) {\n return token instanceof JwtToken;\n }\n @Override\n protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {\n return null;\n }\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n JwtToken jwt = (JwtToken) token;\n log.info(\"jwt----------------->{}\", jwt);\n String userId = jwtUtils.getClaimByToken((String) jwt.getPrincipal()).getSubject();\n User user = userService.getById(Long.parseLong(userId));\n if(user == null) {\n throw new UnknownAccountException(\"账户不存在!\");\n }\n if(user.getStatus() == -1) {\n throw new LockedAccountException(\"账户已被锁定!\");\n }\n AccountProfile profile = new AccountProfile();\n BeanUtil.copyProperties(user, profile);\n log.info(\"profile----------------->{}\", profile.toString());\n return new SimpleAuthenticationInfo(profile, jwt.getCredentials(), getName());\n }\n}\n复制代码\n```\n\n其实主要就是doGetAuthenticationInfo登录认证这个方法,可以看到我们通过jwt获取到用户信息,判断用户的状态,最后异常就抛出对应的异常信息,否者封装成SimpleAuthenticationInfo返回给shiro。 接下来我们逐步分析里面出现的新类:\n\n1、shiro默认supports的是UsernamePasswordToken,而我们现在采用了jwt的方式,所以这里我们自定义一个JwtToken,来完成shiro的supports方法。\n\n#### JwtToken\n\n- com.mao.shiro.JwtToken\n\n```plain\npublic class JwtToken implements AuthenticationToken {\n private String token;\n public JwtToken(String token) {\n this.token = token;\n }\n @Override\n public Object getPrincipal() {\n return token;\n }\n @Override\n public Object getCredentials() {\n return token;\n }\n}\n复制代码\n```\n\n2、JwtUtils是个生成和校验jwt的工具类,其中有些jwt相关的密钥信息是从项目配置文件中配置的:\n\n```plain\n@Component\n@ConfigurationProperties(prefix = \"markerhub.jwt\")\npublic class JwtUtils {\n private String secret;\n private long expire;\n private String header;\n /**\n * 生成jwt token\n */\n public String generateToken(long userId) {\n ...\n }\n \n // 获取jwt的信息\n public Claims getClaimByToken(String token) {\n ...\n }\n \n /**\n * token是否过期\n * @return true:过期\n */\n public boolean isTokenExpired(Date expiration) {\n return expiration.before(new Date());\n }\n}\n复制代码\n```\n\n3、而在AccountRealm我们还用到了AccountProfile,这是为了登录成功之后返回的一个用户信息的载体,\n\n#### AccountProfile\n\n- com.mao.shiro.AccountProfile\n\n```plain\n@Data\npublic class AccountProfile implements Serializable {\n private Long id;\n private String username;\n private String avatar;\n}\n复制代码\n```\n\n第三步,ok,基本的校验的路线完成之后,我们需要少量的基本信息配置:\n\n```\nshiro-redis:\n enabled: true\n redis-manager:\n host: 127.0.0.1:6379\nmarkerhub:\n jwt:\n # 加密秘钥\n secret: f4e2e52034348f86b67cde581c0f9eb5\n # token有效时长,7天,单位秒\n expire: 604800\n header: token\n复制代码\n```\n\n第四步:另外,如果你项目有使用spring-boot-devtools,需要添加一个配置文件,在resources目录下新建文件夹META-INF,然后新建文件spring-devtools.properties,这样热重启时候才不会报错。\n\n- resources/META-INF/spring-devtools.properties\n\n```\nrestart.include.shiro-redis=/shiro-[\\\\w-\\\\.]+jar\n```\n![在这里插入图片描述](https://img-blog.csdnimg.cn/787a0b66f3224f948b5bfe5588a672df.png)\n\n\n#### JwtFilter\n\n第五步:定义jwt的过滤器JwtFilter。\n\n这个过滤器是我们的重点,这里我们继承的是Shiro内置的AuthenticatingFilter,一个可以内置了可以自动登录方法的的过滤器,有些同学继承BasicHttpAuthenticationFilter也是可以的。\n\n我们需要重写几个方法:\n\n1. createToken:实现登录,我们需要生成我们自定义支持的JwtToken\n2. onAccessDenied:拦截校验,当头部没有Authorization时候,我们直接通过,不需要自动登录;当带有的时候,首先我们校验jwt的有效性,没问题我们就直接执行executeLogin方法实现自动登录\n3. onLoginFailure:登录异常时候进入的方法,我们直接把异常信息封装然后抛出\n4. preHandle:拦截器的前置拦截,因为我们是前后端分析项目,项目中除了需要跨域全局配置之外,我们再拦截器中也需要提供跨域支持。这样,拦截器才不会在进入Controller之前就被限制了。\n\n下面我们看看总体的代码:\n\n- com.mao.shiro.JwtFilter\n\n```plain\n@Component\npublic class JwtFilter extends AuthenticatingFilter {\n @Autowired\n JwtUtils jwtUtils;\n @Override\n protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {\n // 获取 token\n HttpServletRequest request = (HttpServletRequest) servletRequest;\n String jwt = request.getHeader(\"Authorization\");\n if(StringUtils.isEmpty(jwt)){\n return null;\n }\n return new JwtToken(jwt);\n }\n @Override\n protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {\n HttpServletRequest request = (HttpServletRequest) servletRequest;\n String token = request.getHeader(\"Authorization\");\n if(StringUtils.isEmpty(token)) {\n return true;\n } else {\n // 判断是否已过期\n Claims claim = jwtUtils.getClaimByToken(token);\n if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {\n throw new ExpiredCredentialsException(\"token已失效,请重新登录!\");\n }\n }\n // 执行自动登录\n return executeLogin(servletRequest, servletResponse);\n }\n @Override\n protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {\n HttpServletResponse httpResponse = (HttpServletResponse) response;\n try {\n //处理登录失败的异常\n Throwable throwable = e.getCause() == null ? e : e.getCause();\n Result r = Result.fail(throwable.getMessage());\n String json = JSONUtil.toJsonStr(r);\n httpResponse.getWriter().print(json);\n } catch (IOException e1) {\n }\n return false;\n }\n /**\n * 对跨域提供支持\n */\n @Override\n protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {\n HttpServletRequest httpServletRequest = WebUtils.toHttp(request);\n HttpServletResponse httpServletResponse = WebUtils.toHttp(response);\n httpServletResponse.setHeader(\"Access-control-Allow-Origin\", httpServletRequest.getHeader(\"Origin\"));\n httpServletResponse.setHeader(\"Access-Control-Allow-Methods\", \"GET,POST,OPTIONS,PUT,DELETE\");\n httpServletResponse.setHeader(\"Access-Control-Allow-Headers\", httpServletRequest.getHeader(\"Access-Control-Request-Headers\"));\n // 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态\n if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {\n httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());\n return false;\n }\n return super.preHandle(request, response);\n }\n}\n复制代码\n```\n\n那么到这里,我们的shiro就已经完成整合进来了,并且使用了jwt进行身份校验。\n\n### 5、异常处理\n\n有时候不可避免服务器报错的情况,如果不配置异常处理机制,就会默认返回tomcat或者nginx的5XX页面,对普通用户来说,不太友好,用户也不懂什么情况。这时候需要我们程序员设计返回一个友好简单的格式给前端。\n\n处理办法如下:通过使用@ControllerAdvice来进行统一异常处理,@ExceptionHandler(value = RuntimeException.class)来指定捕获的Exception各个类型异常 ,这个异常的处理,是全局的,所有类似的异常,都会跑到这个地方处理。\n\n- com.mao.common.exception.GlobalExceptionHandler\n\n步骤二、定义全局异常处理,@ControllerAdvice表示定义全局控制器异常处理,@ExceptionHandler表示针对性异常处理,可对每种异常针对性处理。\n\n```\n/**\n * 全局异常处理\n */\n@Slf4j\n@RestControllerAdvice\npublic class GlobalExcepitonHandler {\n // 捕捉shiro的异常\n @ResponseStatus(HttpStatus.UNAUTHORIZED)\n @ExceptionHandler(ShiroException.class)\n public Result handle401(ShiroException e) {\n return Result.fail(401, e.getMessage(), null);\n }\n /**\n * 处理Assert的异常\n */\n @ResponseStatus(HttpStatus.BAD_REQUEST)\n @ExceptionHandler(value = IllegalArgumentException.class)\n public Result handler(IllegalArgumentException e) throws IOException {\n log.error(\"Assert异常:-------------->{}\",e.getMessage());\n return Result.fail(e.getMessage());\n }\n /**\n * @Validated 校验错误异常处理\n */\n @ResponseStatus(HttpStatus.BAD_REQUEST)\n @ExceptionHandler(value = MethodArgumentNotValidException.class)\n public Result handler(MethodArgumentNotValidException e) throws IOException {\n log.error(\"运行时异常:-------------->\",e);\n BindingResult bindingResult = e.getBindingResult();\n ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();\n return Result.fail(objectError.getDefaultMessage());\n }\n\n @ResponseStatus(HttpStatus.BAD_REQUEST)\n @ExceptionHandler(value = RuntimeException.class)\n public Result handler(RuntimeException e) throws IOException {\n log.error(\"运行时异常:-------------->\",e);\n return Result.fail(e.getMessage());\n }\n}\n复制代码\n```\n\n上面我们捕捉了几个异常:\n\n- ShiroException:shiro抛出的异常,比如没有权限,用户登录异常\n- IllegalArgumentException:处理Assert的异常\n- MethodArgumentNotValidException:处理实体校验的异常\n- RuntimeException:捕捉其他异常\n\n### 6、实体校验\n\n当我们表单数据提交的时候,前端的校验我们可以使用一些类似于jQuery Validate等js插件实现,而 。\n\n我们使用springboot框架作为基础,那么就已经自动集成了Hibernate validatior。\n\n那么用起来啥样子的呢?\n\n第一步:首先在实体的属性上添加对应的校验规则,比如:\n\n```\n@TableName(\"m_user\")\npublic class User implements Serializable {\n private static final long serialVersionUID = 1L;\n @TableId(value = \"id\", type = IdType.AUTO)\n private Long id;\n @NotBlank(message = \"昵称不能为空\")\n private String username;\n @NotBlank(message = \"邮箱不能为空\")\n @Email(message = \"邮箱格式不正确\")\n private String email;\n \n ...\n} \n复制代码\n```\n\n第二步 :这里我们使用@Validated注解方式,如果实体不符合要求,系统会抛出异常,那么我们的异常处理中就捕获到MethodArgumentNotValidException。\n\n- com.mao.controller.UserController\n\n```\n/**\n * 测试实体校验\n * @param user\n * @return\n */\n@PostMapping(\"/save\")\npublic Object testUser(@Validated @RequestBody User user) {\n return user.toString();\n}\n复制代码\n```\n\n### 7、跨域问题\n\n因为是前后端分析,所以跨域问题是避免不了的,我们直接在后台进行全局跨域处理:\n\n- com.mao.config.CorsConfig\n\n```plain\n/**\n * 解决跨域问题\n */\n@Configuration\npublic class CorsConfig implements WebMvcConfigurer {\n @Override\n public void addCorsMappings(CorsRegistry registry) {\n registry.addMapping(\"/**\")\n .allowedOrigins(\"*\")\n .allowedMethods(\"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\")\n .allowCredentials(true)\n .maxAge(3600)\n .allowedHeaders(\"*\");\n }\n}\n复制代码\n```\n\nok,因为我们系统开发的接口比较简单,所以我就不集成swagger2啦,也比较简单而已。下面我们就直接进入我们的正题,进行编写登录接口。\n\n### Swagger2配置\n\nhttp://localhost:8080/swagger-ui.html \n\n```xml\n<!-- swagger-->\n <dependency>\n <groupId>io.springfox</groupId>\n <artifactId>springfox-swagger2</artifactId>\n <version>2.9.2</version>\n </dependency>\n\n <dependency>\n <groupId>io.springfox</groupId>\n <artifactId>springfox-swagger-ui</artifactId>\n <version>2.9.2</version>\n </dependency>\n\n```\n\n```java\npackage com.mao.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport springfox.documentation.service.ApiInfo;\nimport springfox.documentation.service.Contact;\nimport springfox.documentation.spi.DocumentationType;\nimport springfox.documentation.spring.web.plugins.Docket;\nimport springfox.documentation.swagger2.annotations.EnableSwagger2;\n\nimport java.util.ArrayList;\n\n@Configuration //配置类\n@EnableSwagger2// 开启Swagger2的自动配置\npublic class Swagger2Config {\n\n// Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger。\n @Bean //配置docket以配置Swagger具体参数\n public Docket docket() {\n return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());\n }\n //配置文档信息\n private ApiInfo apiInfo() {\n Contact contact = new Contact(\"胡超\", \"https://blog.csdn.net/qq_45696377?spm=1018.2226.3001.5343\", \"2557523039@qq.com\");\n return new ApiInfo(\n \"Vue-SpringBoot-Blog\", // 标题\n \"Personal Blog @HuChao\", // 描述\n \"v2.0\", // 版本\n \"https://www.jianshu.com/u/89b7bf8342cb\", // 组织链接\n contact, // 联系人信息\n \"Apach 2.0 许可\", // 许可\n \"许可链接\", // 许可连接\n new ArrayList<>()// 扩展\n );\n }\n\n}\n\n```\n\n\n\n\n\n### 8、登录接口开发\n\n登录的逻辑其实很简答,只需要接受账号密码,然后把用户的id生成jwt,返回给前段,为了后续的jwt的延期,所以我们把jwt放在header上。具体代码如下:\n\n- com.mao.controller.AccountController\n\n```plain\n@RestController\npublic class AccountController {\n @Autowired\n JwtUtils jwtUtils;\n @Autowired\n UserService userService;\n /**\n * 默认账号密码:markerhub / 111111\n *\n */\n @CrossOrigin\n @PostMapping(\"/login\")\n public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response) {\n User user = userService.getOne(new QueryWrapper<User>().eq(\"username\", loginDto.getUsername()));\n Assert.notNull(user, \"用户不存在\");\n if(!user.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))) {\n return Result.fail(\"密码错误!\");\n }\n String jwt = jwtUtils.generateToken(user.getId());\n response.setHeader(\"Authorization\", jwt);\n response.setHeader(\"Access-Control-Expose-Headers\", \"Authorization\");\n // 用户可以另一个接口\n return Result.succ(MapUtil.builder()\n .put(\"id\", user.getId())\n .put(\"username\", user.getUsername())\n .put(\"avatar\", user.getAvatar())\n .put(\"email\", user.getEmail())\n .map()\n );\n }\n \n // 退出\n @GetMapping(\"/logout\")\n @RequiresAuthentication\n public Result logout() {\n SecurityUtils.getSubject().logout();\n return Result.succ(null);\n }\n}\n复制代码\n```\n\n接口测试: ![图片](https://img-blog.csdnimg.cn/img_convert/a2580ad70253a0c2cd82e7b498b2918a.webp?x-oss-process=image/format,png)\n\n### 9、博客接口开发\n\n我们的骨架已经完成,接下来,我们就可以添加我们的业务接口了,下面我以一个简单的博客列表、博客详情页为例子开发:\n\n- com.markerhub.mao.BlogController\n\n```java\n@RestController\npublic class BlogController {\n @Autowired\n BlogService blogService;\n @GetMapping(\"/blogs\")\n public Result blogs(Integer currentPage) {\n if(currentPage == null || currentPage < 1) currentPage = 1;\n Page page = new Page(currentPage, 5)\n IPage pageData = blogService.page(page, new QueryWrapper<Blog>().orderByDesc(\"created\"));\n return Result.succ(pageData);\n }\n @GetMapping(\"/blog/{id}\")\n public Result detail(@PathVariable(name = \"id\") Long id) {\n Blog blog = blogService.getById(id);\n Assert.notNull(blog, \"该博客已删除!\");\n return Result.succ(blog);\n }\n \n @RequiresAuthentication\n@PostMapping(\"/blog/edit\")\npublic Result edit(@Validated @RequestBody Blog blog) {\n System.out.println(blog.toString());\n Blog temp = null;\n if(blog.getId() != null) {\n temp = blogService.getById(blog.getId());\n Assert.isTrue(temp.getUserId() == ShiroUtil.getProfile().getId(), \"没有权限编辑\");\n } else {\n temp = new Blog();\n temp.setUserId(ShiroUtil.getProfile().getId());\n temp.setCreated(LocalDateTime.now());\n temp.setStatus(0);\n }\n BeanUtil.copyProperties(blog, temp, \"id\", \"userId\", \"created\", \"status\");\n blogService.saveOrUpdate(temp);\n return Result.succ(\"操作成功\", null);\n}\n}\n复制代码\n```\n\n注意@RequiresAuthentication说明需要登录之后才能访问的接口,其他需要权限的接口可以添加shiro的相关注解。 接口比较简单,我们就不多说了,基本增删改查而已。注意的是edit方法是需要登录才能操作的受限资源。\n\n接口测试:\n\n![图片](https://img-blog.csdnimg.cn/img_convert/b6551f815805d0f946f077f9f35df33e.webp?x-oss-process=image/format,png)\n\n### 10、后端总结\n\n个人博客搞定一个基本骨架,基本的东西这里已经有了。接下来开发前端接口。\n\n1、以springboot为基础整合mybatisplus作为数据层的框架\n\n2、利用mybatisplus的代码生成工具,根据数据库表生成必要的controller、service、mapper等类\n\n3、将shiro整合进来,并用jwt作为前后端分离的用户凭证\n\n4、用JwtFilter将jwt封装成JwtToken\n\n5、将JwtToken传到自定义的Realm里面,然后我们再去数据库里面进行一个匹配,匹配用户状态,完成后将信息返回给shiro,然后shiro就可以登录成功\n\n6、登录成功后就可以进入到接口里面,接口前面如果没有添加一些@RequiresAuthentication(说明需要登录之后才能访问的接口),是可以公开进行访问的\n\n 如果前面定义了权限注解,那么这个接口需要进行一些认证或者授权后才能访问\n\n7、跨域问题:在全局配置和JwtFilter都要提前进行这样一个跨域的处理\n\n8、全局异常处理可以根据每一种不用的异常,返回不同的数据\n\n9、统一结果的封装\n\n# Vue前端页面开发\n\n### 1、前言\n\n接下来,我们来完成vueblog前端的部分功能。可能会使用的到技术如下:\n\n- vue\n- element-ui\n- axios\n- mavon-editor\n- markdown-it\n- github-markdown-css\n\n本项目实践需要一点点vue的基础,希望你对vue的一些指令有所了解,这样我们讲解起来就简单多了哈。\n\n### 2、项目演示\n\n我们先来看下我们需要完成的项目长什么样子,考虑到很多同学的样式的掌握程度不够,所以我尽量使用了element-ui的原生组件的样式来完成整个博客的界面。不多说,直接上图:\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/83f55dc5ae8d423b9236f25f54166f55.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n![在这里插入图片描述](https://img-blog.csdnimg.cn/06cc1f1deb8b4b8ab2185738859b4114.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n![在这里插入图片描述](https://img-blog.csdnimg.cn/50c7bfd4b4254f2ebb43f2d77e4c68b8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n\n\n### 3、环境准备\n\n万丈高楼平地起,我们下面一步一步来完成,首先我们安装vue的环境,我实践的环境是windows 10哈。\n\n1、首先我们上node.js官网([nodejs.org/zh-cn/](https://link.juejin.cn?target=https%3A%2F%2Fnodejs.org%2Fzh-cn%2F)),下载最新的长期版本,直接运行安装完成之后,我们就已经具备了node和npm的环境啦。\n\n![图片](https://img-blog.csdnimg.cn/img_convert/34b77edcdf76bae4a76ae168ac4f0bed.webp?x-oss-process=image/format,png)\n\n安装完成之后检查下版本信息:\n\n![图片](https://img-blog.csdnimg.cn/img_convert/59bcea74291c1e58c6d4d90648c4d161.webp?x-oss-process=image/format,png)\n\n2、接下来,我们安装vue的环境\n\n```plain\n# 安装淘宝npm\nnpm install -g cnpm --registry=https://registry.npm.taobao.org\n# vue-cli 安装依赖包\ncnpm install --g vue-cli\n复制代码\n```\n\n### 4、新建项目\n\n```plain\n# 打开vue的可视化管理工具界面\nvue ui\n复制代码\n```\n\n上面我们分别安装了淘宝npm,cnpm是为了提高我们安装依赖的速度。vue ui是@vue/cli3.0增加一个可视化项目管理工具,可以运行项目、打包项目,检查等操作。对于初学者来说,可以少记一些命令,哈哈。 3、创建vueblog-vue项目\n\n运行vue ui之后,会为我们打开一个[http://localhost:8080](https://link.juejin.cn?target=http%3A%2F%2Flocalhost%3A8080) 的页面:\n\n![图片](https://img-blog.csdnimg.cn/img_convert/66ec952f73d1bbd6f376c73777a6338b.webp?x-oss-process=image/format,png)\n\n然后切换到【创建】,注意创建的目录最好是和你运行vue ui同一级。这样方便管理和切换。然后点击按钮【在此创建新羡慕】\n\n![图片](https://img-blog.csdnimg.cn/img_convert/b5f731d6bc9f6699470d3c8982bb9e5f.webp?x-oss-process=image/format,png)\n\n下一步中,项目文件夹中输入项目名称“vueblog-vue”,其他不用改,点击下一步,选择【手动】,再点击下一步,如图点击按钮,勾选上路由Router、状态管理Vuex,去掉js的校验。\n\n![图片](https://img-blog.csdnimg.cn/img_convert/a004e70f5c39c7bcc2b9562724b17266.webp?x-oss-process=image/format,png)\n\n下一步中,也选上【Use history mode for router】,点击创建项目,然后弹窗中选择按钮【创建项目,不保存预设】,就进入项目创建啦。\n\n稍等片刻之后,项目就初始化完成了。上面的步骤中,我们创建了一个vue项目,并且安装了Router、Vuex。这样我们后面就可以直接使用。\n\n我们来看下整个vueblog-vue的项目结构\n\n```plain\n├── README.md 项目介绍\n├── index.html 入口页面\n├── build 构建脚本目录\n│ ├── build-server.js 运行本地构建服务器,可以访问构建后的页面\n│ ├── build.js 生产环境构建脚本\n│ ├── dev-client.js 开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷新\n│ ├── dev-server.js 运行本地开发服务器\n│ ├── utils.js 构建相关工具方法\n│ ├── webpack.base.conf.js wabpack基础配置\n│ ├── webpack.dev.conf.js wabpack开发环境配置\n│ └── webpack.prod.conf.js wabpack生产环境配置\n├── config 项目配置\n│ ├── dev.env.js 开发环境变量\n│ ├── index.js 项目配置文件\n│ ├── prod.env.js 生产环境变量\n│ └── test.env.js 测试环境变量\n├── mock mock数据目录\n│ └── hello.js\n├── package.json npm包配置文件,里面定义了项目的npm脚本,依赖包等信息\n├── src 源码目录 \n│ ├── main.js 入口js文件\n│ ├── app.vue 根组件\n│ ├── components 公共组件目录\n│ │ └── title.vue\n│ ├── assets 资源目录,这里的资源会被wabpack构建\n│ │ └── images\n│ │ └── logo.png\n│ ├── routes 前端路由\n│ │ └── index.js\n│ ├── store 应用级数据(state)状态管理\n│ │ └── index.js\n│ └── views 页面目录\n│ ├── hello.vue\n│ └── notfound.vue\n├── static 纯静态资源,不会被wabpack构建。\n└── test 测试文件目录(unit&e2e)\n └── unit 单元测试\n ├── index.js 入口脚本\n ├── karma.conf.js karma配置文件\n └── specs 单测case目录\n └── Hello.spec.js\n复制代码\n```\n\n### 5、安装element-ui\n\n接下来我们引入element-ui组件([element.eleme.cn](https://link.juejin.cn?target=https%3A%2F%2Felement.eleme.cn%2F%23%2Fzh-CN%2Fcomponent%2Finstallation)),这样我们就可以获得好看的vue组件,开发好看的博客界面。\n\n![图片](https://img-blog.csdnimg.cn/img_convert/307dd8fbdcd4d2da2392bec6220d1d7c.webp?x-oss-process=image/format,png)\n\n命令很简单:\n\n```plain\n# 切换到项目根目录\ncd vueblog-vue\n# 安装element-ui\ncnpm install element-ui --save\n复制代码\n```\n\n然后我们打开项目src目录下的main.js,引入element-ui依赖。\n\n```plain\nimport Element from \'element-ui\'\nimport \"element-ui/lib/theme-chalk/index.css\"\nVue.use(Element)\n复制代码\n```\n\n这样我们就可以愉快得在官网上选择组件复制代码到我们项目中直接使用啦。\n\n### 6、安装axios\n\n接下来,我们来安装axios([www.axios-js.com/](https://link.juejin.cn?target=http%3A%2F%2Fwww.axios-js.com%2F)),axios是一个基于 promise 的 HTTP 库,这样我们进行前后端对接的时候,使用这个工具可以提高我们的开发效率。\n\n安装命令:\n\n```plain\ncnpm install axios --save\n复制代码\n```\n\n然后同样我们在main.js中全局引入axios。\n\n```plain\nimport axios from \'axios\'\nVue.prototype.$axios = axios //\n复制代码\n```\n\n组件中,我们就可以通过this.$axios.get()来发起我们的请求了哈。\n\n### 7、页面路由\n\n接下来,我们先定义好路由和页面,因为我们只是做一个简单的博客项目,页面比较少,所以我们可以直接先定义好,然后在慢慢开发,这样需要用到链接的地方我们就可以直接可以使用:\n\n我们在views文件夹下定义几个页面:\n\n- BlogDetail.vue(博客详情页)\n- BlogEdit.vue(编辑博客)\n- Blogs.vue(博客列表)\n- Login.vue(登录页面)\n\n然后再路由中心配置:\n\n- router\\index.js\n\n```plain\nimport Vue from \'vue\'\nimport VueRouter from \'vue-router\'\nimport Login from \'../views/Login.vue\'\nimport BlogDetail from \'../views/BlogDetail.vue\'\nimport BlogEdit from \'../views/BlogEdit.vue\'\nVue.use(VueRouter)\nconst routes = [\n {\n path: \'/\',\n name: \'Index\',\n redirect: { name: \'Blogs\' }\n },\n {\n path: \'/login\',\n name: \'Login\',\n component: Login\n },\n {\n path: \'/blogs\',\n name: \'Blogs\',\n // 懒加载\n component: () => import(\'../views/Blogs.vue\')\n },\n {\n path: \'/blog/add\', // 注意放在 path: \'/blog/:blogId\'之前\n name: \'BlogAdd\',\n meta: {\n requireAuth: true\n },\n component: BlogEdit\n },\n {\n path: \'/blog/:blogId\',\n name: \'BlogDetail\',\n component: BlogDetail\n },\n {\n path: \'/blog/:blogId/edit\',\n name: \'BlogEdit\',\n meta: {\n requireAuth: true\n },\n component: BlogEdit\n }\n];\nconst router = new VueRouter({\n mode: \'history\',\n base: process.env.BASE_URL,\n routes\n})\nexport default router\n复制代码\n```\n\n接下来我们去开发我们的页面。其中,带有meta:requireAuth: true说明是需要登录字后才能访问的受限资源,后面我们路由权限拦截时候会用到。\n\n### 8、登录页面\n\n接下来,我们来搞一个登陆页面,表单组件我们直接在element-ui的官网上找就行了,登陆页面就两个输入框和一个提交按钮,相对简单,然后我们最好带页面的js校验。emmm,我直接贴代码了~~\n\n- views/Login.vue\n\n```xml\n<template>\n <div>\n <el-container>\n <el-header>\n <router-link to=\"/blogs\">\n <img src=\"https://www.markerhub.com/dist/images/logo/markerhub-logo.png\"\n style=\"height: 60%; margin-top: 10px;\">\n </router-link>\n </el-header>\n <el-main>\n <el-form :model=\"ruleForm\" status-icon :rules=\"rules\" ref=\"ruleForm\" label-width=\"100px\"\n class=\"demo-ruleForm\">\n <el-form-item label=\"用户名\" prop=\"username\">\n <el-input type=\"text\" maxlength=\"12\" v-model=\"ruleForm.username\"></el-input>\n </el-form-item>\n <el-form-item label=\"密码\" prop=\"password\">\n <el-input type=\"password\" v-model=\"ruleForm.password\" autocomplete=\"off\"></el-input>\n </el-form-item>\n <el-form-item>\n <el-button type=\"primary\" @click=\"submitForm(\'ruleForm\')\">登录</el-button>\n <el-button @click=\"resetForm(\'ruleForm\')\">重置</el-button>\n </el-form-item>\n </el-form>\n </el-main>\n </el-container>\n </div>\n</template>\n<script>\n export default {\n name: \'Login\',\n data() {\n var validatePass = (rule, value, callback) => {\n if (value === \'\') {\n callback(new Error(\'请输入密码\'));\n } else {\n callback();\n }\n };\n return {\n ruleForm: {\n password: \'111111\',\n username: \'markerhub\'\n },\n rules: {\n password: [\n {validator: validatePass, trigger: \'blur\'}\n ],\n username: [\n {required: true, message: \'请输入用户名\', trigger: \'blur\'},\n {min: 3, max: 12, message: \'长度在 3 到 12 个字符\', trigger: \'blur\'}\n ]\n }\n };\n },\n methods: {\n submitForm(formName) {\n const _this = this\n this.$refs[formName].validate((valid) => {\n if (valid) {\n // 提交逻辑\n this.$axios.post(\'http://localhost:8081/login\', this.ruleForm).then((res)=>{\n const token = res.headers[\'authorization\']\n _this.$store.commit(\'SET_TOKEN\', token)\n _this.$store.commit(\'SET_USERINFO\', res.data.data)\n _this.$router.push(\"/blogs\")\n })\n } else {\n console.log(\'error submit!!\');\n return false;\n }\n });\n },\n resetForm(formName) {\n this.$refs[formName].resetFields();\n }\n },\n mounted() {\n this.$notify({\n title: \'看这里:\',\n message: \'关注公众号:MarkerHub,回复【vueblog】,领取项目资料与源码\',\n duration: 1500\n });\n }\n }\n</script>\n复制代码\n```\n\n找不到啥好的方式讲解了,之后先贴代码,然后再讲解。 上面代码中,其实主要做了两件事情\n\n1、表单校验\n\n2、登录按钮的点击登录事件\n\n表单校验规则还好,比较固定写法,查一下element-ui的组件就知道了,我们来分析一下发起登录之后的代码:\n\n```plain\nconst token = res.headers[\'authorization\']\n_this.$store.commit(\'SET_TOKEN\', token)\n_this.$store.commit(\'SET_USERINFO\', res.data.data)\n_this.$router.push(\"/blogs\")\n复制代码\n```\n\n从返回的结果请求头中获取到token的信息,然后使用store提交token和用户信息的状态。完成操作之后,我们调整到了/blogs路由,即博客列表页面。\n\n#### token的状态同步\n\n所以在store/index.js中,代码是这样的:\n\n```plain\nimport Vue from \'vue\'\nimport Vuex from \'vuex\'\nVue.use(Vuex)\nexport default new Vuex.Store({\n state: {\n token: \'\',\n userInfo: JSON.parse(sessionStorage.getItem(\"userInfo\"))\n },\n mutations: {\n SET_TOKEN: (state, token) => {\n state.token = token\n localStorage.setItem(\"token\", token)\n },\n SET_USERINFO: (state, userInfo) => {\n state.userInfo = userInfo\n sessionStorage.setItem(\"userInfo\", JSON.stringify(userInfo))\n },\n REMOVE_INFO: (state) => {\n localStorage.setItem(\"token\", \'\')\n sessionStorage.setItem(\"userInfo\", JSON.stringify(\'\'))\n state.userInfo = {}\n }\n },\n getters: {\n getUser: state => {\n return state.userInfo\n }\n },\n actions: {},\n modules: {}\n})\n复制代码\n```\n\n存储token,我们用的是localStorage,存储用户信息,我们用的是sessionStorage。毕竟用户信息我们不需要长久保存,保存了token信息,我们随时都可以初始化用户信息。当然了因为本项目是个比较简单的项目,考虑到初学者,所以很多相对复杂的封装和功能我没有做,当然了,学了这个项目之后,自己想再继续深入,完成可以自行学习和改造哈。\n\n#### 定义全局axios拦截器\n\n点击登录按钮发起登录请求,成功时候返回了数据,如果是密码错误,我们是不是也应该弹窗消息提示。为了让这个错误弹窗能运用到所有的地方,所以我对axios做了个后置拦截器,就是返回数据时候,如果结果的code或者status不正常,那么我对应弹窗提示。\n\n在src目录下创建一个文件axios.js(与main.js同级),定义axios的拦截:\n\n```plain\nimport axios from \'axios\'\nimport Element from \"element-ui\";\nimport store from \"./store\";\nimport router from \"./router\";\naxios.defaults.baseURL=\'http://localhost:8081\'\naxios.interceptors.request.use(config => {\n console.log(\"前置拦截\")\n // 可以统一设置请求头\n return config\n})\naxios.interceptors.response.use(response => {\n const res = response.data;\n console.log(\"后置拦截\")\n // 当结果的code是否为200的情况\n if (res.code === 200) {\n return response\n } else {\n // 弹窗异常信息\n Element.Message({\n message: response.data.msg,\n type: \'error\',\n duration: 2 * 1000\n })\n // 直接拒绝往下面返回结果信息\n return Promise.reject(response.data.msg)\n }\n },\n error => {\n console.log(\'err\' + error)// for debug\n if(error.response.data) {\n error.message = error.response.data.msg\n }\n // 根据请求状态觉得是否登录或者提示其他\n if (error.response.status === 401) {\n store.commit(\'REMOVE_INFO\');\n router.push({\n path: \'/login\'\n });\n error.message = \'请重新登录\';\n }\n if (error.response.status === 403) {\n error.message = \'权限不足,无法访问\';\n }\n Element.Message({\n message: error.message,\n type: \'error\',\n duration: 3 * 1000\n })\n return Promise.reject(error)\n })\n复制代码\n```\n\n前置拦截,其实可以统一为所有需要权限的请求装配上header的token信息,这样不需要在使用是再配置,我的小项目比较小,所以,还是免了吧~\n\n然后再main.js中导入axios.js\n\n```plain\nimport \'./axios.js\' // 请求拦截\n复制代码\n```\n\n后端因为返回的实体是Result,succ时候code为200,fail时候返回的是400,所以可以根据这里判断结果是否是正常的。另外权限不足时候可以通过请求结果的状态码来判断结果是否正常。这里都做了简单的处理。\n\n登录异常时候的效果如下:\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/64d516e300884f6294194d7bc0533810.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n\n### 9、博客列表\n\n登录完成之后直接进入博客列表页面,然后加载博客列表的数据渲染出来。同时页面头部我们需要把用户的信息展示出来,因为很多地方都用到这个模块,所以我们把页面头部的用户信息单独抽取出来作为一个组件。\n\n#### 头部用户信息\n\n那么,我们先来完成头部的用户信息,应该包含三部分信息:id,头像、用户名,而这些信息我们是在登录之后就已经存在了sessionStorage。因此,我们可以通过store的getters获取到用户信息。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/f919318e3a504f6b9728d064a62250cd.png)\n\n\n看起来不是很复杂,我们贴出代码:\n\n- components\\Header.vue\n\n```plain\n<template>\n <div class=\"m-content\">\n <h3>欢迎来到MarkerHub的博客</h3>\n <div class=\"block\">\n <el-avatar :size=\"50\" :src=\"user.avatar\"></el-avatar>\n <div>{{ user.username }}</div>\n </div>\n <div class=\"maction\">\n <el-link href=\"/blogs\">主页</el-link>\n <el-divider direction=\"vertical\"></el-divider>\n <span>\n <el-link type=\"success\" href=\"/blog/add\" :disabled=\"!hasLogin\">发表文章</el-link>\n </span>\n <el-divider direction=\"vertical\"></el-divider>\n <span v-show=\"!hasLogin\">\n <el-link type=\"primary\" href=\"/login\">登陆</el-link>\n </span>\n <span v-show=\"hasLogin\">\n <el-link type=\"danger\" @click=\"logout\">退出</el-link>\n </span>\n </div>\n </div>\n</template>\n<script>\n export default {\n name: \"Header\",\n data() {\n return {\n hasLogin: false,\n user: {\n username: \'请先登录\',\n avatar: \"https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png\"\n },\n blogs: {},\n currentPage: 1,\n total: 0\n }\n },\n methods: {\n logout() {\n const _this = this\n this.$axios.get(\'http://localhost:8081/logout\', {\n headers: {\n \"Authorization\": localStorage.getItem(\"token\")\n }\n }).then((res) => {\n _this.$store.commit(\'REMOVE_INFO\')\n _this.$router.push(\'/login\')\n });\n }\n },\n created() {\n if(this.$store.getters.getUser.username) {\n this.user.username = this.$store.getters.getUser.username\n this.user.avatar = this.$store.getters.getUser.avatar\n this.hasLogin = true\n }\n }\n }\n</script>\n复制代码\n```\n\n上面代码created()中初始化用户的信息,通过hasLogin的状态来控制登录和退出按钮的切换,以及发表文章链接的disabled,这样用户的信息就能展示出来了。 然后这里有个退出按钮,在methods中有个logout()方法,逻辑比较简单,直接访问/logout,因为之前axios.js中我们已经设置axios请求的baseURL,所以这里我们不再需要链接的前缀了哈。因为是登录之后才能访问的受限资源,所以在header中带上了Authorization。返回结果清楚store中的用户信息和token信息,跳转到登录页面。\n\n然后需要头部用户信息的页面只需要几个步骤:\n\n```plain\nimport Header from \"@/components/Header\";\ndata() {\n components: {Header}\n}\n# 然后模板中调用组件\n<Header></Header>\n复制代码\n```\n\n#### 博客分页\n\n接下来就是列表页面,需要做分页,列表我们在element-ui中直接使用**时间线**组件来作为我们的列表样式,还是挺好看的。还有我们的分页组件。\n\n需要几部分信息:\n\n- 分页信息\n- 博客列表内容,包括id、标题、摘要、创建时间\n- views\\Blogs.vue\n\n```plain\n<template>\n <div class=\"m-container\">\n <Header></Header>\n <div class=\"block\">\n <el-timeline>\n <el-timeline-item v-bind:timestamp=\"blog.created\" placement=\"top\" v-for=\"blog in blogs\">\n <el-card>\n <h4><router-link :to=\"{name: \'BlogDetail\', params: {blogId: blog.id}}\">{{blog.title}}</router-link></h4>\n <p>{{blog.description}}</p>\n </el-card>\n </el-timeline-item>\n </el-timeline>\n \n </div>\n <el-pagination class=\"mpage\"\n background\n layout=\"prev, pager, next\"\n :current-page=currentPage\n :page-size=pageSize\n @current-change=page\n :total=\"total\">\n </el-pagination>\n </div>\n</template>\n<script>\n import Header from \"@/components/Header\";\n export default {\n name: \"Blogs\",\n components: {Header},\n data() {\n return {\n blogs: {},\n currentPage: 1,\n total: 0,\n pageSize: 5\n }\n },\n methods: {\n page(currentPage) {\n const _this = this\n this.$axios.get(\'http://localhost:8081/blogs?currentPage=\' + currentPage).then((res) => {\n console.log(res.data.data.records)\n _this.blogs = res.data.data.records\n _this.currentPage = res.data.data.current\n _this.total = res.data.data.total\n _this.pageSize = res.data.data.size\n })\n }\n },\n mounted () {\n this.page(1);\n }\n }\n</script>\n复制代码\n```\n\ndata()中直接定义博客列表blogs、以及一些分页信息。methods()中定义分页的调用接口page(currentPage),参数是需要调整的页码currentPage,得到结果之后直接赋值即可。然后初始化时候,直接在mounted()方法中调用第一页this.page(1)。完美。使用element-ui组件就是简单快捷哈哈! 注意标题这里我们添加了链接,使用的是标签。\n\n### 10、博客编辑(发表)\n\n我们点击发表博客链接调整到/blog/add页面,这里我们需要用到一个markdown编辑器,在vue组件中,比较好用的是mavon-editor,那么我们直接使用哈。先来安装mavon-editor相关组件:\n\n#### 安装mavon-editor\n\n基于Vue的markdown编辑器mavon-editor\n\n```plain\ncnpm install mavon-editor --save\n复制代码\n```\n\n然后在main.js中全局注册:\n\n```javascript\n// 全局注册\nimport Vue from \'vue\'\nimport mavonEditor from \'mavon-editor\'\nimport \'mavon-editor/dist/css/index.css\'\n// use\nVue.use(mavonEditor)\n复制代码\n```\n\nok,那么我们去定义我们的博客表单:\n\n```plain\n<template>\n <div class=\"m-container\">\n <Header></Header>\n <div class=\"m-content\">\n <el-form ref=\"editForm\" status-icon :model=\"editForm\" :rules=\"rules\" label-width=\"80px\">\n <el-form-item label=\"标题\" prop=\"title\">\n <el-input v-model=\"editForm.title\"></el-input>\n </el-form-item>\n <el-form-item label=\"摘要\" prop=\"description\">\n <el-input type=\"textarea\" v-model=\"editForm.description\"></el-input>\n </el-form-item>\n <el-form-item label=\"内容\" prop=\"content\">\n <mavon-editor v-model=\"editForm.content\"/>\n </el-form-item>\n <el-form-item>\n <el-button type=\"primary\" @click=\"submitForm()\">立即创建</el-button>\n <el-button>取消</el-button>\n </el-form-item>\n </el-form>\n </div>\n </div>\n</template>\n<script>\n import Header from \"@/components/Header\";\n export default {\n name: \"BlogEdit\",\n components: {Header},\n data() {\n return {\n editForm: {\n id: null,\n title: \'\',\n description: \'\',\n content: \'\'\n },\n rules: {\n title: [\n {required: true, message: \'请输入标题\', trigger: \'blur\'},\n {min: 3, max: 50, message: \'长度在 3 到 50 个字符\', trigger: \'blur\'}\n ],\n description: [\n {required: true, message: \'请输入摘要\', trigger: \'blur\'}\n ]\n }\n }\n },\n created() {\n const blogId = this.$route.params.blogId\n const _this = this\n if(blogId) {\n this.$axios.get(\'/blog/\' + blogId).then((res) => {\n const blog = res.data.data\n _this.editForm.id = blog.id\n _this.editForm.title = blog.title\n _this.editForm.description = blog.description\n _this.editForm.content = blog.content\n });\n }\n },\n methods: {\n submitForm() {\n const _this = this\n this.$refs.editForm.validate((valid) => {\n if (valid) {\n this.$axios.post(\'/blog/edit\', this.editForm, {\n headers: {\n \"Authorization\": localStorage.getItem(\"token\")\n }\n }).then((res) => {\n _this.$alert(\'操作成功\', \'提示\', {\n confirmButtonText: \'确定\',\n callback: action => {\n _this.$router.push(\"/blogs\")\n }\n });\n });\n } else {\n console.log(\'error submit!!\');\n return false;\n }\n })\n }\n }\n }\n</script>\n复制代码\n```\n\n逻辑依然简单,校验表单,然后点击按钮提交表单,注意头部加上Authorization信息,返回结果弹窗提示操作成功,然后跳转到博客列表页面。emm,和写ajax没啥区别。熟悉一下vue的一些指令使用即可。 然后因为编辑和添加是同一个页面,所以有了create()方法,比如从编辑连接/blog/7/edit中获取blogId为7的这个id。然后回显博客信息。获取方式是const blogId = this.$route.params.blogId。\n\n对了,mavon-editor因为已经全局注册,所以我们直接使用组件即可:\n\n```plain\n<mavon-editor v-model=\"editForm.content\"/>\n复制代码\n```\n\n效果如下: \n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/973a2c06a77d45ed98e16f776bc3d579.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n\n### 11、博客详情\n\n博客详情中需要回显博客信息,然后有个问题就是,后端传过来的是博客内容是markdown格式的内容,我们需要进行渲染然后显示出来,这里我们使用一个插件markdown-it,用于解析md文档,然后导入github-markdown-c,所谓md的样式。\n\n方法如下:\n\n```plain\n# 用于解析md文档\ncnpm install markdown-it --save\n# md样式\ncnpm install github-markdown-css\n复制代码\n```\n\n然后就可以在需要渲染的地方使用:\n\n- views\\BlogDetail.vue\n\n```plain\n<template>\n <div class=\"m-container\">\n <Header></Header>\n <div class=\"mblog\">\n <h2>{{ blog.title }}</h2>\n <el-link icon=\"el-icon-edit\" v-if=\"ownBlog\"><router-link :to=\"{name: \'BlogEdit\', params: {blogId: blog.id}}\">编辑</router-link></el-link>\n <el-divider></el-divider>\n <div class=\"content markdown-body\" v-html=\"blog.content\"></div>\n </div>\n </div>\n</template>\n<script>\n import \'github-markdown-css/github-markdown.css\' // 然后添加样式markdown-body\n import Header from \"@/components/Header\";\n export default {\n name: \"BlogDetail\",\n components: {\n Header\n },\n data() {\n return {\n blog: {\n userId: null,\n title: \"\",\n description: \"\",\n content: \"\"\n },\n ownBlog: false\n }\n },\n methods: {\n getBlog() {\n const blogId = this.$route.params.blogId\n const _this = this\n this.$axios.get(\'/blog/\' + blogId).then((res) => {\n console.log(res)\n console.log(res.data.data)\n _this.blog = res.data.data\n var MarkdownIt = require(\'markdown-it\'),\n md = new MarkdownIt();\n var result = md.render(_this.blog.content);\n _this.blog.content = result\n // 判断是否是自己的文章,能否编辑\n _this.ownBlog = (_this.blog.userId === _this.$store.getters.getUser.id)\n });\n }\n },\n created() {\n this.getBlog()\n }\n }\n</script>\n复制代码\n```\n\n具体逻辑还是挺简单,初始化create()方法中调用getBlog()方法,请求博客详情接口,返回的博客详情content通过markdown-it工具进行渲染。\n\n再导入样式:\n\n```plain\nimport \'github-markdown.css\'\n复制代码\n```\n\n然后在content的div中添加class为markdown-body即可哈。 效果如下:\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/85b88779901943c8916048882cf2bcfa.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5q-bX-S4ieaciA==,size_20,color_FFFFFF,t_70,g_se,x_16)\n\n另外标题下添加了个小小的编辑按钮,通过ownBlog (判断博文作者与登录用户是否同一人)来判断按钮是否显示出来。\n\n### 12、路由权限拦截\n\n页面已经开发完毕之后,我们来控制一下哪些页面是需要登录之后才能跳转的,如果未登录访问就直接重定向到登录页面,因此我们在src目录下定义一个js文件:\n\n- src\\permission.js\n\n```plain\nimport router from \"./router\";\n// 路由判断登录 根据路由配置文件的参数\nrouter.beforeEach((to, from, next) => {\n if (to.matched.some(record => record.meta.requireAuth)) { // 判断该路由是否需要登录权限\n const token = localStorage.getItem(\"token\")\n console.log(\"------------\" + token)\n if (token) { // 判断当前的token是否存在 ; 登录存入的token\n if (to.path === \'/login\') {\n } else {\n next()\n }\n } else {\n next({\n path: \'/login\'\n })\n }\n } else {\n next()\n }\n})\n复制代码\n```\n\n通过之前我们再定义页面路由时候的的meta信息,指定requireAuth: true,需要登录才能访问,因此这里我们在每次路由之前(router.beforeEach)判断token的状态,觉得是否需要跳转到登录页面。\n\n```plain\n{\n path: \'/blog/add\', // 注意放在 path: \'/blog/:blogId\'之前\n name: \'BlogAdd\',\n meta: {\n requireAuth: true\n },\n component: BlogEdit\n}\n复制代码\n```\n\n然后我们再main.js中import我们的permission.js\n\n```plain\nimport \'./permission.js\' // 路由拦截\n复制代码\n```\n\n### 13、前端总结\n\nok,基本所有页面就已经开发完毕啦,侧边栏和相关的css样式信息我未贴出来,大家直接上github上clone下来查看。\n\n## 项目大总结\n\n好啦,项目先到这里,花了3天半录制了一套对应的视频,记得去看,给我三连哇。\n\n项目视频:\n\n\n# Github提交\n\n\n\n\n\n\n\n','2021-09-01 22:05:25','2021-09-06 20:51:25',0),(37,1,'请问请问','去恶趣味',' 请问 ','2021-09-16 22:41:47','2021-09-16 22:41:47',1),(38,1,'qwewqew','wqeqwewqe','wqewqew','2021-09-22 13:09:49','2021-09-22 13:09:49',1);
/*Table structure for table `m_user` */
DROP TABLE IF EXISTS `m_user`;
CREATE TABLE `m_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(64) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`email` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL,
`status` int(5) NOT NULL,
`created` datetime DEFAULT NULL,
`last_login` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `UK_USERNAME` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*Data for the table `m_user` */
insert into `m_user`(`id`,`username`,`avatar`,`email`,`password`,`status`,`created`,`last_login`) values (1,'胡超','https://z3.ax1x.com/2021/08/24/hkNxW4.png','2557523039@qq.com','22050e33d3f4754c611de340dd47d0a7',1,'2021-08-20 09:48:24','2021-08-27 09:48:26'),(2,'詹圆圆','https://z3.ax1x.com/2021/08/27/hKo360.jpg','2129307869@qq.com','e10adc3949ba59abbe56e057f20f883e',1,'2021-08-27 09:50:44','2021-08-27 09:50:56');
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;