Browse Source

Merge branch 'master' of http://git.shengws.com/zhangbj/scrm-go

庄逸洲 5 years ago
parent
commit
8608862a13

+ 424 - 4
controllers/article/article_controller.go View File

@@ -24,6 +24,17 @@ func ArticleRouters() {
24 24
 	beego.Router("/api/acticle/delete",&ArticleManage{},"Delete:DeleteCategorys")
25 25
 	beego.Router("/api/acticle/addvido",&ArticleManage{},"Post:Addvido")
26 26
 	beego.Router("/api/acticle/savedraft",&ArticleManage{},"Post:SaveDraft")
27
+	beego.Router("/api/acticle/prviewArticle",&ArticleManage{},"Post:PrviewArticle")
28
+	beego.Router("/api/acticle/getarticlePreview",&ArticleManage{},"Get:GetArtilcePreview")
29
+	beego.Router("/api/acticle/save",&ArticleManage{},"Post:AddDraft")
30
+	beego.Router("/api/acticle/getEditArticle",&ArticleManage{},"Get:GetArticleInfo")
31
+	beego.Router("/api/article/Delete",&ArticleManage{},"Delete:DeleteArticle")
32
+	beego.Router("/api/acticle/getMenus",&ArticleManage{},"Get:GetMenus")
33
+	beego.Router("/api/acticle/updateArticlesInfo",&ArticleManage{},"Post:UpdataArticleInfo")
34
+	beego.Router("/api/acticle/previewEditArticle",&ArticleManage{},"Post:PreviewEditArticle")
35
+	beego.Router("/api/acticle/getPreviewInfo",&ArticleManage{},"Post:GetPreviewInfo")
36
+	beego.Router("/api/acticle/getAllComment",&ArticleManage{},"Get:GetAllComment")
37
+	beego.Router("/api/acticle/getArticleCommentDetail",&ArticleManage{},"Get:GetArticleCommentDetail")
27 38
 }
28 39
 
