Go to file
calvin wong a764ff57f0 后端接口修改 2020-03-26 09:12:36 +08:00
CreateName-api 后端接口修改 2020-03-26 09:12:36 +08:00
CreateName-uni [dev]form表单 2020-03-11 08:39:08 +08:00
image image 2019-12-12 15:31:47 +08:00
README.md 后端接口修改 2020-03-26 09:12:36 +08:00

README.md

uni-app微信小程序----“起名”

第一章 uni-app入门

1-1 uni-app简介

1-2 小程序项目介绍

宝宝在线起名一直是中国人的刚需,与其路边寻找大师,不如求教线上业务

目前只在淘宝搜索:发现营业额极高,那就让我们把大师的价格打下去

image-20200305151834887

image-20200305151850634

数理派

五格法天格、人格、地格、外格和总格是笔画数来判定吉凶—判定标准为81数理

人格、地格、外格和总格

认为笔划全吉,人生就大吉

切记:全部测算需要繁体字

有算过命的人都会知道,当你把你的姓名生辰年月告诉给算命先生以后,算命先生就会根据你所提供的信息跟你简单地说一下你的天格多少,地格多少,等等。算命先生说的五格:天格,地格,人格,外格,总格分别是什么" 相信大部分人对此都不太了解他们口中所说的天格,地格是什么意思,接下来笔者就为各位朋友详细描述一下。

天格

天格为五格之一,是姓氏格,因姓氏来源于祖先,古人将先人、君主比作天,故有关姓氏的数理称为天格。天格对人格有相当密切的关系及影响力。代表童少年运。

计算方法复姓合计姓氏之笔画单姓再加假添一数。若是单姓其天格数理为姓的笔划数加1例如为四划加1为5其天数理为5。若是复姓其天格数理即复姓的笔划数而不必再加1。例如司马笔画为8则其天书里为8。

地格

地格为五格之一由名组成地格是前运格和基础运格主管1-17岁之间的运势代表少年时期和人格有相当密切的关系。其数理吉凶亦代表与子女、部属、晚辈的关系。代表前运。

计算方法由名字全部笔画数构成若是单名则地格数理名字笔画加1例如王三3画加1为4,其地格数理为4若是复姓其地格数理的整个名字的笔划数。例如王江海江海10画其地格理数为10。

人格

人格由姓与名字中的第一个字决定主管17-32岁之间的运势以主人之成就能力、个性、境遇作用力可影响人的一生是人的中心主运并且与天格、地格、外格均有相互的作用是五格的重点

计算方法:人格:又称'主运'是整个姓名的中心点人一生的命运均由此人格推断。如果是单姓姓名前两个字的原笔画相加就是人格。比如王一智4画的王加上1画的一等于5那这个5就是人格。如果是复姓则由复姓的总和数再加上姓名第三个字的笔画数比如司马相如15划的司马再加上8画的相等于23那23就是此名的人格。

外格

外格代表副运或中年运主管33-48岁之间的运势代表着社交活动、人际关系、环境家居、同辈关系等其数理吉凶与人格关系密切相互作用力较大。代表副运。

**计算方法:**总格笔画数减去人格笔画数如是单字名或单姓再加一划。单姓单名如“王石”这个名字王为4画“石”为5画总格为9画人格为9画单姓单名再加2画外格为2画。单姓双名如“关云长”这个名字“关”为19画“云”为12画“长”为8画。总格为39画39画减去人格31画在加1等于9画外格之数为9画。复姓单名如“司马懿”这个名字“司”为5画“马”为10画“懿”为22画相加为37画37话减去人格笔画再加1等于6画。司马懿的外格之数是6画。复姓复名的计算方法也是一样的。

总格

总格是合计姓与名的总笔画数,主中年至晚年的命运,又称'后运'。总格代表其人之一生,总纳天地人三格之意义,但对于其人性格、职业、命运富于变化之中早年运缺乏明显的暗灵作用,但于其人成为一定之性向类型之后,即发生灵导吉凶作用。

**计算方法:**合计姓与名的总笔画数,主中年至晚年的命运,又称'后运'。如司马懿总格数是5+10+22=37。刘江海总格数是14+7+11=32。

原理81数理判定吉凶

第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数舟归平海富贵荣华身心安泰雨露惠泽万事亨通。 (吉)

第64数非命骨肉分离孤独悲愁难得心安做事不成。 (凶)

第65数巨流归海天长地久家运隆昌福寿绵长事事成就。 (吉)

第66数岩头步马进退维谷艰难不堪等待时机一跃而起。 (凶)

第67数顺风通达天赋幸运四通八达家道繁昌富贵东来。 (吉)

第68数顺风吹帆智虑周密集众信达发明能智拓展昂进。 (吉)

第69数非业非业非力精神迫滞灾害交至遍偿痛苦。 (凶)

第70数残菊逢霜残菊逢霜寂寞无碍惨淡忧愁晚景凄凉。 (凶)

第71数石上金花石上金花内心劳苦贯彻始终定可昌隆。 (半吉)

第72数劳苦荣苦相伴阴云覆月外表吉祥内实凶祸。 (半吉)

第73数无勇盛衰交加徒有高志天王福祉终世平安。 (半吉)

第74数残菊经霜残菊经霜秋叶寂寞无能无智辛苦繁多。 (凶)

第75数退守退守保吉发迹甚迟虽有吉象无谋难成。 (凶)

第76数离散倾覆离散骨肉分离内外不和虽劳无功。 (凶)

第77数半吉家庭有悦半吉半凶能获援护陷落不幸。 (半吉)

第78数晚苦祸福参半先天智能中年发达晚景困苦。 (凶)

第79数云头望月云头望月身疲力尽穷迫不伸精神不定。 (凶)

第80数遁吉辛苦不绝早入隐遁安心立命化凶转吉。 (凶)

