63 KiB
uni-app微信小程序----“起名”
第一章 uni-app入门
1-1 uni-app简介
1-2 小程序项目介绍
宝宝在线起名一直是中国人的刚需,与其路边寻找大师,不如求教线上业务
目前只在淘宝搜索:发现营业额极高,那就让我们把大师的价格打下去
数理派
五格法:天格、人格、地格、外格和总格是笔画数来判定吉凶—判定标准为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水),可以明显看出:五行木旺、缺火。因此可以判断出这个宝宝的八字喜“火”,起名最好用五行属性为“火”的字。
计算方法:天干地支法
八字命盘从阴阳干支三合历取得。上排是天干,由五行「金水木火土」轮流排列。下排是地支,用十二生肖顺序排列。十二生肖可转换成五行。
六十甲子表:
甲子 | 乙丑 | 丙寅 | 丁卯 | 戊辰 | 已巳 | 庚午 | 辛未 | 壬申 | 癸酉 |
---|---|---|---|---|---|---|---|---|---|
甲戌 | 乙亥 | 丙子 | 丁丑 | 戊寅 | 已卯 | 庚辰 | 辛巳 | 壬午 | 癸未 |
甲申 | 乙酉 | 丙戌 | 丁亥 | 戊子 | 已丑 | 庚寅 | 辛卯 | 壬辰 | 癸巳 |
甲午 | 乙未 | 丙申 | 丁酉 | 戊戌 | 已亥 | 庚子 | 辛丑 | 壬寅 | 癸卯 |
甲辰 | 乙巳 | 丙午 | 丁未 | 戊申 | 已酉 | 庚戌 | 辛亥 | 壬子 | 癸丑 |
甲寅 | 乙卯 | 丙辰 | 丁巳 | 戊午 | 已未 | 庚申 | 辛酉 | 壬戌 | 癸亥 |
六十甲子纳音五行如下:
甲子乙丑海中金,甲午乙未沙中金
丙寅丁卯炉中火,丙申丁酉山下火
戊辰己巳大林木,戊戌己亥平地木
庚午辛未路旁土,庚子辛丑壁上土
壬申癸酉剑锋金,壬寅癸卯金泊金
甲戌乙亥山头火,甲辰乙巳覆灯火
丙子丁丑涧下水,丙午丁未天河水
戊寅己卯城头土,戊申己酉大驿土
庚辰辛巳白腊金,庚戌辛亥钗钏金
壬午癸未杨柳木,壬子癸丑桑柘木
甲申乙酉泉中水,甲寅乙卯大溪水
丙戌丁亥屋上土,丙辰丁巳沙中土
戊子己丑霹雳火,戊午己未天上火
庚寅辛卯松柏木,庚申辛酉石榴木
壬辰癸巳长流水,壬戌癸亥大海水
天干五行和地支五行对照表
天干:,甲-木,乙-木,丙-火,丁-火,戊-土,己-土,庚-金,辛-金,壬-水,癸-水 地支:子-水,丑-土,寅-木,卯-木,辰-土,巳-火,午-火,未-土,申-金,酉-金,戌-土,亥-水
八字根据 输入公历日期,查出你的出生年,月,日的干支四柱,从而判断八字属性:
年柱计算方法:
最简单的是查表地表法:
记年60年是一个循环,记月60月是一个循环,记日60日是一个循环,记时60时是一个循环。年月日时各用两个字表示,这就是人们通常说的“生辰八字”。
1、比如,记住几个特殊的年份如1984年为甲子年,类推1924,1864,1804,……均为甲子年。提到的壬戌是第59顺位,那么用甲子年份加上59减1得到的1982,1922,1862,1802……都是壬戌年!
2、比如,《辛亥革命》的辛亥年是1911年(48号干支),《戊戌变法》的戊戌年为35号干支,比辛亥年早13年,则“1911-13=1898”故《戊戌变法》是1898年。
3、比如,2008年:2008-3=2005,2005&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*2=1036800个
寓意派
根据小孩出生时间-地点,配合寓意良好的的诗词提字组合!
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设计规范
设计接口是一件容易的事,也是件困难的事。设计接口每个人都会,每个人都能设计,也由此产生了各种各样的理念的接口。工作这么多年,我也很有感悟。很多人会说,设计接口多么简单,只要命名好,然后联调通了,上线可以调用就行了。特别是非互联网行业的人,这里没有歧视的意思。因为互联网行业和传统行业太多不一致性决定了这种思想的产生。
通过不同的method(get\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操作,降低数据库性能。 不要使用uuid,md5,hash,字符串列作为主键。因为这种主键不能保证主键的值是顺序增长的,如果后来的主键值在已有主键值的中间段,那么这个主键插入的时候,会将所有主键值大于它的列都向后移。 最好选择能保证值的顺序为顺序增长的列为主键。并且数据不能重复,建议用mysql自增id建立主键 面试问题1: 要在哪些列上建立索引? 在select,delete,update的where从句中的列 包含在order by,group by,distinct字段中的列 多表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表示字符数而不是字节数 避免使用text,blog来存储字段,这种类型只能使用前缀索引,如果非要使用,建议将这种数据分离到单独的拓展表中 避免使用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(不包含union,group by,order by,limit)才能转换为关联查询 避免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安全模块,需要临时关闭该安全模块,或者添加目录到白名单 临时关闭selinux:su -c "setenforce 0" 重新开启selinux:su -c "setenforce 1" 添加selinux规则,将要挂载的目录添加到白名单: 示例:chcon -Rt svirt_sandbox_file_t /data/mysql01(我启动挂载的路径)
error:用navicat连接如果报错
我们在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
效果展示:
微信小程序登录步骤
第一步:小程序通过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;
}
}