29 40
 type ArticleManage struct {
@@ -108,10 +119,13 @@ func (this *ArticleManage) GetArticleType(){
108 119
 
109 120
 func (this *ArticleManage) GetAllArticles()  {
110 121
 	page, _ := this.GetInt64("page", 1)
122
+	fmt.Println("页面",page)
111 123
 	limit, _ := this.GetInt64("limit", 10)
112
-	searchKey := this.GetString("search", "")
113
-	classId,_ := this.GetInt64("classid",0)
114
-    fmt.Println("页面",page,"限制",limit,"关键字",searchKey,"分类号",classId)
124
+	fmt.Println("限制",limit)
125
+	searchKey := this.GetString("keyword", "")
126
+	classId,_ := this.GetInt64("id",0)
127
+	status, _ := this.GetInt64("status")
128
+	fmt.Println("页面",page,"限制",limit,"关键字",searchKey,"分类号",classId,"状态值:",status)
115 129
 
116 130
 	if page <= 0 {
117 131
 		page = 1
@@ -119,10 +133,53 @@ func (this *ArticleManage) GetAllArticles()  {
119 133
 	if limit <= 0 {
120 134
 		limit = 10
121 135
 	}
136
+
137
+
122 138
 	adminUserInfo := this.GetAdminUserInfo()
123 139
 	userOrgID := adminUserInfo.CurrentOrgId
124 140
 
125
-	articles, total, err := article_service.FindAllArticle(userOrgID, page,limit, searchKey, classId)
141
+	if(status == 1){
142
+		articles, total, err := article_service.GetPublished(userOrgID, page, limit, searchKey)
143
+		category, err := article_service.FindCategoryList(userOrgID)
144
+		if err !=nil{
145
+			this.ServeFailJsonSend(enums.ErrorCodeDataException, "获取文章分类列表失败")
146
+			return
147
+		}
148
+		this.ServeSuccessJSON(map[string]interface{}{
149
+			"articles":articles,
150
+			"total":total,
151
+			"category":category,
152
+		})
153
+	}
154
+	if(status == 2){
155
+		articles, total, err := article_service.GetDraftbox(userOrgID, page, limit, searchKey)
156
+		category, err := article_service.FindCategoryList(userOrgID)
157
+		if err !=nil{
158
+			this.ServeFailJsonSend(enums.ErrorCodeDataException, "获取文章分类列表失败")
159
+			return
160
+		}
161
+		this.ServeSuccessJSON(map[string]interface{}{
162
+			"articles":articles,
163
+			"total":total,
164
+			"category":category,
165
+		})
166
+	}
167
+
168
+	if(status == 3){
169
+		articles, total, err := article_service.GetNoPass(userOrgID, page, limit, searchKey)
170
+		category, err := article_service.FindCategoryList(userOrgID)
171
+		if err !=nil{
172
+			this.ServeFailJsonSend(enums.ErrorCodeDataException, "获取文章分类列表失败")
173
+			return
174
+		}
175
+		this.ServeSuccessJSON(map[string]interface{}{
176
+			"articles":articles,
177
+			"total":total,
178
+			"category":category,
179
+		})
180
+	}
181
+
182
+	articles, total, err := article_service.FindAllArticle(userOrgID, page,limit, searchKey,classId)
126 183
 	fmt.Println("文章内容是是么",articles)
127 184
 	fmt.Println("total",total)
128 185
 	fmt.Println("err",err)
@@ -453,4 +510,367 @@ func (this *ArticleManage) SaveDraft()  {
453 510
 	this.ServeSuccessJSON(map[string]interface{}{
454 511
 		"savedraft":articles,
455 512
 	})
513
+}
514
+
515
+func (this *ArticleManage) PrviewArticle()  {
516
+	adminUserInfo := this.GetAdminUserInfo()
517
+	userOrgID := adminUserInfo.CurrentOrgId
518
+	dataBody := make(map[string]interface{}, 0)
519
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &dataBody)
520
+	fmt.Println("视频发布是什么呢",err)
521
+
522
+	if err != nil {
523
+		utils.ErrorLog(err.Error())
524
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
525
+		return
526
+	}
527
+
528
+	actname := dataBody["act_name"].(string)
529
+	if len(actname) == 0 {
530
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "文章标题不能为空")
531
+		return
532
+	}
533
+
534
+	actcontent := dataBody["act_content"].(string)
535
+	if len(actcontent) == 0 {
536
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "文章内容不能为空")
537
+		return
538
+	}
539
+
540
+	orglogo := dataBody["org_logo"].(string)
541
+	if len(orglogo) == 0 {
542
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "封面图片不能为空")
543
+		return
544
+	}
545
+
546
+	acttype := int64(dataBody["act_type"].(float64))
547
+	if  acttype <= 0 {
548
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "分类号不能为空")
549
+		return
550
+	}
551
+
552
+	timenow := time.Now().Unix()
553
+	fmt.Println("姓名:",actname,"文章内容",actcontent,"图片",orglogo,"文章类型",acttype,userOrgID)
554
+	articles := models.Articles{
555
+		Title: actname,
556
+		Content: actcontent,
557
+		Imgs: orglogo,
558
+		ClassId: acttype,
559
+		UserOrgId:userOrgID,
560
+		Ctime:timenow,
561
+		Status:1,
562
+		Type:1,
563
+		ArticleStatus:3,
564
+	}
565
+	 err = article_service.AddPrviewArticle(articles)
566
+	if err !=nil{
567
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "添加预览失败")
568
+		return
569
+	}
570
+	this.ServeSuccessJSON(map[string]interface{}{
571
+		"art":articles,
572
+	})
573
+	fmt.Println("art是谁",articles)
574
+}
575
+
576
+func (this *ArticleManage) GetArtilcePreview()  {
577
+	adminUserInfo := this.GetAdminUserInfo()
578
+	userOrgID := adminUserInfo.CurrentOrgId
579
+	articles, err := article_service.GetArtilcePreview(userOrgID)
580
+	if err !=nil{
581
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "获取预览失败")
582
+		return
583
+	}
584
+	fmt.Println("文章内容",articles)
585
+	this.ServeSuccessJSON(map[string]interface{}{
586
+		"articles":articles,
587
+	})
588
+
589
+}
590
+
591
+func (this *ArticleManage) AddDraft() {
592
+	adminUserInfo := this.GetAdminUserInfo()
593
+	userOrgID := adminUserInfo.CurrentOrgId
594
+	dataBody := make(map[string]interface{}, 0)
595
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &dataBody)
596
+	fmt.Println("视频发布是什么呢", err)
597
+
598
+	if err != nil {
599
+		utils.ErrorLog(err.Error())
600
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
601
+		return
602
+	}
603
+	actname := dataBody["act_name"].(string)
604
+	if len(actname) == 0 {
605
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "文章标题不能为空")
606
+		return
607
+	}
608
+
609
+	actcontent := dataBody["act_content"].(string)
610
+	if len(actcontent) == 0 {
611
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "文章内容不能为空")
612
+		return
613
+	}
614
+
615
+	orglogo := dataBody["org_logo"].(string)
616
+	if len(orglogo) == 0 {
617
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "封面图片不能为空")
618
+		return
619
+	}
620
+
621
+	acttype := int64(dataBody["act_type"].(float64))
622
+	if acttype <= 0 {
623
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "分类号不能为空")
624
+		return
625
+	}
626
+
627
+	timenow := time.Now().Unix()
628
+	fmt.Println("姓名:", actname, "文章内容", actcontent, "图片", orglogo, "文章类型", acttype, userOrgID)
629
+	articles := models.Articles{
630
+		Title:         actname,
631
+		Content:       actcontent,
632
+		Imgs:          orglogo,
633
+		ClassId:       acttype,
634
+		UserOrgId:     userOrgID,
635
+		Ctime:         timenow,
636
+		Status:        1,
637
+		Type:          1,
638
+		ArticleStatus: 2,
639
+	}
640
+	err = article_service.AddDraft(articles)
641
+	if err !=nil{
642
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "保存草稿失败")
643
+		return
644
+	}
645
+	fmt.Println("文章内容",articles)
646
+	this.ServeSuccessJSON(map[string]interface{}{
647
+		"articles":articles,
648
+	})
649
+}
650
+
651
+func (this *ArticleManage) GetArticleInfo()  {
652
+	id, _ := this.GetInt64("id")
653
+	fmt.Println("id是啥",id)
654
+	adminUserInfo := this.GetAdminUserInfo()
655
+	userOrgID := adminUserInfo.CurrentOrgId
656
+	articles, err := article_service.GetArticleInfo(userOrgID, id)
657
+	if err !=nil{
658
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "查询文章信息失败")
659
+		return
660
+	}
661
+	fmt.Println("内容是啥什么",articles)
662
+	this.ServeSuccessJSON(map[string]interface{}{
663
+		"articles":articles,
664
+	})
665
+}
666
+
667
+func (this *ArticleManage) DeleteArticle()  {
668
+	adminUserInfo := this.GetAdminUserInfo()
669
+	userOrgID := adminUserInfo.CurrentOrgId
670
+
671
+	dataBody := make(map[string]interface{}, 0)
672
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &dataBody)
673
+	if err != nil {
674
+		utils.ErrorLog(err.Error())
675
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
676
+		return
677
+	}
678
+
679
+	idsInters := dataBody["ids"].([]interface{})
680
+	if len(idsInters) == 0 {
681
+		if err != nil {
682
+			this.ServeFailJsonSend(enums.ErrorCodeDBDelete, "删除会员失败:(没有选择会员)")
683
+			return
684
+		}
685
+	}
686
+
687
+	ids := make([]int64, 0)
688
+	for _, idsInter := range idsInters {
689
+		id := int64(idsInter.(float64))
690
+		ids = append(ids, id)
691
+	}
692
+
693
+	err = article_service.DeleteArticle(ids, userOrgID)
694
+	if err !=nil{
695
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "删除失败")
696
+		return
697
+	}
698
+	returnData := make(map[string]interface{}, 0)
699
+	returnData["msg"] = "ok"
700
+	this.ServeSuccessJSON(returnData)
701
+	return
702
+}
703
+
704
+func (this *ArticleManage) GetMenus(){
705
+	adminUserInfo := this.GetAdminUserInfo()
706
+	userOrgID := adminUserInfo.CurrentOrgId
707
+	categorys, err := article_service.GetMenus(userOrgID)
708
+	if err !=nil{
709
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "查询失败")
710
+		return
711
+	}
712
+	this.ServeSuccessJSON(map[string]interface{}{
713
+		"categorys":categorys,
714
+	})
715
+}
716
+
717
+func (this * ArticleManage) UpdataArticleInfo(){
718
+	adminUserInfo := this.GetAdminUserInfo()
719
+	userOrgID := adminUserInfo.CurrentOrgId
720
+	id, _ := this.GetInt64("id")
721
+	fmt.Println("id是多少",id)
722
+	dataBody := make(map[string]interface{}, 0)
723
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &dataBody)
724
+	fmt.Println("视频发布是什么呢",err)
725
+
726
+	if err != nil {
727
+		utils.ErrorLog(err.Error())
728
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
729
+		return
730
+	}
731
+
732
+	title := dataBody["title"].(string)
733
+	if len(title) == 0 {
734
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "文章标题不能为空")
735
+		return
736
+	}
737
+	fmt.Println("title",title)
738
+	content := dataBody["content"].(string)
739
+	if len(content) == 0 {
740
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "内容不能为空")
741
+		return
742
+	}
743
+	fmt.Println("content",content)
744
+	images := dataBody["imgs"].(string)
745
+	fmt.Println("images",images)
746
+	classid :=int64( dataBody["class_id"].(float64))
747
+	if classid <=0 {
748
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "分类号不能为空")
749
+		return
750
+	}
751
+	//classid, _ := strconv.ParseInt(class_id, 10, 64)
752
+    fmt.Println("分类号",classid)
753
+	fmt.Println("标题",title,"内容",content,"图片",images,"分类号",classid,"机构ID",userOrgID)
754
+
755
+	articles := models.Articles{
756
+		Title:   title,
757
+		Content: content,
758
+		Imgs:    images,
759
+		ClassId: classid,
760
+		Status:  1,
761
+		Mtime:   time.Now().Unix(),
762
+	}
763
+	 fmt.Println("为什么",articles)
764
+	 article_service.UpdataArticleInfo(&articles,userOrgID, id)
765
+	this.ServeSuccessJSON(map[string]interface{}{
766
+		"articls":articles,
767
+	})
768
+}
769
+
770
+func (this * ArticleManage) PreviewEditArticle()  {
771
+	adminUserInfo := this.GetAdminUserInfo()
772
+	userOrgID := adminUserInfo.CurrentOrgId
773
+	id, _ := this.GetInt64("id")
774
+	fmt.Println("id是多少",id)
775
+	dataBody := make(map[string]interface{}, 0)
776
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &dataBody)
777
+	fmt.Println("视频发布是什么呢",err)
778
+
779
+	if err != nil {
780
+		utils.ErrorLog(err.Error())
781
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
782
+		return
783
+	}
784
+
785
+	title := dataBody["title"].(string)
786
+	if len(title) == 0 {
787
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "文章标题不能为空")
788
+		return
789
+	}
790
+	fmt.Println("title",title)
791
+	content := dataBody["content"].(string)
792
+	if len(content) == 0 {
793
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "内容不能为空")
794
+		return
795
+	}
796
+	fmt.Println("content",content)
797
+	images := dataBody["imgs"].(string)
798
+	fmt.Println("images",images)
799
+	classid :=int64( dataBody["class_id"].(float64))
800
+	if classid <=0 {
801
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "分类号不能为空")
802
+		return
803
+	}
804
+	//classid, _ := strconv.ParseInt(class_id, 10, 64)
805
+	fmt.Println("分类号",classid)
806
+	fmt.Println("标题",title,"内容",content,"图片",images,"分类号",classid,"机构ID",userOrgID)
807
+
808
+	articles := models.Articles{
809
+		Title:   title,
810
+		Content: content,
811
+		Imgs:    images,
812
+		ClassId: classid,
813
+		Status:  1,
814
+		Mtime:   time.Now().Unix(),
815
+		ArticleStatus:3,
816
+	}
817
+
818
+	article_service.PreviewEditArticle(articles,userOrgID,id)
819
+	this.ServeSuccessJSON(map[string]interface{}{
820
+		"articls":articles,
821
+	})
822
+}
823
+
824
+func (this * ArticleManage) GetPreviewInfo()  {
825
+	adminUserInfo := this.GetAdminUserInfo()
826
+	userOrgID := adminUserInfo.CurrentOrgId
827
+	articles, err := article_service.GetPreviewInfo(userOrgID)
828
+	if err !=nil{
829
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "获取预览失败")
830
+		return
831
+	}
832
+	fmt.Println("文章内容",articles)
833
+	this.ServeSuccessJSON(map[string]interface{}{
834
+		"articles":articles,
835
+	})
836
+}
837
+
838
+func  (this * ArticleManage) GetAllComment()  {
839
+	page, _ := this.GetInt64("page", 1)
840
+	fmt.Println("页面",page)
841
+	limit, _ := this.GetInt64("limit", 10)
842
+	fmt.Println("限制",limit)
843
+	if page <= 0 {
844
+		page = 1
845
+	}
846
+	if limit <= 0 {
847
+		limit = 10
848
+	}
849
+	adminUserInfo := this.GetAdminUserInfo()
850
+	userOrgID := adminUserInfo.CurrentOrgId
851
+	articles, total, err := article_service.GetAllComment(page, limit, userOrgID)
852
+	fmt.Println("文章内容",articles)
853
+	if err !=nil{
854
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "获取预览失败")
855
+		return
856
+	}
857
+	this.ServeSuccessJSON(map[string]interface{}{
858
+		"total":total,
859
+		"articles":articles,
860
+	})
861
+}
862
+
863
+func (this * ArticleManage) GetArticleCommentDetail(){
864
+	id, _ := this.GetInt64("id")
865
+	fmt.Println("id是多少",id)
866
+	adminUserInfo := this.GetAdminUserInfo()
867
+	userOrgID := adminUserInfo.CurrentOrgId
868
+	articles, err := article_service.GetArticleCommentDetail(id, userOrgID)
869
+	if err !=nil{
870
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "获取预览失败")
871
+		return
872
+	}
873
+	this.ServeSuccessJSON(map[string]interface{}{
874
+		"articles":articles,
875
+	})
456 876
 }

+ 5 - 0
controllers/kefu/router_collector.go View File

@@ -0,0 +1,5 @@
1
+package kefu
2
+
3
+func RegisterRouters() {
4
+	TencentUsersigApiRegistRouters()
5
+}

+ 64 - 0
controllers/kefu/tencent_usersig_api_controller.go View File