第81数万物回春最吉之数还本归元吉祥重叠富贵尊荣。 (吉)

八字派

根据出生年月判定天地人三才五行相生,判定喜用神,补到先天八字命盘欠缺!

生辰八字中的天干地支分别对应五行中的五个元素,如天干的甲、乙和地支的寅、卯属性为木,天干中的丙、丁和地支中的巳、午属性为火等。根据这个规律,可以推算出宝宝的五行为:金木土木木土木水(庚寅己卯甲戌甲子)。

一般来说,生辰八字中所含的金、木、水、火、土的数量,某一个属性有两个相同的,就是适中的,多于两个为“旺”,少于两个为“弱”,没有的为缺。缺和弱的需要补足,过旺过强则需要抑制。

通过分析这个宝宝的八字五行:金木土木木土木水(4木0火2土1金1水),可以明显看出:五行木旺、缺火。因此可以判断出这个宝宝的八字喜“火”,起名最好用五行属性为“火”的字。

计算方法:天干地支法

八字命盘从阴阳干支三合历取得。上排是天干,由五行「金水木火土」轮流排列。下排是地支,用十二生肖顺序排列。十二生肖可转换成五行。

六十甲子表:

甲子 乙丑 丙寅 丁卯 戊辰 已巳 庚午 辛未 壬申 癸酉
甲戌 乙亥 丙子 丁丑 戊寅 已卯 庚辰 辛巳 壬午 癸未
甲申 乙酉 丙戌 丁亥 戊子 已丑 庚寅 辛卯 壬辰 癸巳
甲午 乙未 丙申 丁酉 戊戌 已亥 庚子 辛丑 壬寅 癸卯
甲辰 乙巳 丙午 丁未 戊申 已酉 庚戌 辛亥 壬子 癸丑
甲寅 乙卯 丙辰 丁巳 戊午 已未 庚申 辛酉 壬戌 癸亥

六十甲子纳音五行如下:

甲子乙丑海中金,甲午乙未沙中金

丙寅丁卯炉中火,丙申丁酉山下火

戊辰己巳大林木,戊戌己亥平地木

庚午辛未路旁土,庚子辛丑壁上土

壬申癸酉剑锋金,壬寅癸卯金泊金

甲戌乙亥山头火,甲辰乙巳覆灯火

丙子丁丑涧下水,丙午丁未天河水

戊寅己卯城头土,戊申己酉大驿土

庚辰辛巳白腊金,庚戌辛亥钗钏金

壬午癸未杨柳木,壬子癸丑桑柘木

甲申乙酉泉中水,甲寅乙卯大溪水

丙戌丁亥屋上土,丙辰丁巳沙中土

戊子己丑霹雳火,戊午己未天上火

庚寅辛卯松柏木,庚申辛酉石榴木

壬辰癸巳长流水,壬戌癸亥大海水

天干五行和地支五行对照表

天干:,甲-木,乙-木,丙-火,丁-火,戊-土,己-土,庚-金,辛-金,壬-水,癸-水 地支:子-水,丑-土,寅-木,卯-木,辰-土,巳-火,午-火,未-土,申-金,酉-金,戌-土,亥-水

八字根据 输入公历日期,查出你的出生年,月,日的干支四柱,从而判断八字属性:

image-20200314142027464

年柱计算方法:

最简单的是查表地表法:

记年60年是一个循环记月60月是一个循环记日60日是一个循环记时60时是一个循环。年月日时各用两个字表示这就是人们通常说的“生辰八字”。

1、比如记住几个特殊的年份如1984年为甲子年类推192418641804……均为甲子年。提到的壬戌是第59顺位那么用甲子年份加上59减1得到的1982192218621802……都是壬戌年!

2、比如《辛亥革命》的辛亥年是1911年(48号干支)《戊戌变法》的戊戌年为35号干支比辛亥年早13年则“1911-13=1898”故《戊戌变法》是1898年。

3、比如2008年2008-3=20052005&pide;60余数为25查六十年甲子(干支表)25号干支得知是戊子年。

4、比如求1991年干支1991&pide;60=33余11年干支序号数=11-3=8。查干支表知该年为辛未年。

(注意:年干支的是以立春为分界的,正月立春以后出生的,用本年干支;在立春前出生的,用上一年的干支)

其次是计算法:

方法一:

首先要能记住十大天干和十二地支,十天干:甲、乙、丙、丁、戊、己、庚、辛、壬、癸;十二地支:子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥;

天干地支纪年法首先是天干在前地支在后比如今年2005就为-乙酉年。

天干算法:

4、 5、 6、 7、 8、 9、 0、 1、 2、 3 对应的十天干就是

甲、乙、丙、丁、戊、己、庚、辛、壬、癸,

数字为年代的最后的一位数字比如2005年最后一位是5对应的天干就是乙

地支的算法用年代数除以12后面的余数就代表某个地支

余数分别为4、 5、 6、 7、 8、 9、 10、 11、 0(能整除)、1、 2、3

代表地支为:子、丑、寅、卯、辰、巳、午、 未、 申、酉、戌、亥,

比如2005年为例年代末尾数为5对应的天干为乙2005除以12余数为1对应的地支为酉所以2005年为乙酉年。

方法二:

对应数字1、 2、 3、 4、 5、 6、 7、 8、 9、 0

相应天干:甲、乙、丙、丁、戊、己、庚、辛、壬、癸

对应数字1、 2、 3、 4、 5、 6、 7、 8、 9、 10、 11、 0

相应地支:子、丑、寅、卯、辰、巳、午、未、 申、 酉、 戌、 亥

公元年份-3除以10得余数可得天干如1984年(1984-3)|10=1所以天干为甲

公元年份-3除以12得余数可得地支如1984年(1984-3)|12=1所以地支为子

所以公元1984年为甲子年。

方法三:

用一个你知道的年份的天干地支来推算比如用2006年算1955年的天干地支先要知道2006年是丙戌年用2006-1955=51再用51除以10余数为1表明天干是丙往前推一位答案是乙接着用51除以12余数为3表明地支是戌往前推三位答案是未那么1955年就是乙未年。

月柱计算方法:

月的地支是固定不变的:正月是寅,二月是卯,三月是子,依次类推......

月的天干记忆比较简单,只要在你年干的基础上记住几句歌诀便可以了:

甲己之年丙做首;乙庚之年卯为头;

丙辛必定寻庚起;丁壬壬位顺流行;

还有戊癸何方觅,甲定之上好追求。

意思就是说,若遇甲或己的年份,正月是丙寅;遇上乙或庚之年,正月为戊寅;遇上丙或辛之年,正月为庚寅;遇上丁或壬之年,正月为壬寅;遇上戊或癸之年,正月为甲寅。依照正月之干支,其余月份按干支推算即可。有表如下:

年 份一月 二月 三月 四月 五月 六月 七月 八月 九月 十月 十一月 十二月

甲、巳丙寅 丁卯 戊辰 己巳 庚午 辛未 壬申 癸酉 甲戌 乙亥 丙子 丁丑

乙、庚戊寅 己卯 庚辰 辛巳 壬午 癸未 甲申 乙酉 丙戌 丁亥 戊子 己丑

丙、辛庚寅 辛卯 壬辰 癸巳 甲午 乙未 丙申 丁酉 戊戌 己亥 庚子 辛丑

丁、壬壬寅 癸卯 甲辰 乙巳 丙午 丁未 戊申 己酉 庚戌 辛亥 壬子 癸丑

戊、癸甲寅 乙卯 丙辰 丁巳 戊午 己未 庚申 辛酉 壬戌 癸亥 甲子 乙丑

年 份 一月 二月 三月 四月 五月 六月 七月 八月 九月 十月 十一月 十二月
甲、巳 丙寅 丁卯 戊辰 己巳 庚午 辛未 壬申 癸酉 甲戌 乙亥 丙子 丁丑
乙、庚 戊寅 己卯 庚辰 辛巳 壬午 癸未 甲申 乙酉 丙戌 丁亥 戊子 己丑
丙、辛 庚寅 辛卯 壬辰 癸巳 甲午 乙未 丙申 丁酉 戊戌 己亥 庚子 辛丑
丁、壬 壬寅 癸卯 甲辰 乙巳 丙午 丁未 戊申 己酉 庚戌 辛亥 壬子 癸丑
戊、癸 甲寅 乙卯 丙辰 丁巳 戊午 己未 庚申 辛酉 壬戌 癸亥 甲子 乙丑

日柱计算方法:

1.已知某年元旦干支,推算日干日支

公式:日干代数=元旦天干代数+所求日数±按月加减数-天干周转数。

   日支代数=元旦地支代数+所求日数±按月加减数-地支周转数。

说明1.按月加减数是根据日数与六十环周推算出来的。

   2.各月干支加减表如下图。

举例已知1981年的元旦干支为“己卯”求该年8月14日的日干支。

解答1981为平年推算日干支代数

   日干代数=己6+14+1-2×10=1

   日支代数=卯4+14+7-2×12=1

   故1981年8月14日的日干支为甲子。

2.已知某年元旦干支,推求所求年的元旦干支,再推求该年的日干支

公式:①平年求下一年的元旦干支=平年的元旦干支的基数+5

   因为平年的元旦到下一年的元旦干支数差5天

   ②闰年求下一年的元旦干支=闰年的元旦干支的基数+6

   因为闰年的元旦到下一年的元旦干支数差6天

举例已知1980年的元旦干支是癸酉求1981年的元旦干支。

解答1980年为闰年推算日干支代数

   日干代数=癸10+6-10=6

   日支代数=酉10+6-12=4

   故1981年的元旦干支为己卯。

3.已知某年某日的日干支,求该年或他年的日干支。

步骤①先求日总数②总数的个位数个位数为0则取10作为顺数日干的根据按值顺数即为所求日干③总数除以12的余数能整除则取12作为顺数日支的依据按值顺数即为所求日支。

举例已知1988年元月4日为“戊午”求1988年8月23日干支。

解答:①求日总数

   元月 2月 3月 4月 5月 6月 7月 8月

   28 + 29 + 31 + 30 + 31 + 30 + 31 + 23 = 233天

   ②总数个位数推日干

   个位数为3从戊推戊→己→庚故日干为庚。

   ③总数除以12的余数推日支

   233&pide;12=19······5从午推午→未→申→酉→戌故日支为戌。

   故8月23日干支为庚戌。

时干支的计算方法:

每日十二时辰与十二地支相配是固定不变的,因一天起于夜半的子时,故计算时亦从子时起,然后即顺排下去即知一天的时辰干支。有日上起时歌诀如下:

甲己还加甲,乙庚丙作初,丙辛生戊子,

丁壬庚子头,戊癸起壬子,周而复始求。

甲己起甲子:甲日、己日夜半的子时起于甲子时,顺推乙丑等。

乙庚起丙子:乙日、庚日夜半的子时起于丙子时,顺推乙丑等。

丙辛起戊子:丙日、辛日夜半的子时起于戊子时,顺推乙丑等。

丁壬起庚子:丁日、壬日夜半的子时起于庚子时,顺推乙丑等。

戊癸起壬子:戊日、癸日夜半的子时起于壬子时,顺推乙丑等。

 

举例求癸日的6点的时干支。

解答癸日起壬子6点为卯时从子时至卯时推四位所以时干从壬开始推四位壬、癸、甲、乙。

八字分类一共720*720=518400个

大家知道四柱八字男女断法不同起大运方法不同这样一个四柱八字又会生出两种组合即四柱八字组合数乘以男女即518400*21036800个

寓意派