@@ -0,0 +1,64 @@
1
+package kefu
2
+
3
+import (
4
+	base_ctl "SCRM/controllers"
5
+	"SCRM/enums"
6
+	"SCRM/service/tencentim_service"
7
+	"SCRM/utils"
8
+	"strconv"
9
+
10
+	"github.com/astaxie/beego"
11
+)
12
+
13
+type TencentUsersigApiController struct {
14
+	base_ctl.BaseAuthAPIController
15
+}
16
+
17
+func TencentUsersigApiRegistRouters() {
18
+	beego.Router("/api/tencent/usersig", &TencentUsersigApiController{}, "get:GetUsersig")
19
+
20
+}
21
+
22
+//GetUsersig 签名
23
+func (c *TencentUsersigApiController) GetUsersig() {
24
+
25
+	adminUserInfo := c.GetAdminUserInfo()
26
+
27
+	adminId := adminUserInfo.AdminUser.Id
28
+	Indentifier := "Org_" + strconv.FormatInt(adminUserInfo.CurrentOrgId, 10)
29
+	appIDAt3rd := Indentifier
30
+	sig, err := tencentim_service.CreateUserSig(Indentifier, appIDAt3rd)
31
+	if err != nil {
32
+		utils.ErrorLog("usersig err : %v", err)
33
+		c.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeParamWrong)
34
+		return
35
+	}
36
+
37
+
38
+	orgInfo := adminUserInfo.Orgs[adminUserInfo.CurrentOrgId]
39
+	IdentifierNick := ""
40
+	HeadURL := "http://jk.kuyicloud.com/static/images/ico_gjh.png"
41
+	if orgInfo != nil {
42
+		if len(orgInfo.OrgName) > 0 {
43
+			IdentifierNick = orgInfo.OrgName
44
+		}
45
+		if len(orgInfo.OrgLogo) > 0 {
46
+			HeadURL = orgInfo.OrgLogo
47
+		}
48
+	}
49
+
50
+	returnData := map[string]interface{}{
51
+		"sig":            sig,
52
+		"adminId":        adminId,
53
+		"Indentifier":    Indentifier,
54
+		"appIDAt3rd":     appIDAt3rd,
55
+		"IdentifierNick": IdentifierNick,
56
+		"HeadURL":        HeadURL,
57
+		"sdkAppID":       tencentim_service.ThisSDKAppId,
58
+		"accountType":    tencentim_service.ThisAccType,
59
+		"selToAdmin":     "AdminKeFu",
60
+	}
61
+
62
+	c.ServeSuccessJSON(returnData)
63
+	return
64
+}

+ 5 - 0
controllers/staff/router_collector.go View File

@@ -0,0 +1,5 @@
1
+package staff
2
+
3
+func RegisterRouters() {
4
+	staffRouters()
5
+}

+ 150 - 0
controllers/staff/staff_controller.go View File

@@ -0,0 +1,150 @@
1
+package staff
2
+
3
+import (
4
+	"github.com/astaxie/beego"
5
+   "SCRM/controllers"
6
+	"encoding/json"
7
+	"fmt"
8
+	"SCRM/utils"
9
+	"SCRM/enums"
10
+	"SCRM/models"
11
+	"SCRM/service/staff_service"
12
+	"time"
13
+)
14
+func staffRouters() {
15
+	 
16
+	beego.Router("/api/staff/addStaffInfo",&StaffManage{},"Post:AddStaffInfo")
17
+	beego.Router("/api/staff/getAllStaffInfo",&StaffManage{},"Get:GetAllStaffInfo")
18
+}
19
+
20
+type StaffManage struct {
21
+
22
+	controllers.BaseAuthAPIController
23
+}
24
+
25
+func (this *StaffManage) AddStaffInfo()  {
26
+	adminUserInfo := this.GetAdminUserInfo()
27
+	userOrgID := int64(adminUserInfo.CurrentOrgId)
28
+	fmt.Println("机构ID",userOrgID)
29
+	dataBody := make(map[string]interface{}, 0)
30
+
31
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &dataBody)
32
+	fmt.Println("视频发布是什么呢",err)
33
+
34
+	if err != nil {
35
+		utils.ErrorLog(err.Error())
36
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
37
+		return
38
+	}
39
+     fmt.Println("hhhhh")
40
+	staffname := dataBody["name"].(string)
41
+	if len(staffname) == 0 {
42
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "姓名不能为空")
43
+		return
44
+	}
45
+
46
+	phone := dataBody["phone"].(string)
47
+	if len(phone) == 0 {
48
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "手机不能为空")
49
+		return
50
+	}
51
+
52
+	gender := int64(dataBody["gender"].(float64))
53
+	if gender <= 0{
54
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "性别不能为空")
55
+		return
56
+	}
57
+
58
+	birthday, _ := dataBody["birthday"].(string)
59
+	if len(birthday) == 0 {
60
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "生日格式不正确")
61
+		return
62
+	}
63
+
64
+	timeLayout := "2006-01-02 15:04:05"
65
+	theTime, err := utils.ParseTimeStringToTime(timeLayout, birthday+" 00:00:00")
66
+	if err != nil {
67
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "生日格式不正确")
68
+		return
69
+	}
70
+
71
+	var staffbirthday = theTime.Unix()
72
+	fmt.Println("生日",staffbirthday)
73
+
74
+	userType := int64(dataBody["user_type"].(float64))
75
+
76
+	if userType <= 0 {
77
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "职称类型不正确")
78
+		return
79
+	}
80
+	userTitle :=int64(dataBody["user_title"].(float64))
81
+	if userTitle <= 0 {
82
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "职称名称不正确")
83
+		return
84
+	}
85
+	dochead := dataBody["dochead"].(string)
86
+	if len(dochead) == 0 {
87
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "头像不正确")
88
+		return
89
+	}
90
+	content := dataBody["content"].(string)
91
+	if len(content) == 0 {
92
+		this.ServeFailJsonSend(enums.ErrorCodeParamWrong, "头像不正确")
93
+		return
94
+	}
95
+
96
+	fmt.Println("姓名",staffname,"性别" ,gender,"生日",staffbirthday,"职称类型",userType,"职称名称",userTitle,"头像",dochead,"内容",content)
97
+
98
+	StaffInfo := models.SgjUserStaffInfo{
99
+		Name:      staffname,
100
+		Phone:     phone,
101
+		Birthday:  staffbirthday,
102
+		Gender:gender,
103
+		Content:content,
104
+		UserType:  userType,
105
+		UserTitle: userType,
106
+		Dochead:   dochead,
107
+		Status: 1,
108
+		UserOrgId:userOrgID,
109
+		Ctime:time.Now().Unix(),
110
+	}
111
+
112
+	err = staff_service.AddStaffInfo(StaffInfo)
113
+	if err !=nil{
114
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "插入文章失败")
115
+		return
116
+	}
117
+	this.ServeSuccessJSON(map[string]interface{}{
118
+		"staffInfo":StaffInfo,
119
+	})
120
+	return
121
+}
122
+
123
+func (this *StaffManage) GetAllStaffInfo()  {
124
+	keyword := this.GetString("keyword")
125
+	page, _ := this.GetInt64("page", 1)
126
+	fmt.Println("页面",page)
127
+	limit, _ := this.GetInt64("limit", 10)
128
+
129
+	if page <= 0 {
130
+		page = 1
131
+	}
132
+	if limit <= 0 {
133
+		limit = 10
134
+	}
135
+	fmt.Println("限制",limit)
136
+	fmt.Println("关键字",keyword,"limit",limit,"page",page)
137
+
138
+	adminUserInfo := this.GetAdminUserInfo()
139
+	userOrgID := int64(adminUserInfo.CurrentOrgId)
140
+	userStaffInfo, total, err := staff_service.GetAllStaffInfo(userOrgID, page, limit, keyword)
141
+    fmt.Println("内容",userStaffInfo,"total",total,"err",err)
142
+	if err !=nil{
143
+		this.ServeFailJsonSend(enums.ErrorCodeDataException, "获取文章分类列表失败")
144
+		return
145
+	}
146
+	this.ServeSuccessJSON(map[string]interface{}{
147
+		"userStaffInfo":userStaffInfo,
148
+		"total":total,
149
+	})
150
+}

+ 1 - 1
main.go View File

@@ -19,7 +19,7 @@ func main() {
19 19
 	//微信开放平台
20 20
 	go func() {
21 21
 		// jobcron.RequestComponentAccessToken()
22
-		jobcron.RequestMpWechatAccessToken()
22
+		// jobcron.RequestMpWechatAccessToken()
23 23
 		// jobcron.ReqJsapiTicket()
24 24
 	}()
25 25
 	jobcron.StartOpenWechatCron()

+ 1 - 0
models/article_models.go View File

@@ -26,6 +26,7 @@ type Articles struct {
26 26
 	Content       string    `gorm:"column:content" json:"content"`
27 27
 	Type          int       `gorm:"column:type" json:"type"`
28 28
 	Num           int       `gorm:"column:num" json:"num"`
29
+	Realreadnum   int64       `gorm:"column:real_read_num" json:"real_read_num"`
29 30
 	Mtime         int64     `gorm:"column:mtime" json:"mtime"`
30 31
 	Ctime         int64     `gorm:"column:ctime" json:"ctime"`
31 32
 	ClassId       int64     `gorm:"column:class_id" json:"class_id"`

+ 20 - 0
models/user_models.go View File

@@ -35,3 +35,23 @@ type User struct {
35 35
 func (User) TableName() string {
36 36
 	return "sgj_user_user"
37 37
 }
38
+
39
+type SgjUserStaffInfo struct {
40
+	ID int64 `gorm:"column:id" json:"id" form:"id"`
41
+	Name string `gorm:"column:name" json:"name" form:"name"`
42
+	Phone string `gorm:"column:phone" json:"phone" form:"phone"`
43
+	Birthday int64 `gorm:"column:birthday" json:"birthday" form:"birthday"`
44
+	Gender int64 `gorm:"column:gender" json:"gender" form:"gender"`
45
+	UserType int64 `gorm:"column:user_type" json:"user_type" form:"user_type"`
46
+	UserTitle int64 `gorm:"column:user_title" json:"user_title" form:"user_title"`
47
+	Dochead string `gorm:"column:dochead" json:"dochead" form:"dochead"`
48
+	Content string `gorm:"column:content" json:"content" form:"content"`
49
+	Ctime int64 `gorm:"column:ctime" json:"ctime" form:"ctime"`
50
+	Mtime int64 `gorm:"column:mtime" json:"mtime" form:"mtime"`
51
+	Status int64 `gorm:"column:status" json:"status" form:"status"`
52
+	UserOrgId int64 `gorm:"column:user_org_id" json:"user_org_id" form:"user_org_id"`
53
+}
54
+
55
+func (SgjUserStaffInfo) TableName() string{
56
+	return "sgj_user_staff_info"
57
+}

+ 4 - 0
routers/router.go View File

@@ -4,12 +4,14 @@ import (
4 4
 	"SCRM/controllers/admin_user"
5 5
 	"SCRM/controllers/article"
6 6
 	"SCRM/controllers/global"
7
+	"SCRM/controllers/kefu"
7 8
 	"SCRM/controllers/login"
8 9
 	"SCRM/controllers/marketing_tool"
9 10
 	"SCRM/controllers/members"
10 11
 	"SCRM/controllers/mpwechat"
11 12
 	"SCRM/controllers/role"
12 13
 	"SCRM/controllers/sms"
14
+	"SCRM/controllers/staff"
13 15
 
14 16
 	"github.com/astaxie/beego"
15 17
 	"github.com/astaxie/beego/plugins/cors"
@@ -33,4 +35,6 @@ func init() {
33 35
 	article.RegisterRouters()
34 36
 	mpwechat.RegisterRouters()
35 37
 	sms.RegisterRouters()
38
+	staff.RegisterRouters()
39
+	kefu.RegisterRouters()
36 40
 }

+ 147 - 5
service/article_service/article_category_service.go View File

@@ -25,15 +25,15 @@ func AddAritcle(articles models.Articles) error {
25 25
 	return  err
26 26
 }
27 27
 
28
-func FindAllArticle(orgID int64, page int64,limit int64, searchKey string, classId int64) (articles []*models.Articles,total int64, err error) {
28
+func FindAllArticle(orgID int64, page int64,limit int64, searchKey string,classId int64) (articles []*models.Articles,total int64, err error) {
29 29
     fmt.Println("机构ID",orgID,"搜搜索",searchKey)
30 30
 	db := service.PatientReadDB().Table("sgj_patient_articles as a ").Where("a.status=1")
31 31
 	if(orgID>0){
32 32
 	  db = db.Where("a.user_org_id=?" ,orgID)
33 33
 	}
34 34
 
35
-	if(classId>0){
36
-	  db = db.Joins("JOIN sgj_patient_articles_menu as s ON s.id = a.class_id AND s.status=1 AND user_org_id=?",orgID)
35
+	if(classId > 0){
36
+		db = db.Where("class_id = ?",classId)
37 37
 	}
38 38
 
39 39
 	if len(searchKey) > 0 {
@@ -45,7 +45,7 @@ func FindAllArticle(orgID int64, page int64,limit int64, searchKey string, class
45 45
 		err = db.Count(&total).Order("a.ctime desc").Offset(offset).Limit(limit).
46 46
 		Preload("Comments", func(db *gorm.DB) *gorm.DB {
47 47
 			return  db.Where(" status = ?",1)
48
-		}).
48
+		}).Where("article_status <> ?",3).
49 49
 		Select("a.id, a.title, a.summary, a.content, a.type, a.num, a.mtime, a.real_read_num, a.ctime, a.class_id, a.author, a.status, a.reason,a.star_num, a.comment_num,a.user_org_id, a.article_status, a.imgs, a.video_url, a.source, a.category_id, a.ttid, a.ttype, a.toid").Find(&articles).Error
50 50
 		fmt.Println("err是啥呢?",err)
51 51
 		 	if err != nil {
@@ -100,4 +100,146 @@ func DeleteCategorys(orgID int64,ids[] int64)(err error){
100 100
 func AddVido(articles models.Articles) error  {
101 101
 	err := service.PatientWriteDB().Create(&articles).Error
102 102
 	return  err
103
-}
103
+}
104
+
105
+func AddPrviewArticle(articles models.Articles)(err error) {
106
+	err = service.PatientWriteDB().Create(&articles).Error
107
+	return err
108
+}
109
+
110
+func GetArtilcePreview(OrgID int64)(models.Articles,error) {
111
+	article := models.Articles{}
112
+	err := service.PatientReadDB().Where("user_org_id = ? AND article_status = ? AND type = ?", OrgID, 3, 1).Last(&article).Error
113
+	return article, err
114
+}
115
+
116
+func AddDraft(articles models.Articles) error {
117
+	err := service.PatientReadDB().Create(&articles).Error
118
+	return  err
119
+}
120
+
121
+func GetPublished(orgID int64,page int64,limit int64, searchKey string)(articles []*models.Articles,total int64, err error) {
122
+	db := service.PatientReadDB().Table("sgj_patient_articles as a ").Where("a.status=1")
123
+	if (orgID > 0) {
124
+		db = db.Where("a.user_org_id=? AND article_status = ?", orgID,1)
125
+	}
126
+	if len(searchKey) > 0 {
127
+		searchKey = "%" + searchKey + "%"
128
+		db = db.Where("a.title LIKE ?", searchKey)
129
+	}
130
+	offset := (page - 1) * limit
131
+	err = db.Count(&total).Order("a.ctime desc").Offset(offset).Limit(limit).
132
+		Preload("Comments", func(db *gorm.DB) *gorm.DB {
133
+		return  db.Where(" status = ?",1)
134
+	}).Where("status = ?",1).
135
+		Select("a.id, a.title, a.summary, a.content, a.type, a.num, a.mtime, a.real_read_num, a.ctime, a.class_id, a.author, a.status, a.reason,a.star_num, a.comment_num,a.user_org_id, a.article_status, a.imgs, a.video_url, a.source, a.category_id, a.ttid, a.ttype, a.toid").Find(&articles).Error
136
+	fmt.Println("err是啥呢?",err)
137
+	if err != nil {
138
+		return
139
+	}
140
+	return
141
+}
142
+
143
+func GetDraftbox(orgID int64,page int64,limit int64, searchKey string)(articles []*models.Articles,total int64, err error) {
144
+	db := service.PatientReadDB().Table("sgj_patient_articles as a ").Where("a.status=1")
145
+	if (orgID > 0) {
146
+		db = db.Where("a.user_org_id=? AND article_status = ?", orgID,2)
147
+	}
148
+	if len(searchKey) > 0 {
149
+		searchKey = "%" + searchKey + "%"
150
+		db = db.Where("a.title LIKE ?", searchKey)
151
+	}
152
+	offset := (page - 1) * limit
153
+	err = db.Count(&total).Order("a.ctime desc").Offset(offset).Limit(limit).
154
+		Preload("Comments", func(db *gorm.DB) *gorm.DB {
155
+		return  db.Where(" status = ?",1)
156
+	}).Where("article_status = ? AND status = ?",2,1).
157
+		Select("a.id, a.title, a.summary, a.content, a.type, a.num, a.mtime, a.real_read_num, a.ctime, a.class_id, a.author, a.status, a.reason,a.star_num, a.comment_num,a.user_org_id, a.article_status, a.imgs, a.video_url, a.source, a.category_id, a.ttid, a.ttype, a.toid").Find(&articles).Error
158
+	fmt.Println("err是啥呢?",err)
159
+	if err != nil {
160
+		return
161
+	}
162
+	return
163
+}
164
+
165
+func GetNoPass(orgID int64,page int64,limit int64, searchKey string)(articles []*models.Articles,total int64, err error)  {
166
+	db := service.PatientReadDB().Table("sgj_patient_articles as a ").Where("a.status=1")
167
+	if (orgID > 0) {
168
+		db = db.Where("a.user_org_id=? AND article_status = ?", orgID,4)
169
+	}
170
+	if len(searchKey) > 0 {
171
+		searchKey = "%" + searchKey + "%"
172
+		db = db.Where("a.title LIKE ?", searchKey)
173
+	}
174
+	offset := (page - 1) * limit
175
+	err = db.Count(&total).Order("a.ctime desc").Offset(offset).Limit(limit).
176
+		Preload("Comments", func(db *gorm.DB) *gorm.DB {
177
+		return  db.Where(" status = ?",1)
178
+	}).Where("article_status = ? AND status = ?",4,1).
179
+		Select("a.id, a.title, a.summary, a.content, a.type, a.num, a.mtime, a.real_read_num, a.ctime, a.class_id, a.author, a.status, a.reason,a.star_num, a.comment_num,a.user_org_id, a.article_status, a.imgs, a.video_url, a.source, a.category_id, a.ttid, a.ttype, a.toid").Find(&articles).Error
180
+	fmt.Println("err是啥呢?",err)
181
+	if err != nil {
182
+		return
183
+	}
184
+	return
185
+}
186
+
187
+func GetArticleInfo(orgID int64,id int64)(articles models.Articles,err error){
188
+	err = service.PatientReadDB().Where("user_org_id = ? AND id = ? AND status = ?", orgID, id, 1).Find(&articles).Error
189
+	return  articles,err
190
+}
191
+
192
+func DeleteArticle(ids[] int64,orgID int64)(err error)  {
193
+	if len(ids) ==1{
194
+		err =	service.PatientWriteDB().Model(&models.Articles{}).Where( "id=? and user_org_id = ?",ids[0],orgID).Update(map[string]interface{}{"status":0,"mtime":time.Now().Unix()}).Error
195
+	}else {
196
+		err =	service.PatientWriteDB().Model(&models.Articles{}).Where("id IN (?) and user_org_id = ?",ids,orgID).Update(map[string]interface{}{"status":0,"mtime":time.Now().Unix()}).Error
197
+	}
198
+	return
199
+}
200
+
201
+func GetMenus(OrgID int64)(categorys []*models.ArticleCategory,err error)  {
202
+	err = service.PatientReadDB().Where("user_org_id = ?", OrgID).Find(&categorys).Error
203
+	return  categorys ,err
204
+}
205
+
206
+func UpdataArticleInfo(art *models.Articles,orgID int64,id int64){
207
+	service.PatientWriteDB().Model(art).Where("user_org_id = ? AND id = ? AND status = ?",orgID,id,1).Update(map[string]interface{}{"title":art.Title,"content":art.Content,"imgs":art.Imgs,"class_id":art.ClassId,"mtime":art.Mtime})
208
+
209
+}
210
+
211
+func PreviewEditArticle(art models.Articles,orgID int64,id int64)  {
212
+	service.PatientWriteDB().Model(art).Where("user_org_id = ? AND id = ? AND status = ?",orgID,id,1).Update(map[string]interface{}{"title":art.Title,"content":art.Content,"imgs":art.Imgs,"class_id":art.ClassId,"mtime":art.Mtime,"article_status":art.ArticleStatus})
213
+}
214
+
215
+func GetPreviewInfo(orgID int64)(models.Articles,error)  {
216
+	article := models.Articles{}
217
+	err := service.PatientReadDB().Where("user_org_id = ? AND article_status = ? AND type = ?", orgID, 3, 1).Last(&article).Error
218
+	return article, err
219
+}
220
+
221
+func GetAllComment(page int64,limit int64,orgID int64)(articles []*models.Articles,total int64, err error) {
222
+	db := service.PatientReadDB().Table("sgj_patient_articles as a ").Where("a.status=1")
223
+	if (orgID > 0) {
224
+		db = db.Where("a.user_org_id=? AND article_status = ?", orgID,1)
225
+	}
226
+	offset := (page - 1) * limit
227
+	err = db.Count(&total).Order("a.ctime desc").Offset(offset).Limit(limit).
228
+		Preload("Comments", func(db *gorm.DB) *gorm.DB {
229
+		return  db.Where(" status = ?",1)
230
+	}).
231
+		Select("a.id, a.title, a.summary, a.content, a.type, a.num, a.mtime, a.real_read_num, a.ctime, a.class_id, a.author, a.status, a.reason,a.star_num, a.comment_num,a.user_org_id, a.article_status, a.imgs, a.video_url, a.source, a.category_id, a.ttid, a.ttype, a.toid").Find(&articles).Error
232
+	fmt.Println("err是啥呢?",err)
233
+	if err != nil {
234
+		return
235
+	}
236
+	return
237
+}
238
+
239
+func GetArticleCommentDetail(articleId int64,orgID int64)(*models.Articles, error){
240
+	article := &models.Articles{}
241
+	err :=service.PatientReadDB().Where("id = ? AND user_org_id = ? AND status = ?", articleId, orgID,1).Find(article).Error
242
+	tm := time.Unix(article.Ctime, 0)
243
+	article.PublicTime = tm.Format("2006-01-02 15:04:05")
244
+	return article, err
245
+}

+ 34 - 0
service/staff_service/staff_manager_service.go View File

@@ -0,0 +1,34 @@
1
+package staff_service
2
+
3
+import (
4
+	"SCRM/models"
5
+	"SCRM/service"
6
+)
7
+
8
+func AddStaffInfo(info models.SgjUserStaffInfo) error  {
9
+	err := service.UserWriteDB().Create(&info).Error
10
+	return  err
11
+}
12
+
13
+func  GetAllStaffInfo(orgID int64,page int64,limit int64,keyword string)(userStaffInfo  []*models.SgjUserStaffInfo,total int64,err error){
14
+
15
+	db := service.UserReadDB().Table("sgj_user_staff_info as s").Where("s.status = 1")
16
+	if(orgID>0){
17
+		db = db.Where("s.user_org_id = ?", orgID)
18
+	}
19
+
20
+	if len(keyword) >0{
21
+		keyword = "%" + keyword + "%"
22
+		db.Where("s.name LIKE ? AND s.user_title LIEK ?",keyword,keyword)
23
+	}
24
+
25
+	offset := (page - 1) * limit
26
+	err = db.Count(&total).Order("s.ctime desc").Offset(offset).Limit(limit).
27
+		Select("s.id,s.name,s.phone,s.birthday,s.gender,s.user_type,s.user_title,s.dochead,s.content,s.ctime,s.mtime,s.status,s.user_org_id").Find(&userStaffInfo).Error
28
+
29
+	if err != nil {
30
+		return
31
+	}
32
+	return
33
+}
34
+

+ 28 - 0
service/tencentim_service/tencent_im_service.go View File

@@ -0,0 +1,28 @@
1
+package tencentim_service
2
+
3
+import (
4
+	"SCRM/utils/tencentsig"
5
+)
6
+
7
+//正式
8
+const (
9
+	ThisAccType    = 29296
10
+	ThisSDKAppId   = "1400103109"
11
+	ThisIdentifier = "kuyiyun" //  admin
12
+	ThisPriKey     = `-----BEGIN PRIVATE KEY-----
13
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgpM/saduI49IF+DeL
14
+jEjyU1ryKIEKTyRLekw4w4qeC7ShRANCAAQHinjtGLt6WF2wOfEKA1xomfcS19/i
15
+ZyymFHJlKR49WASx/9XEbm2/TNZUn/qbe5jc4HhujQR7IUVKVFtEU6Oa
16
+-----END PRIVATE KEY-----`
17
+	ThisPubKey = `-----BEGIN PUBLIC KEY-----
18
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEB4p47Ri7elhdsDnxCgNcaJn3Etff
19
+4mcsphRyZSkePVgEsf/VxG5tv0zWVJ/6m3uY3OB4bo0EeyFFSlRbRFOjmg==
20
+-----END PUBLIC KEY-----`
21
+)
22
+
23
+func CreateUserSig(Identifier, AppId3rd string) (userSig string, err error) {
24
+	conf := tencentsig.NewConf(ThisSDKAppId, Identifier, AppId3rd).WithExpire(86400)
25
+	userSig, err = conf.GenUserSig(ThisPriKey)
26
+
27
+	return
28
+}

+ 21 - 0
utils/tencentsig/LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2018 yinheli
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 12 - 0
utils/tencentsig/Makefile View File

@@ -0,0 +1,12 @@
1
+all: test
2
+
3
+.PHONY: fmt
4
+fmt:
5
+	@gofmt -s -l -w *.go
6
+
7
+.PHONY: test
8
+test:
9
+	go test -v -coverprofile .cover.out ./...
10
+	@go tool cover -func=.cover.out
11
+	@go tool cover -html=.cover.out -o .cover.html
12
+	@rm .cover.out

+ 15 - 0
utils/tencentsig/README.md View File

@@ -0,0 +1,15 @@
1
+# 腾讯登录服务(Tencent Login Service,TLS) userSig 生成
2
+
3
+> 这是纯go实现的版本,官方提供的是 cgo 版本,部分用户可能在 mac 等系统上会遇到编译失败的问题,故实现了这个版本
4
+
5
+
6
+支持的 curve 包括: prime256v1, secp256k1
7
+
8
+> 之前腾讯云只支持 secp256k1,后来测试发现有 prime256v1 的密钥
9
+
10
+具体使用请参考 `usersig_test.go`
11
+
12
+## 参考资料
13
+
14
+* https://cloud.tencent.com/document/product/269/1510
15
+* http://bbs.qcloud.com/thread-21826-1-1.html

+ 350 - 0
utils/tencentsig/bitelliptic.go View File

@@ -0,0 +1,350 @@
1
+package tencentsig
2
+
3
+// @see https://github.com/ThePiachu/Golang-Koblitz-elliptic-curve-DSA-library/blob/master/bitelliptic/bitelliptic.go
4
+
5
+import (
6
+	"crypto/elliptic"
7
+	"io"
8
+	"math/big"
9
+	"sync"
10
+)
11
+
12
+// A BitCurve represents a Koblitz Curve with a=0.
13
+// See http://www.hyperelliptic.org/EFD/g1p/auto-shortw.html
14
+type BitCurve struct {
15
+	P       *big.Int // the order of the underlying field
16
+	N       *big.Int // the order of the base point
17
+	B       *big.Int // the constant of the BitCurve equation
18
+	Gx, Gy  *big.Int // (x,y) of the base point
19
+	BitSize int      // the size of the underlying field
20
+}
21
+
22
+func (BitCurve *BitCurve) Params() *elliptic.CurveParams {
23
+	return &elliptic.CurveParams{P: BitCurve.P, N: BitCurve.N, B: BitCurve.B, Gx: BitCurve.Gx, Gy: BitCurve.Gy, BitSize: BitCurve.BitSize}
24
+}
25
+
26
+// IsOnBitCurve returns true if the given (x,y) lies on the BitCurve.
27
+func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
28
+	// y² = x³ + b
29
+	y2 := new(big.Int).Mul(y, y) //y²
30
+	y2.Mod(y2, BitCurve.P)       //y²%P
31
+
32
+	x3 := new(big.Int).Mul(x, x) //x²
33
+	x3.Mul(x3, x)                //x³
34
+
35
+	x3.Add(x3, BitCurve.B) //x³+B
36
+	x3.Mod(x3, BitCurve.P) //(x³+B)%P
37
+
38
+	return x3.Cmp(y2) == 0
39
+}
40
+
41
+//TODO: double check if the function is okay
42
+// affineFromJacobian reverses the Jacobian transform. See the comment at the
43
+// top of the file.
44
+func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
45
+	zinv := new(big.Int).ModInverse(z, BitCurve.P)
46
+	zinvsq := new(big.Int).Mul(zinv, zinv)
47
+
48
+	xOut = new(big.Int).Mul(x, zinvsq)
49
+	xOut.Mod(xOut, BitCurve.P)
50
+	zinvsq.Mul(zinvsq, zinv)
51
+	yOut = new(big.Int).Mul(y, zinvsq)
52
+	yOut.Mod(yOut, BitCurve.P)
53
+	return
54
+}
55
+
56
+// Add returns the sum of (x1,y1) and (x2,y2)
57
+func (BitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
58
+	z := new(big.Int).SetInt64(1)
59
+	return BitCurve.affineFromJacobian(BitCurve.addJacobian(x1, y1, z, x2, y2, z))
60
+}
61
+
62
+// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and
63
+// (x2, y2, z2) and returns their sum, also in Jacobian form.
64
+func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) {
65
+	// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
66
+	z1z1 := new(big.Int).Mul(z1, z1)
67
+	z1z1.Mod(z1z1, BitCurve.P)
68
+	z2z2 := new(big.Int).Mul(z2, z2)
69
+	z2z2.Mod(z2z2, BitCurve.P)
70
+
71
+	u1 := new(big.Int).Mul(x1, z2z2)
72
+	u1.Mod(u1, BitCurve.P)
73
+	u2 := new(big.Int).Mul(x2, z1z1)
74
+	u2.Mod(u2, BitCurve.P)
75
+	h := new(big.Int).Sub(u2, u1)
76
+	if h.Sign() == -1 {
77
+		h.Add(h, BitCurve.P)
78
+	}
79
+	i := new(big.Int).Lsh(h, 1)
80
+	i.Mul(i, i)
81
+	j := new(big.Int).Mul(h, i)
82
+
83
+	s1 := new(big.Int).Mul(y1, z2)
84
+	s1.Mul(s1, z2z2)
85
+	s1.Mod(s1, BitCurve.P)
86
+	s2 := new(big.Int).Mul(y2, z1)
87
+	s2.Mul(s2, z1z1)
88
+	s2.Mod(s2, BitCurve.P)
89
+	r := new(big.Int).Sub(s2, s1)
90
+	if r.Sign() == -1 {
91
+		r.Add(r, BitCurve.P)
92
+	}
93
+	r.Lsh(r, 1)
94
+	v := new(big.Int).Mul(u1, i)
95
+
96
+	x3 := new(big.Int).Set(r)
97
+	x3.Mul(x3, x3)
98
+	x3.Sub(x3, j)
99
+	x3.Sub(x3, v)
100
+	x3.Sub(x3, v)
101
+	x3.Mod(x3, BitCurve.P)
102
+
103
+	y3 := new(big.Int).Set(r)
104
+	v.Sub(v, x3)
105
+	y3.Mul(y3, v)
106
+	s1.Mul(s1, j)
107
+	s1.Lsh(s1, 1)
108
+	y3.Sub(y3, s1)
109
+	y3.Mod(y3, BitCurve.P)
110
+
111
+	z3 := new(big.Int).Add(z1, z2)
112
+	z3.Mul(z3, z3)
113
+	z3.Sub(z3, z1z1)
114
+	if z3.Sign() == -1 {
115
+		z3.Add(z3, BitCurve.P)
116
+	}
117
+	z3.Sub(z3, z2z2)
118
+	if z3.Sign() == -1 {
119
+		z3.Add(z3, BitCurve.P)
120
+	}
121
+	z3.Mul(z3, h)
122
+	z3.Mod(z3, BitCurve.P)
123
+
124
+	return x3, y3, z3
125
+}
126
+
127
+// Double returns 2*(x,y)
128
+func (BitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
129
+	z1 := new(big.Int).SetInt64(1)
130
+	return BitCurve.affineFromJacobian(BitCurve.doubleJacobian(x1, y1, z1))
131
+}
132
+
133
+// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and
134
+// returns its double, also in Jacobian form.
135
+func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) {
136
+	// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
137
+
138
+	a := new(big.Int).Mul(x, x) //X1²
139
+	b := new(big.Int).Mul(y, y) //Y1²
140
+	c := new(big.Int).Mul(b, b) //B²
141
+
142
+	d := new(big.Int).Add(x, b) //X1+B
143
+	d.Mul(d, d)                 //(X1+B)²
144
+	d.Sub(d, a)                 //(X1+B)²-A
145
+	d.Sub(d, c)                 //(X1+B)²-A-C
146
+	d.Mul(d, big.NewInt(2))     //2*((X1+B)²-A-C)
147
+
148
+	e := new(big.Int).Mul(big.NewInt(3), a) //3*A
149
+	f := new(big.Int).Mul(e, e)             //E²
150
+
151
+	x3 := new(big.Int).Mul(big.NewInt(2), d) //2*D
152
+	x3.Sub(f, x3)                            //F-2*D
153
+	x3.Mod(x3, BitCurve.P)
154
+
155
+	y3 := new(big.Int).Sub(d, x3)                  //D-X3
156
+	y3.Mul(e, y3)                                  //E*(D-X3)
157
+	y3.Sub(y3, new(big.Int).Mul(big.NewInt(8), c)) //E*(D-X3)-8*C
158
+	y3.Mod(y3, BitCurve.P)
159
+
160
+	z3 := new(big.Int).Mul(y, z) //Y1*Z1
161
+	z3.Mul(big.NewInt(2), z3)    //3*Y1*Z1
162
+	z3.Mod(z3, BitCurve.P)
163
+
164
+	return x3, y3, z3
165
+}
166
+
167
+//TODO: double check if it is okay
168
+// ScalarMult returns k*(Bx,By) where k is a number in big-endian form.
169
+func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) {
170
+	// We have a slight problem in that the identity of the group (the
171
+	// point at infinity) cannot be represented in (x, y) form on a finite
172
+	// machine. Thus the standard add/double algorithm has to be tweaked
173
+	// slightly: our initial state is not the identity, but x, and we
174
+	// ignore the first true bit in |k|.  If we don't find any true bits in
175
+	// |k|, then we return nil, nil, because we cannot return the identity
176
+	// element.
177
+
178
+	Bz := new(big.Int).SetInt64(1)
179
+	x := Bx
180
+	y := By
181
+	z := Bz
182
+
183
+	seenFirstTrue := false
184
+	for _, b := range k {
185
+		for bitNum := 0; bitNum < 8; bitNum++ {
186
+			if seenFirstTrue {
187
+				x, y, z = BitCurve.doubleJacobian(x, y, z)
188
+			}
189
+			if b&0x80 == 0x80 {
190
+				if !seenFirstTrue {
191
+					seenFirstTrue = true
192
+				} else {
193
+					x, y, z = BitCurve.addJacobian(Bx, By, Bz, x, y, z)
194
+				}
195
+			}
196
+			b <<= 1
197
+		}
198
+	}
199
+
200
+	if !seenFirstTrue {
201
+		return nil, nil
202
+	}
203
+
204
+	return BitCurve.affineFromJacobian(x, y, z)
205
+}
206
+
207
+// ScalarBaseMult returns k*G, where G is the base point of the group and k is
208
+// an integer in big-endian form.
209
+func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
210
+	return BitCurve.ScalarMult(BitCurve.Gx, BitCurve.Gy, k)
211
+}
212
+
213
+var mask = []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f}
214
+
215
+//TODO: double check if it is okay
216
+// GenerateKey returns a public/private key pair. The private key is generated
217
+// using the given reader, which must return random data.
218
+func (BitCurve *BitCurve) GenerateKey(rand io.Reader) (priv []byte, x, y *big.Int, err error) {
219
+	byteLen := (BitCurve.BitSize + 7) >> 3
220
+	priv = make([]byte, byteLen)
221
+
222
+	for x == nil {
223
+		_, err = io.ReadFull(rand, priv)
224
+		if err != nil {
225
+			return
226
+		}
227
+		// We have to mask off any excess bits in the case that the size of the
228
+		// underlying field is not a whole number of bytes.
229
+		priv[0] &= mask[BitCurve.BitSize%8]
230
+		// This is because, in tests, rand will return all zeros and we don't
231
+		// want to get the point at infinity and loop forever.
232
+		priv[1] ^= 0x42
233
+		x, y = BitCurve.ScalarBaseMult(priv)
234
+	}
235
+	return
236
+}
237
+
238
+// Marshal converts a point into the form specified in section 4.3.6 of ANSI
239
+// X9.62.
240
+func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
241
+	byteLen := (BitCurve.BitSize + 7) >> 3
242
+
243
+	ret := make([]byte, 1+2*byteLen)
244
+	ret[0] = 4 // uncompressed point
245
+
246
+	xBytes := x.Bytes()
247
+	copy(ret[1+byteLen-len(xBytes):], xBytes)
248
+	yBytes := y.Bytes()
249
+	copy(ret[1+2*byteLen-len(yBytes):], yBytes)
250
+	return ret
251
+}
252
+
253
+// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On
254
+// error, x = nil.
255
+func (BitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
256
+	byteLen := (BitCurve.BitSize + 7) >> 3
257
+	if len(data) != 1+2*byteLen {
258
+		return
259
+	}
260
+	if data[0] != 4 { // uncompressed form
261
+		return
262
+	}
263
+	x = new(big.Int).SetBytes(data[1 : 1+byteLen])
264
+	y = new(big.Int).SetBytes(data[1+byteLen:])
265
+	return
266
+}
267
+
268
+//curve parameters taken from:
269
+//http://www.secg.org/collateral/sec2_final.pdf
270
+
271
+var initonce sync.Once
272
+var secp160k1 *BitCurve
273
+var secp192k1 *BitCurve
274
+var secp224k1 *BitCurve
275
+var secp256k1 *BitCurve
276
+
277
+func initAll() {
278
+	initS160()
279
+	initS192()
280
+	initS224()
281
+	initS256()
282
+}
283
+
284
+func initS160() {
285
+	// See SEC 2 section 2.4.1
286
+	secp160k1 = new(BitCurve)
287
+	secp160k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73", 16)
288
+	secp160k1.N, _ = new(big.Int).SetString("0100000000000000000001B8FA16DFAB9ACA16B6B3", 16)
289
+	secp160k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000007", 16)
290
+	secp160k1.Gx, _ = new(big.Int).SetString("3B4C382CE37AA192A4019E763036F4F5DD4D7EBB", 16)
291
+	secp160k1.Gy, _ = new(big.Int).SetString("938CF935318FDCED6BC28286531733C3F03C4FEE", 16)
292
+	secp160k1.BitSize = 160
293
+}
294
+
295
+func initS192() {
296
+	// See SEC 2 section 2.5.1
297
+	secp192k1 = new(BitCurve)
298
+	secp192k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37", 16)
299
+	secp192k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D", 16)
300
+	secp192k1.B, _ = new(big.Int).SetString("000000000000000000000000000000000000000000000003", 16)
301
+	secp192k1.Gx, _ = new(big.Int).SetString("DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D", 16)
302
+	secp192k1.Gy, _ = new(big.Int).SetString("9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D", 16)
303
+	secp192k1.BitSize = 192
304
+}
305
+
306
+func initS224() {
307
+	// See SEC 2 section 2.6.1
308
+	secp224k1 = new(BitCurve)
309
+	secp224k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D", 16)
310
+	secp224k1.N, _ = new(big.Int).SetString("010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7", 16)
311
+	secp224k1.B, _ = new(big.Int).SetString("00000000000000000000000000000000000000000000000000000005", 16)
312
+	secp224k1.Gx, _ = new(big.Int).SetString("A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C", 16)
313
+	secp224k1.Gy, _ = new(big.Int).SetString("7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5", 16)
314
+	secp224k1.BitSize = 224
315
+}
316
+
317
+func initS256() {
318
+	// See SEC 2 section 2.7.1
319
+	secp256k1 = new(BitCurve)
320
+	secp256k1.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16)
321
+	secp256k1.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
322
+	secp256k1.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16)
323
+	secp256k1.Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
324
+	secp256k1.Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
325
+	secp256k1.BitSize = 256
326
+}
327
+
328
+// S160 returns a BitCurve which implements secp160k1 (see SEC 2 section 2.4.1)
329
+func S160() *BitCurve {
330
+	initonce.Do(initAll)
331
+	return secp160k1
332
+}
333
+
334
+// S192 returns a BitCurve which implements secp192k1 (see SEC 2 section 2.5.1)
335
+func S192() *BitCurve {
336
+	initonce.Do(initAll)
337
+	return secp192k1
338
+}
339
+
340
+// S224 returns a BitCurve which implements secp224k1 (see SEC 2 section 2.6.1)
341
+func S224() *BitCurve {
342
+	initonce.Do(initAll)
343
+	return secp224k1
344
+}
345
+
346
+// S256 returns a BitCurve which implements secp256k1 (see SEC 2 section 2.7.1)
347
+func S256() *BitCurve {
348
+	initonce.Do(initAll)
349
+	return secp256k1
350
+}