根据小孩出生时间-地点,配合寓意良好的的诗词提字组合!

1-3 uni-app安装及目录介绍

下载hbuilderx

要讲UNI-APP,不得不介绍dcloud旗下一款轻量级ide编辑器hbuilderx

下载地址dcloud.io官网下载

hx mac和win之间分为标准和APP版

标准版需要自己安装插件app开发版已经把app开发常用的插件预先集成开箱即可用

UNI-APP有两种创建方式

使用 vue-cli 脚手架 创建 uni-app

cli创建UNI-APP目录名称 解析
dist ->build 存放通过build编译的各个平台的代码如mp-weixin
node_modules 项目依赖包模块
public 放置的为公共文件比如index.html文件为项目的生成模板我们写的vue的代码在webpack打包项目的时候最后都会基于该模板转换为浏览器可读的三大件html+javascript+css
src 存放通过HBuilderX可视化界面创建的的所有目录为源码目录
.gitignore git上传需要忽略的文件格式
babel.config.js ES6语法编译配置
package.json 项目基本信息
package-lock.json 锁定安装时的包的版本号并且需要上传到git以保证其他人在npm install时大家的依赖能保证一致
postcss.config.js postcss-loader 的配置文件名通过js对 CSS 进行处理
README.md 项目说明
pages 业务页面文件存放的目录
static 存放应用引用静态资源(如图片、视频等)的地方,注意:静态资源只能存放于此
app.vue 应用配置用来配置App全局样式以及监听生命周期
main.js Vue初始化入口文件
manifest.json 配置应用名称、appid、logo、版本等打包信息
pages.json 配置页面路由、导航条、选项卡等页面类信息
uni.scss 全局样式

1-4 uni-app配置

uni-app配置项目目录和微信小程序的目录基本相同

mainfest.json

主要作用配置应用名称、appid、logo、版本等打包信息

appid是微信小程序身份唯一标识

appid以微信小程序为例子

进入公众平台

pages.json

配置页面路由、导航条、tabbar选项卡等页面类信息

当我们在微信小程序 json 中设置 backgroundColor 时,实际在电脑的模拟器中根本看不到效果,

窗体下拉刷新或上拉加载时露出的背景

第二章 整体设计

2.1api接口设计

首先我们了解下

什么是前后端分离?

前后端分离,就是在对前端开发人员和后端开发人员的工作进行解耦,尽量减少他她们之间的交流成本,帮助他她们更能专注于自己擅长的工作。

前端web 框架 vue \react \angular

安卓端

ios端

小程序端

pwa

本项目是一款基于 SpringBoot 的 Api 服务器脚手架。服务端基础通用框架提取,配以详细的说明文档,针对 Restful 风格 API 服务器,降低学习成本,提高开发效率

API设计规范

设计接口是一件容易的事,也是件困难的事。设计接口每个人都会,每个人都能设计,也由此产生了各种各样的理念的接口。工作这么多年,我也很有感悟。很多人会说,设计接口多么简单,只要命名好,然后联调通了,上线可以调用就行了。特别是非互联网行业的人,这里没有歧视的意思。因为互联网行业和传统行业太多不一致性决定了这种思想的产生。

通过不同的methodget\post\put\delete来进行crud

格式{ "data": 返回数据, "code": 状态码, "msg": "返回描述"}

2.2 mysql数据库搭建

本机vmware搭建centos7环境

使用docker安装mysql8

2.3 mysql数据库创建和设计规范

《mysql设计规范》

数据结构设计:逻辑设计 > 物理设计 实际工作中:逻辑设计 + 物理设计 物理设计:表名,字段名,字段类型 磁盘IO和操作系统类型对mysql的性能是非常大的

一. 数据库命名规范

所有的数据库对象名称必须使用小写字母并用下划线表示因为默认情况下mysql对大小写敏感mysql数据库本质上是linux系统下的一个文件而linux系统是大小写敏感的 所有数据库对象名称禁止使用mysql保留关键字 数据库对象的命名要能做到见名知意并且最好不要超过32个字符。太长不方便使用并且会在传输时增加网络开销 临时表必须以tmp_为前缀并以日期为后缀 备份表必须以bak_为前缀并以日期为后缀 所有存储相同数据的列名和列类型必须一致比如user表中的id和order表中的user_id

二. 数据库基本设计规范

所有表必须使用Innodb存储引擎 极少数特殊业务需求除外 Innodb引擎是5.6之后的默认存储引擎mysql5.5之前使用Myisam(默认存储引擎) Innodb优点支持事务行级锁更好的恢复性高并发下性能更好 数据库和表的字符集统一使用UTF-8 如果要存储一些如表情符号的还需使用UTF-8的拓展字符集 数据库,表,字段字符集一定要统一,统一字符集可以避免由于字符集转换产生的乱码 在mysql中UTF-8字符集汉字占3字节ASCII码占1字节 所有表和字段都需要添加注释 从一开始就进行数据字典的维护 即数据库说明文档 尽量控制单表数据量大小, 建议控制在500万以内虽然500万并不是mysql的数据库限制但是会给修改表结构备份恢复带来很大困难。 单表可存储数据量大小取决于存储设置和文件系统 想减少单表数据量:历史数据归档(常见于日志表),分库分表(常见于业务表),分区表 建议不要使用mysql分区表因为分区表在物理上表现为多个文件在逻辑上表现为一个表。如果一定要分区请谨慎选择分区键跨分区查询效率比查询大数据量的单表查询效率更低 建议采物理分表的方式管理大数据,但是对应用程序的开发要求和复杂度更高 尽量做到冷热数据分离,减少表的宽度(字段数) 减少磁盘IO保证热数据的内存缓存命中率更有效的利用缓存避免读入无用的冷数据 这样的话,就要对表的列进行拆分,将经常使用的列放到一个表中,可以避免过多的关联操作,也可以提高查询性能 禁止在表中建立预留字段 预留字段很难做到见名知义,预留字段无法确定存储的数据类型,后期如果修改字段类型,会对全表锁定,严重影响数据库的并发性 对目前mysql来说修改一个字段的成本要远远大于增加一个字段的成本 禁止在数据库中存储图片,文件等二级制数据 这类数据如果要存就得使用blog或者text这样的大字段加以存储会影响数据库的性能 文件这种通常所占数据容量很大会在短时间内造成数据库文件的快速增长而数据库在读取数据时会进行大量的随机IO操作如果数据文件过大IO操作会非常耗时从而影响数据库性能 正确做法是将这类数据存储在文件服务器中,而数据库只村存储地址信息 禁止在线上做数据库压力测试 会对正常业务造成影响,也会产生很多垃圾数据 建议建立专门的压力测试数据库,进行测试,然后对比测试服务器和线上服务器的硬件环境,评估线上数据库的性能 禁止从开发环境,测试环境直连生产环境数据库