+ 8 - 0
utils/tencentsig/go.mod View File

@@ -0,0 +1,8 @@
1
+module github.com/yinheli/tencentsig
2
+
3
+require (
4
+	github.com/davecgh/go-spew v1.1.0
5
+	github.com/pmezard/go-difflib v1.0.0
6
+	github.com/stretchr/objx v0.1.0
7
+	github.com/stretchr/testify v1.2.1
8
+)

+ 7 - 0
utils/tencentsig/go.sum View File

@@ -0,0 +1,7 @@
1
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6
+github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U=
7
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=

+ 31 - 0
utils/tencentsig/parse.go View File

@@ -0,0 +1,31 @@
1
+package tencentsig
2
+
3
+import (
4
+	"crypto/x509/pkix"
5
+	"encoding/asn1"
6
+)
7
+
8
+var (
9
+	oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
10
+	oidNamedCurveS256 = asn1.ObjectIdentifier{1, 3, 132, 0, 10}
11
+)
12
+
13
+type pkcs8 struct {
14
+	Version    int
15
+	Algo       pkix.AlgorithmIdentifier
16
+	PrivateKey []byte
17
+	// optional attributes omitted.
18
+}
19
+
20
+type publicKeyInfo struct {
21
+	Raw       asn1.RawContent
22
+	Algorithm pkix.AlgorithmIdentifier
23
+	PublicKey asn1.BitString
24
+}
25
+
26
+type ecPrivateKey struct {
27
+	Version       int
28
+	PrivateKey    []byte
29
+	NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
30
+	PublicKey     asn1.BitString        `asn1:"optional,explicit,tag:1"`
31
+}