三. 索引设计规范(Innodb中主键实质上是一个索引)

限制每张表上索引数量建议单表不超过5个索引。索引并不是越多越好可以提高查询效率但是会降低插入和更新的效率。甚至在一些情况下还会降低查询效率因为mysql优化器在选择如何优化查询时会根据统计信息对每一个可用索引来进行评估以生成一个最好的执行计划如果同时有很多索引都可以用于查询就会增加mysql查询优化器生成查询计划的时间。 每个Innodb表都必须有一个主键。Innodb是一种索引索引组织表是指数据存储的逻辑顺序和索引的顺序是相同Innodb是按照主键索引的顺序来组织表的因此每个Innodb表都必须要有一个主键如果我们没有指定主键那么Innodb会优先选择表中第一个非空唯一索引来作为主键如果没有这个索引那么Innodb会自动生成一个占6字节的主键而这个主键的性能并不是最好。 不使用更新频繁的列作为主键不使用多列联合主键。因为Innodb是一种索引索引组织表如果主键上的值频繁更新就意味着数据存储的逻辑顺序频繁变动必然会带来大量的IO操作降低数据库性能。 不要使用uuidmd5hash字符串列作为主键。因为这种主键不能保证主键的值是顺序增长的如果后来的主键值在已有主键值的中间段那么这个主键插入的时候会将所有主键值大于它的列都向后移。 最好选择能保证值的顺序为顺序增长的列为主键。并且数据不能重复建议用mysql自增id建立主键 面试问题1: 要在哪些列上建立索引? 在selectdeleteupdate的where从句中的列 包含在order bygroup bydistinct字段中的列 多表join的关联列mysql对关联操作的处理方式只有一种那就是嵌套循环的关联方式所以这种操作的性能对关联列上的索引的依赖性很大 面试问题2: 复合索引,如何选择索引列的顺序? 从左到右的顺序来使用的 区分度(列中group by的数目和此列总行数的比值趋近于1)最高的列放在联合索引的最左侧 在区分度差不多的情况下尽量吧字段长度小的放在联合索引的最左侧因为同样的行数字段小的文件也小读取时IO性能更优 使用最频繁的列放在联合索引的左侧,这样的话,可以较少地建立索引就能满足需求 避免建立冗余索引和重复索引 对于频繁的查询优先使用覆盖索引 就是包含了所有查询字段的索引这样可以避免Innodb表进行索引的二次查找并可以把随机IO变为顺序IO提高查询效率 尽量避免使用外键 mysql和别的数据库不同会自动在外键上建立索引会降低数据库的写性能 建议不使用外键约束,但是一定要在表与表之间的关联键上建立索引,虽然外键是为了保证数据的完整性,但是最好在代码中去保证。

四. 字段设计规范

优先选择符合存储需要的最小的数据类型 尽量将字符串转化为数字类型存储如将ip存储为数字inet_aton(255.255.255.255) = 4294967295 ,反之, inet_ntoa(4294967295) = 255.255.255.255 对于非负整型数据优先使用无符号整型来存储id,age,无符号相对于有符号,可以多出一倍的存储空间 mysql中varchar(n)中n表示字符数而不是字节数 避免使用textblog来存储字段这种类型只能使用前缀索引如果非要使用建议将这种数据分离到单独的拓展表中 避免使用enum类型。枚举本身是一个字符串类型但是内部确是用正数类型来存储的所以最多可存储65535种不同的值修改的话必须使用alter语句直接修改元数据有操作风险order by效率低必须转换并无法使用索引禁止使用数值作为enum值因为enum本身是索引顺序存储的会造成逻辑混淆 尽可能把所有列定义为not null。 索引null列需要额外的空间来保存占更多空间 进行比较和计算时对null值作特别的处理可能造成索引失效 禁止使用字符串来存储日期型数据。 无法使用日期函数计算比较 字符串存储要占更多的内存空间datetime(8字节)和timestamp(本身是以int存储占4字节,范围:1970-01-01 00:00:01到2038-01-19 03:14:07) 财务相关数据使用decimal类型 (精准浮点类型,在计算时不丢失精度)。

五. SQL开发规范

建议使用预编译语句(prepareStatment)进行数据库操作 可以同步执行预编译计划,减少预编译时间 可以有效避免动态sql带来的SQL注入的问题 只传参数一次解析多次使用比传递sql语句更高效 避免数据类型的隐式转换 一般出现在where从句中会导致索引失效select id,name from user where id = 12; 充分利用已存在的索引 避免使用双%的查询条件,不走索引 一个SQL只能利用到复合索引中的一列进行范围查询 使用left join或not exists来优化not in操作 程序连接不同的数据库使用不同的账号,禁止跨库查询 为数据库迁移和分库分表留出余地 降低业务耦合度 避免权限过大而产生的安全风险 禁止使用select * 来查询,必须用字段名 可能会消耗更多的cpu和IO以及网络资源 无法使用覆盖索引 可以减少表结构变更对已有程序的影响 禁止使用不含字段列表的insert语句。 可以减少表结构变更对已有程序的影响 禁止使用子查询 虽然可使sql可读性好但是缺点远远大于优点 子查询返回的结果集无法使用索引,结果集会被存储到一个临时表中,结果集越大性能越低 把子查询优化为join操作但是并不是所有的都可以优化为join一般情况下只有当子查询是在in字句中并且子查询是一个简单的sql(不包含uniongroup byorder bylimit)才能转换为关联查询 避免join过多的表 每join一个表会占一部分内存(join_buffer_size) 会产生临时表操作,影响查询效率 mysql最多允许关联61个表建议不超过5个 减少同数据库的交互次数 数据库更适合处理批量操作 合并多个相同的操作到一起,提高处理效率 使用in代替or in的值不要超过500个 in 操作可以有效利用索引 禁止使用order by rand()进行随机排序 会把表中所有符合条件的数据装载到内存中进行排序 会消耗大量的cpu和io及内存资源 推荐在程序中获取随机值 禁止在where从句中对列进行函数转换和计算 导致无法使用相关列上的索引 where date(create_time)=20170901 写成 where create_time >= 20170901 and create_time < 20170902 在明显不会有重复值时使用union all而不是union union 会把所有数据放在临时表中后再进行去重操作会多消耗内存IO网络资源 union all 不会再对结果集进行去重操作 拆分复杂的大sql为多个小sql 目前mysql中一个sql只能使用一个cpu计算不支持多cpu并行计算 sql拆分后可以通过并行执行来提高处理效率

六. 数据库操作行为规范

主要面向手动操作数据库的行为 超过100万的批量写操作要分批多次进行操作 主从复制中:大批量操作可能会造成严重的主从延迟,因为当主库执行完成后,才会在从库执行 binlog日志为row格式时会产生大量的日志 避免产生大量事务,产生阻塞,占满可用连接 对大表数据结构的修改一定要谨慎 可能会造成严重的锁表操作,尤其是生产环境,是不能忍受的 对于大表使用pt-online-schema-change修改表结构 首先会建立一个与原表结构相同的新表 然后在新表上进行表结构的修改 然后把原表中的数据复制到新表中,并且增加一些触发器,以便把原表中即时新增的数据也复制到新表中 在行的所有数据复制完成之后,会在原表上增加一个很准的时间锁,同时把新表命名为原表,把原表删掉 [实际上是把一个原子的DDL操作分解成多批次进行] [避免大表修改产生的主从延迟问题] [避免在对表字段进行修改时进行锁表] 禁止为程序使用的账号赋予super权限 当数据库连接数达到最大限制时允许1个有super权限的用户连接 super权限只能留给DBA处理问题的账号使用 对于程序连接数据库账号,遵循权限最小原则 程序使用的数据库账号只能在一个DB下使用不准跨库 程序使用的账号原则上不准有drop权限

docker安装mysql,远程访问

//搜索mysql
docker search mysql
//选定版本,抓取镜像
docker pull mysql:8.0
//创建同步mysql的文件夹
mkdir -p /data/mysql01
//创建容器
docker run --name mysql01  -p 3307:3306 -v /data/mysql01:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=zan123456 -d mysql:8.0 


--restart 标志会检查容器的退出代码,并据此来决定是否要重启容器,默认是不会重启。
--restart的参数说明
always无论容器的退出代码是什么Docker都会自动重启该容器。
on-failure只有当容器的退出代码为非0值的时候才会自动重启。另外该参数还接受一个可选的重启次数参数`--restart=on-fialure:5`表示当容器退出代码为非0时Docker会尝试自动重启该容器最多5次。

-v 容器内的 /var/lib/mysql 在宿主机上 /data/mysql01 做映射  
-e MYSQL_ROOT_PASSWORD 初始密码
-p 将宿主机3306的端口映射到容器3306端口

error:如果启动失败查看日志docker logs mysql01提示

chown: cannot read directory '/var/lib/mysql/': Permission denied

容器中没有执行权限 //挂载外部数据卷时,无法启动容器, 报 chown: cannot read directory '/var/lib/mysql/': Permission denied 由$ docker logs [name] 查看得知 该原因为centOs7默认开启selinux安全模块,需要临时关闭该安全模块,或者添加目录到白名单 临时关闭selinuxsu -c "setenforce 0" 重新开启selinuxsu -c "setenforce 1" 添加selinux规则将要挂载的目录添加到白名单 示例chcon -Rt svirt_sandbox_file_t /data/mysql01(我启动挂载的路径)

error:用navicat连接如果报错

img报错是因为加密算法变了

我们在docker里面改变加密算法

mysql> grant all PRIVILEGES on *.* to root@'%' WITH GRANT OPTION;
Query OK, 0 rows affected (0.01 sec)
 
mysql> ALTER user 'root'@'%' IDENTIFIED BY '123456' PASSWORD EXPIRE NEVER;
Query OK, 0 rows affected (0.11 sec)
 
mysql> ALTER user 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
Query OK, 0 rows affected (0.11 sec)
 
mysql>  FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)

算法换成mysql_native_password即可

user数据表建立

配置application.yml

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://192.168.253.133:3307/test?useUnicode=true&useSSL=false&characterEndcoding=utf8&useTimezone=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

创建表格