+ 256 - 0
utils/tencentsig/usersig.go View File

@@ -0,0 +1,256 @@
1
+package tencentsig
2
+
3
+import (
4
+	"bytes"
5
+	"compress/zlib"
6
+	"crypto"
7
+	"crypto/ecdsa"
8
+	"crypto/elliptic"
9
+	"crypto/rand"
10
+	"crypto/sha256"
11
+	"crypto/x509"
12
+	"encoding/asn1"
13
+	"encoding/base64"
14
+	"encoding/hex"
15
+	"encoding/json"
16
+	"encoding/pem"
17
+	"fmt"
18
+	"io/ioutil"
19
+	"math/big"
20
+	"strings"
21
+	"time"
22
+)
23
+
24
+const (
25
+	accountType   = "29296"
26
+	version       = "201512300000"
27
+	defaultExpire = 3600 * 24 * 180
28
+)
29
+
30
+var (
31
+	tlsReplace = map[string]string{
32
+		"+": "*",
33
+		"/": "-",
34
+		"=": "_",
35
+	}
36
+)
37
+
38
+type Conf struct {
39
+	AccountType string `json:"TLS.account_type"`
40
+	Identifier  string `json:"TLS.identifier"`
41
+	AppidAt3rd  string `json:"TLS.appid_at_3rd"`
42
+	SdkAppid    string `json:"TLS.sdk_appid"`
43
+	ExpireAfter string `json:"TLS.expire_after"`
44
+	Version     string `json:"TLS.version"`
45
+	Time        string `json:"TLS.time"`
46
+	Sig         string `json:"TLS.sig"`
47
+}
48
+
49
+func NewConf(sdkAppId string, identifier string, appidAt3rd string) *Conf {
50
+	return &Conf{
51
+		AccountType: accountType,
52
+		Identifier:  identifier,
53
+		AppidAt3rd:  appidAt3rd,
54
+		SdkAppid:    sdkAppId,
55
+		ExpireAfter: fmt.Sprintf("%d", defaultExpire),
56
+		Version:     version,
57
+		Time:        fmt.Sprintf("%d", time.Now().Unix()),
58
+	}
59
+}
60
+
61
+func (c *Conf) WithExpire(expireInSeconds int) *Conf {
62
+	c.ExpireAfter = fmt.Sprintf("%d", expireInSeconds)
63
+	return c
64
+}
65
+
66
+func (c *Conf) GenUserSig(pemPrivateKey string) (string, error) {
67
+	var err error
68
+	c.Sig, err = c.sign(pemPrivateKey)
69
+	if err != nil {
70
+		return "", err
71
+	}
72
+	data, _ := json.Marshal(c)
73
+
74
+	var b bytes.Buffer
75
+	z := zlib.NewWriter(&b)
76
+	z.Write(data)
77
+	z.Close()
78
+
79
+	return base64Encode(b.Bytes()), nil
80
+}
81
+
82
+func VerifyUserSig(pemPublicKey string, userSig string) (*Conf, bool, error) {
83
+	data, err := base64Decode(userSig)
84
+	if err != nil {
85
+		return nil, false, err
86
+	}
87
+	reader, err := zlib.NewReader(bytes.NewReader(data))
88
+	if err != nil {
89
+		return nil, false, err
90
+	}
91
+
92
+	data, err = ioutil.ReadAll(reader)
93
+	if err != nil {
94
+		return nil, false, err
95
+	}
96
+
97
+	var conf Conf
98
+	err = json.Unmarshal(data, &conf)
99
+	if err != nil {
100
+		return nil, false, err
101
+	}
102
+
103
+	block, _ := pem.Decode([]byte(pemPublicKey))
104
+
105
+	pk, err := x509.ParsePKIXPublicKey(block.Bytes)
106
+	if err != nil {
107
+		if strings.Contains(err.Error(), "unsupported elliptic curve") {
108
+			var pki publicKeyInfo
109
+			if _, err := asn1.Unmarshal(block.Bytes, &pki); err != nil {
110
+				return nil, false, err
111
+			}
112
+
113
+			asn1Data := pki.PublicKey.RightAlign()
114
+			fmt.Println(hex.EncodeToString(asn1Data))
115
+			paramsData := pki.Algorithm.Parameters.FullBytes
116
+			namedCurveOID := new(asn1.ObjectIdentifier)
117
+			_, err = asn1.Unmarshal(paramsData, namedCurveOID)
118
+			if err != nil {
119
+				return nil, false, err
120
+			}
121
+
122
+			if namedCurveOID.Equal(oidNamedCurveS256) {
123
+				pubk := new(ecdsa.PublicKey)
124
+				pubk.Curve = S256()
125
+				pubk.X, pubk.Y = elliptic.Unmarshal(pubk.Curve, asn1Data)
126
+				pk = pubk
127
+			}
128
+		} else {
129
+			return nil, false, err
130
+		}
131
+	}
132
+
133
+	pubKey := pk.(*ecdsa.PublicKey)
134
+
135
+	content := conf.signContent()
136
+	hashed := sha256.Sum256([]byte(content))
137
+
138
+	signature, _ := base64.StdEncoding.DecodeString(conf.Sig)
139
+	r, s, err := pointsFromDER(signature)
140
+	if err != nil {
141
+		return nil, false, err
142
+	}
143
+
144
+	res := ecdsa.Verify(pubKey, hashed[:], r, s)
145
+	return &conf, res, nil
146
+}
147
+
148
+func (c *Conf) signContent() string {
149
+	var builder strings.Builder
150
+
151
+	builder.WriteString("TLS.appid_at_3rd:")
152
+	builder.WriteString(c.AppidAt3rd)
153
+	builder.WriteString("\n")
154
+
155
+	builder.WriteString("TLS.account_type:")
156
+	builder.WriteString(c.AccountType)
157
+	builder.WriteString("\n")
158
+
159
+	builder.WriteString("TLS.identifier:")
160
+	builder.WriteString(c.Identifier)
161
+	builder.WriteString("\n")
162
+
163
+	builder.WriteString("TLS.sdk_appid:")
164
+	builder.WriteString(c.SdkAppid)
165
+	builder.WriteString("\n")
166
+
167
+	builder.WriteString("TLS.time:")
168
+	builder.WriteString(c.Time)
169
+	builder.WriteString("\n")
170
+
171
+	builder.WriteString("TLS.expire_after:")
172
+	builder.WriteString(c.ExpireAfter)
173
+	builder.WriteString("\n")
174
+
175
+	return builder.String()
176
+}
177
+
178
+func (c *Conf) sign(privateKey string) (string, error) {
179
+	block, _ := pem.Decode([]byte(privateKey))
180
+
181
+	pk, err := x509.ParsePKCS8PrivateKey(block.Bytes)
182
+	if err != nil {
183
+		if strings.Contains(err.Error(), "unknown elliptic curve") {
184
+			var privKey pkcs8
185
+			if _, err := asn1.Unmarshal(block.Bytes, &privKey); err != nil {
186
+				return "", err
187
+			}
188
+
189
+			if privKey.Algo.Algorithm.Equal(oidPublicKeyECDSA) {
190
+				namedCurveOID := new(asn1.ObjectIdentifier)
191
+				asn1.Unmarshal(privKey.Algo.Parameters.FullBytes, namedCurveOID)
192
+				if namedCurveOID.Equal(oidNamedCurveS256) {
193
+					var ecPrivKey ecPrivateKey
194
+					asn1.Unmarshal(privKey.PrivateKey, &ecPrivKey)
195
+
196
+					k := new(ecdsa.PrivateKey)
197
+					k.Curve = S256()
198
+					d := new(big.Int)
199
+					d.SetBytes(ecPrivKey.PrivateKey)
200
+					k.D = d
201
+					k.X, k.Y = S256().ScalarBaseMult(d.Bytes())
202
+					pk = k
203
+				}
204
+			}
205
+		} else {
206
+			return "", err
207
+		}
208
+	}
209
+
210
+	priv := pk.(*ecdsa.PrivateKey)
211
+
212
+	content := c.signContent()
213
+
214
+	hashed := sha256.Sum256([]byte(content))
215
+
216
+	sig, err := priv.Sign(rand.Reader, hashed[:], crypto.SHA256)
217
+	if err != nil {
218
+		return "", err
219
+	}
220
+	return base64.StdEncoding.EncodeToString(sig), nil
221
+}
222
+
223
+func base64Encode(data []byte) string {
224
+	res := base64.StdEncoding.EncodeToString(data)
225
+	for k, v := range tlsReplace {
226
+		res = strings.Replace(res, k, v, -1)
227
+	}
228
+	return res
229
+}
230
+
231
+func base64Decode(data string) ([]byte, error) {
232
+	for k, v := range tlsReplace {
233
+		data = strings.Replace(data, v, k, -1)
234
+	}
235
+	return base64.StdEncoding.DecodeString(data)
236
+}
237
+
238
+func pointsFromDER(der []byte) (R, S *big.Int, err error) {
239
+	R, S = &big.Int{}, &big.Int{}
240
+
241
+	data := asn1.RawValue{}
242
+	if _, err = asn1.Unmarshal(der, &data); err != nil {
243
+		return
244
+	}
245
+
246
+	// The format of our DER string is 0x02 + rlen + r + 0x02 + slen + s
247
+	rLen := data.Bytes[1] // The entire length of R + offset of 2 for 0x02 and rlen
248
+	r := data.Bytes[2 : rLen+2]
249
+	// Ignore the next 0x02 and slen bytes and just take the start of S to the end of the byte array
250
+	s := data.Bytes[rLen+4:]
251
+
252
+	R.SetBytes(r)
253
+	S.SetBytes(s)
254
+
255
+	return
256
+}