CREATE TABLE `user`  (
  `open_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'open_id',
  `skey` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'skey',
  `create_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `last_visit_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后登录时间',
  `session_key` varchar(100) CHARACTER SET  COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'session_key',
  `avatar_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
  `nick_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '网名',
  PRIMARY KEY (`open_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '微信用户信息' ;

第三章 前端页面

3.1 前端登录login开发

前端使用uni-app

后端springboot2.X+mybatis plus

持久化数据库mysql8.0.16

效果展示:

image

微信小程序登录步骤

image

第一步小程序通过uni.login()获取code。

第二步小程序通过uni.request()发送code到开发者服务器。

第三步开发者服务器接收小程序发送的code并携带appid、appsecret这两个需要到微信小程序后台查看、code发送到微信服务器。

第四步微信服务器接收开发者服务器发送的appid、appsecret、code进行校验。校验通过后向开发者服务器发送session_key、openid。

第五步开发者服务器自己生成一个key自定义登录状态与openid、session_key进行关联并存到数据库中mysql、redis等

第六步开发者服务器返回生成key自定义登录状态到小程序。

第七步小程序存储key自定义登录状态到本地。

首页index

<template>
	<view class="content">
		<image class="logo" :src="userInfo.avatarUrl || '/static/missing-face.png'"></image>
		<view class="text-area">
			<text class="title" @click="toLogin">{{ hasLogin ? userInfo.nickName || '未设置昵称' : '立即登录' }}</text>
		</view>
	</view>
</template>

<script>
import { mapState } from 'vuex';
export default {
	data() {
		return {};
	},
	onLoad() {},
	computed: {
		...mapState(['hasLogin', 'userInfo'])
	},

	methods: {
		toLogin() {
			if (!this.hasLogin) {
				uni.navigateTo({
					url: '/pages/login/login'
				});
			}
		}
	}
};
</script>

<style>
.content {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
}

.logo {
	height: 200rpx;
	width: 200rpx;
	margin-top: 200rpx;
	margin-left: auto;
	margin-right: auto;
	margin-bottom: 50rpx;
}

.text-area {
	display: flex;
	justify-content: center;
}

.title {
	font-size: 36rpx;
	color: #8f8f94;
}
</style>

登录页面login

<template>
	<view class="container">
		<view class="left-top-sign">LOGIN</view>
		<view class="welcome">欢迎回来!</view>
		<button class="confirm-btn" open-type="getUserInfo" @getuserinfo="wxLogin" :disabled="logining">微信授权登录</button>
	</view>
</template>

<script>
import { mapMutations } from 'vuex';

export default {
	data() {
		return {
			logining: false
		};
	},
	onLoad() {},
	methods: {
		wxLogin(e) {
		const that = this;
		that.logining = true;
		let userInfo = e.detail.userInfo;
		uni.login({
			provider:"weixin",
			success:(login_res => {
				let code = login_res.code;
				uni.getUserInfo({
					success(info_res) {
						console.log(info_res)
						uni.request({
							url:'http://localhost:8080/wxlogin',
							method:"POST",
							header: {
							                  'content-type': 'application/x-www-form-urlencoded'
							                },
							data:{
								code : code,
								rawData : info_res.rawData
							},
							success(res) {
								if(res.data.status == 200){
									that.$store.commit('login',userInfo);
									// uni.setStorageSync("userInfo",userInfo);
									// uni.setStorageSync("skey", res.data.data);
								}else{
									console.log('服务器异常')
								}
							},
							fail(error) {
								console.log(error)
							}
						})
						uni.hideLoading()
						uni.navigateBack()
					}
				})
				
			})
			})
		}
	}
};
</script>

<style lang="scss">
.container {
	display: flex;
	overflow: hidden;
	background: #fff;
	flex-direction: column;
	justify-content: center;
	.left-top-sign {
		font-size: 120upx;
		color: $page-color-base;
		position: relative;
		left: -10upx;
		margin-top: 100upx;
	}
	.welcome {
		position: relative;
		left: 50upx;
		top: -90upx;
		font-size: 46upx;
		color: #555;
		text-shadow: 1px 0px 1px rgba(0, 0, 0, 0.3);
	}
	.confirm-btn {
		width: 630upx;
		height: 76upx;
		line-height: 76upx;
		border-radius: 50px;
		margin-top: 70upx;
		background: $uni-color-primary;
		color: #fff;
		font-size: $font-lg;
		&:after {
			border-radius: 100px;
		}
	}
}
</style>

第四章 uni-app业务逻辑开发

4.1 vuex在uni-app中的使用

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
	state: {
		hasLogin: false,
		userInfo: {},
	},
	mutations: {
		login(state, provider) {

			state.hasLogin = true;
			state.userInfo = provider;
			uni.setStorage({//缓存用户登陆状态
			    key: 'userInfo',  
			    data: provider  
			}) 
			console.log(state.userInfo);
		},
		logout(state) {
			state.hasLogin = false;
			state.userInfo = {};
			uni.removeStorage({  
	            key: 'userInfo'  
	        })
		}
	},
	actions: {
	
	}

})

export default store

4.2 登录功能实现

小程序已经抛弃getUserInfo,使用open-type绑定即可;

调用 wx.login 获取 code。

使用 wx.getSetting 获取用户的授权情况

  • 如果用户已经授权,直接调用 API wx.getUserInfo 获取用户最新的信息;
  • 用户未授权,在界面中显示一个按钮提示用户登入,当用户点击并授权后就获取到用户的最新信息。

将获取到的用户数据连同wx.login返回的code一同传给后端

第五章 springboot开发后端接口

4.1 entity

package top.weimumu.loginapi.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

/**
 * @author: create by calvin wong
 * @date:2019/12/10
 **/

@Data
@TableName("user")
public class User {
    /**
     * openid
     */
    @TableId(value = "open_id",type = IdType.INPUT)
    private String openId;
    /**
     * 用户头像
     */
    private String avatarUrl;
    /**
     * 用户网名
     */
    private String nickName;
    /**
     * session_key
     */
    private String session_key;
    /**
     * skey
     */
    private String skey;
    /**
     * 创建时间
     */
    @TableField("create_time")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private Date createTime;
    /**
     * 最后登录时间
     */
    @TableField("last_visit_time")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private Date lastVisitTime;

}

4.2 mapper

package top.weimumu.loginapi.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import top.weimumu.loginapi.entity.User;

/**
 * @author: create by calvin wong
 * @date:2019/12/4
 **/
public interface UserMapper extends BaseMapper<User> {

}

4.3 common封装工具类

GolbalResult

package top.weimumu.loginapi.common;

/**
 * @author: create by calvin wong
 * @date:2019/12/10
 **/
public class GlobalResult {
    // 响应业务状态
    private Integer status;

    // 响应消息
    private String msg;

    // 响应中的数据
    private Object data;

    private String ok;	// 不使用

    public static GlobalResult build(Integer status, String msg, Object data) {
        return new GlobalResult(status, msg, data);
    }

    public static GlobalResult ok(Object data) {
        return new GlobalResult(data);
    }

    public static GlobalResult ok() {
        return new GlobalResult(null);
    }

    public static GlobalResult errorMsg(String msg) {
        return new GlobalResult(500, msg, null);
    }

    public static GlobalResult errorMap(Object data) {
        return new GlobalResult(501, "error", data);
    }

    public static GlobalResult errorTokenMsg(String msg) {
        return new GlobalResult(502, msg, null);
    }

    public static GlobalResult errorException(String msg) {
        return new GlobalResult(555, msg, null);
    }

    public GlobalResult() {

    }

    public GlobalResult(Integer status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public GlobalResult(Object data) {
        this.status = 200;
        this.msg = "OK";
        this.data = data;
    }

    public Boolean isOK() {
        return this.status == 200;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getOk() {
        return ok;
    }

    public void setOk(String ok) {
        this.ok = ok;
    }

}

HttpClientUtils

package top.weimumu.loginapi.common;

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;



import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author: create by calvin wong
 * @date:2019/12/10
 **/
public class HttpClientUtil {
    public static String doGet(String url, Map<String, String> param) {

        // 创建Httpclient对象
        CloseableHttpClient httpclient = HttpClients.createDefault();

        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // 创建uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();

            // 创建http GET请求
            HttpGet httpGet = new HttpGet(uri);

            // 执行请求
            response = httpclient.execute(httpGet);
            // 判断返回状态是否为200
            if (response.getStatusLine().getStatusCode() == 200) {
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultString;
    }

    public static String doGet(String url) {
        return doGet(url, null);
    }

    public static String doPost(String url, Map<String, String> param) {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);
            // 创建参数列表
            if (param != null) {
                List<NameValuePair> paramList = new ArrayList<>();
                for (String key : param.keySet()) {
                    paramList.add(new BasicNameValuePair(key, param.get(key)));
                }
                // 模拟表单
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
                httpPost.setEntity(entity);
            }
            // 执行http请求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    public static String doPost(String url) {
        return doPost(url, null);
    }

    public static String doPostJson(String url, String json) {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);
            // 创建请求内容
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            // 执行http请求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

}

WechatUtil

package top.weimumu.loginapi.common;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.codec.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;


import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: create by calvin wong
 * @date:2019/12/10
 **/
public class WechatUtil {
    public static JSONObject getSessionKeyOrOpenId(String code) {
        String requestUrl = "https://api.weixin.qq.com/sns/jscode2session";
        Map<String, String> requestUrlParam = new HashMap<>();
        // https://mp.weixin.qq.com/wxopen/devprofile?action=get_profile&token=164113089&lang=zh_CN
        //小程序appId
        requestUrlParam.put("appid", "wx62e0151eaf62eff7");
        //小程序secret
        requestUrlParam.put("secret", "c45170b3aa8dbc5cd45ab12c319298f8");
        //小程序端返回的code
        requestUrlParam.put("js_code", code);
        //默认参数
        requestUrlParam.put("grant_type", "authorization_code");
        //发送post请求读取调用微信接口获取openid用户唯一标识
        JSONObject jsonObject = JSON.parseObject(HttpClientUtil.doPost(requestUrl, requestUrlParam));
        return jsonObject;
    }

    public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
            // 如果密钥不足16位那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, "UTF-8");
                return JSON.parseObject(result);
            }
        } catch (Exception e) {
        }
        return null;
    }

}

4.3 controller

package top.weimumu.loginapi.controller;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.weimumu.loginapi.common.GlobalResult;
import top.weimumu.loginapi.common.WechatUtil;
import top.weimumu.loginapi.dao.UserMapper;
import top.weimumu.loginapi.entity.User;

import java.util.Date;
import java.util.UUID;

/**
 * @author: create by calvin wong
 * @date:2019/12/4
 **/
@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @PostMapping("/wxlogin")
    public GlobalResult wxLogin(
            @RequestParam(value = "code", required = false) String code,
            @RequestParam(value = "rawData", required = false) String rawData
    ) {
        JSONObject rawDataJson = JSON.parseObject(rawData);
        JSONObject SessionKeyOpenId = WechatUtil.getSessionKeyOrOpenId(code);
        String openid = SessionKeyOpenId.getString("openid");
        String sessionKey = SessionKeyOpenId.getString("session_key");
        User user = this.userMapper.selectById(openid);
        String skey = UUID.randomUUID().toString();
        if (user == null) {
            String nickName = rawDataJson.getString("nickName");
            String avatarUrl = rawDataJson.getString("avatarUrl");
            user = new User();
            user.setOpenId(openid);
            user.setSkey(skey);
            user.setCreateTime(new Date());
            user.setLastVisitTime(new Date());
            user.setSession_key(sessionKey);
            user.setAvatarUrl(avatarUrl);
            user.setNickName(nickName);
            this.userMapper.insert(user);
        }else {
            // 已存在,更新用户登录时间

            user.setLastVisitTime(new Date());
//            // 重新设置会话skey
            user.setSkey(skey);

            this.userMapper.updateById(user);
        }
        GlobalResult result = GlobalResult.build(200, null, skey);
        return result;
    }
}