+ 97 - 0
utils/tencentsig/usersig_test.go View File

@@ -0,0 +1,97 @@
1
+package tencentsig
2
+
3
+import (
4
+	"encoding/json"
5
+	"github.com/stretchr/testify/assert"
6
+	"log"
7
+	"os"
8
+	"strings"
9
+	"testing"
10
+)
11
+
12
+var (
13
+	sdkAppId   = "1234567890"
14
+	identifier = "zhangshan"
15
+
16
+	l = log.New(os.Stdout, "[tencentsig - test] ", log.LstdFlags)
17
+)
18
+
19
+func doVerifyUserSig(t *testing.T, pemPrivateKey, pemPublicKey string) {
20
+	conf := NewConf(sdkAppId, identifier).WithExpire(defaultExpire)
21
+	userSig, err := conf.GenUserSig(pemPrivateKey)
22
+	assert.Nil(t, err)
23
+
24
+	l.Print("userSig:", userSig)
25
+
26
+	c, valid, err := VerifyUserSig(pemPublicKey, userSig)
27
+
28
+	assert.Nil(t, err)
29
+	assert.True(t, valid)
30
+
31
+	b, _ := json.MarshalIndent(c, "", "  ")
32
+	l.Print("conf:", string(b))
33
+}
34
+
35
+// 准备
36
+
37
+/*
38
+
39
+# list curves
40
+openssl ecparam -list_curves
41
+
42
+
43
+# prime256v1 curve
44
+
45
+openssl ecparam -name prime256v1 -genkey -noout -out private.pem
46
+openssl pkcs8 -topk8 -nocrypt -in private.pem -out private-pkcs8.pem && cat private-pkcs8.pem
47
+openssl ec -in private.pem -pubout
48
+
49
+
50
+# secp256k1 curve
51
+
52
+openssl ecparam -name secp256k1 -genkey -noout -out private.pem
53
+openssl pkcs8 -topk8 -nocrypt -in private.pem -out private-pkcs8.pem && cat private-pkcs8.pem
54
+openssl ec -in private.pem -pubout
55
+*/
56
+
57
+func TestVerifyUserSig(t *testing.T) {
58
+	// prime256v1
59
+	doVerifyUserSig(
60
+		t,
61
+
62
+		strings.TrimSpace(`
63
+-----BEGIN PRIVATE KEY-----
64
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJzqd3dF+O6vd+bGJ
65
+7tGA7TLsWNzbYBKRGELEA65ywQahRANCAATIBFu6F5SlqrPFkuhi46IRXXKyEiuU
66
+g8pP+n3L5ZSiW3o0N58P0Ix77PrRVSXLfHd5VqeyF2CWWDUQZyA/butY
67
+-----END PRIVATE KEY-----
68
+		`),
69
+
70
+		strings.TrimSpace(`
71
+-----BEGIN PUBLIC KEY-----
72
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyARbuheUpaqzxZLoYuOiEV1yshIr
73
+lIPKT/p9y+WUolt6NDefD9CMe+z60VUly3x3eVanshdgllg1EGcgP27rWA==
74
+-----END PUBLIC KEY-----
75
+		`),
76
+	)
77
+
78
+	// secp256k1
79
+	doVerifyUserSig(
80
+		t,
81
+
82
+		strings.TrimSpace(`
83
+-----BEGIN PRIVATE KEY-----
84
+MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgkRrBHsxAXy4ssvSYsJIM
85
+TUzzLIHOeUQ/QKygM3JhvDahRANCAATyucyxciWHFclVxRPW7zJ6d51F5au6xnZk
86
+bjkiDOpa6gl8JhdeWcKLYgRb5raHNq/JYUYJSrsH29whxdx0lpq7
87
+-----END PRIVATE KEY-----
88
+		`),
89
+
90
+		strings.TrimSpace(`
91
+-----BEGIN PUBLIC KEY-----
92
+MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE8rnMsXIlhxXJVcUT1u8yenedReWrusZ2
93
+ZG45IgzqWuoJfCYXXlnCi2IEW+a2hzavyWFGCUq7B9vcIcXcdJaauw==
94
+-----END PUBLIC KEY-----
95
+		`),
96
+	)
97
+}