Browse Source

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

xiaoming_global 5 years ago
parent
commit
77bdc7d7b7

+ 2 - 2
controllers/global/open_wechat_controller.go View File

@@ -37,7 +37,7 @@ func (c *OpenWechatAPIController) Prepare() {
37 37
 //OpenWechatCtlRegistRouters 微信开放平台注册路由
38 38
 func OpenWechatCtlRegistRouters() {
39 39
 	beego.Router("/openwechat/authorizationevents", &OpenWechatAPIController{}, "*:AuthorizationEvents")
40
-	beego.Router("/openwechat/mp/notice", &OpenWechatAPIController{}, "Post:MpWechatNotice")
40
+	beego.Router("/openwechat/mp/notice/:appid", &OpenWechatAPIController{}, "Post:MpWechatNotice")
41 41
 	beego.Router("/openwechat/mp/authorization", &OpenWechatAPIController{}, "Get:Authorization")
42 42
 	beego.Router("/openwechat/mp/authorizerinfo/:id/:vtime/:vcode", &OpenWechatAPIController{}, "*:AuthorizerInfo")
43 43
 }
@@ -132,7 +132,7 @@ func (c *OpenWechatAPIController) AuthorizationEvents() {
132 132
 
133 133
 //MpWechatNotice 授权后公众号消息与事件接收URL
134 134
 func (c *OpenWechatAPIController) MpWechatNotice() {
135
-	appid := c.GetString("appid")
135
+	appid := c.Ctx.Input.Param(":appid")
136 136
 	if len(appid) == 0 {
137 137
 		utils.ErrorLog("appid not found")
138 138
 		c.Ctx.WriteString("success")

+ 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
+}

+ 188 - 0
controllers/marketing_tool/activity_controller.go View File

@@ -16,6 +16,11 @@ func ActivityCtlRegistRouters() {
16 16
 	beego.Router("/api/activities", &ActivityAPIController{}, "get:Activities")
17 17
 	beego.Router("/api/activity", &ActivityAPIController{}, "get:GetActivity")
18 18
 	beego.Router("/api/activity/submit", &ActivityAPIController{}, "post:ActivitySubmit")
19
+	beego.Router("/api/activity/detailview", &ActivityAPIController{}, "get:GetActivityDetailView")
20
+	beego.Router("/api/activity/signups", &ActivityAPIController{}, "get:SignupUsers")
21
+	beego.Router("/api/activity/delete", &ActivityAPIController{}, "post:DeleteActivity")
22
+	beego.Router("/api/activity/publish", &ActivityAPIController{}, "post:PublishActivity")
23
+	beego.Router("/api/activity/shareinfo", &ActivityAPIController{}, "get:GetActivityShareInfo")
19 24
 }
20 25
 
21 26
 type ActivityAPIController struct {
@@ -153,12 +158,17 @@ func (this *ActivityAPIController) GetActivity() {
153 158
 		paragraphContent = paragraph.Text
154 159
 	}
155 160
 
161
+	currentOrg := adminUserInfo.Orgs[adminUserInfo.CurrentOrgId]
156 162
 	this.ServeSuccessJSON(map[string]interface{}{
157 163
 		"activity": activity,
158 164
 		"paragraph": map[string]interface{}{
159 165
 			"title":   paragraphTitle,
160 166
 			"content": paragraphContent,
161 167
 		},
168
+		"org": map[string]interface{}{
169
+			"logo": currentOrg.OrgLogo,
170
+			"name": currentOrg.OrgName,
171
+		},
162 172
 	})
163 173
 }
164 174
 
@@ -207,8 +217,12 @@ func (this *ActivityAPIController) ActivitySubmit() {
207 217
 		} else if activity == nil || activity.Status == 9 {
208 218
 			this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityNotExist)
209 219
 			return
220
+		} else if activity.Status == 1 {
221
+			this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityPublishedCannotModify)
222
+			return
210 223
 		}
211 224
 	}
225
+
212 226
 	if len(activityForm.Title) == 0 || len(activityForm.Subtitle) == 0 || len(activityForm.PosterPhoto) == 0 || len(activityForm.Address) == 0 || len(activityForm.SignUpDeadline) == 0 || len(activityForm.StartTime) == 0 {
213 227
 		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeParamWrong)
214 228
 		return
@@ -329,3 +343,177 @@ func (this *ActivityAPIController) ActivitySubmit() {
329 343
 		"activity_id": activity.Id,
330 344
 	})
331 345
 }
346
+
347
+// /api/activity/detailview [get]
348
+// @param id:int
349
+func (this *ActivityAPIController) GetActivityDetailView() {
350
+	activityID, _ := this.GetInt64("id")
351
+	if activityID <= 0 {
352
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeParamWrong)
353
+		return
354
+	}
355
+
356
+	adminUserInfo := this.GetAdminUserInfo()
357
+	activity, getActivityErr := marketing_tool_service.GetActivityWithID(adminUserInfo.CurrentOrgId, activityID)
358
+	if getActivityErr != nil {
359
+		this.ErrorLog("获取活动失败:%v", getActivityErr)
360
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
361
+		return
362
+	} else if activity == nil {
363
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityNotExist)
364
+		return
365
+	}
366
+	if activity.Status != 1 && activity.Status != 4 {
367
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityNotExist)
368
+		return
369
+	}
370
+
371
+	signupUsers, _, getUsersErr := marketing_tool_service.GetValidActivitySignupUsers(activityID, "", 1)
372
+	if getUsersErr != nil {
373
+		this.ErrorLog("获取活动报名客户失败:%v", getUsersErr)
374
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
375
+		return
376
+	}
377
+
378
+	this.ServeSuccessJSON(map[string]interface{}{
379
+		"activity": activity,
380
+		"users":    signupUsers,
381
+	})
382
+}
383
+
384
+// /api/activity/signups [get]
385
+// @param id:int
386
+// @param keyword?:string
387
+// @param page?:int
388
+func (this *ActivityAPIController) SignupUsers() {
389
+	activityID, _ := this.GetInt64("id")
390
+	if activityID <= 0 {
391
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeParamWrong)
392
+		return
393
+	}
394
+	page, _ := this.GetInt("page")
395
+	if page <= 0 {
396
+		page = 1
397
+	}
398
+	keyword := this.GetString("keyword")
399
+
400
+	adminUserInfo := this.GetAdminUserInfo()
401
+	activity, getActivityErr := marketing_tool_service.GetActivityWithID(adminUserInfo.CurrentOrgId, activityID)
402
+	if getActivityErr != nil {
403
+		this.ErrorLog("获取活动失败:%v", getActivityErr)
404
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
405
+		return
406
+	} else if activity == nil {
407
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityNotExist)
408
+		return
409
+	}
410
+
411
+	signupUsers, total, getUsersErr := marketing_tool_service.GetValidActivitySignupUsers(activityID, keyword, page)
412
+	if getUsersErr != nil {
413
+		this.ErrorLog("获取活动报名客户失败:%v", getUsersErr)
414
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
415
+		return
416
+	}
417
+	this.ServeSuccessJSON(map[string]interface{}{
418
+		"users": signupUsers,
419
+		"total": total,
420
+	})
421
+}
422
+
423
+// /api/activity/delete [post]
424
+// @param id:int
425
+func (this *ActivityAPIController) DeleteActivity() {
426
+	activityID, _ := this.GetInt64("id")
427
+	if activityID <= 0 {
428
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeParamWrong)
429
+		return
430
+	}
431
+
432
+	adminUserInfo := this.GetAdminUserInfo()
433
+	activity, getActivityErr := marketing_tool_service.GetActivityWithID(adminUserInfo.CurrentOrgId, activityID)
434
+	if getActivityErr != nil {
435
+		this.ErrorLog("获取活动失败:%v", getActivityErr)
436
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
437
+		return
438
+	} else if activity == nil || activity.Status == 9 {
439
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityNotExist)
440
+		return
441
+	}
442
+
443
+	deleteErr := marketing_tool_service.DeleteActivity(adminUserInfo.CurrentOrgId, activityID)
444
+	if deleteErr != nil {
445
+		this.ErrorLog("删除活动失败:%v", deleteErr)
446
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
447
+		return
448
+	}
449
+
450
+	this.ServeSuccessJSON(nil)
451
+}
452
+
453
+// /api/activity/publish [post]
454
+// @param id:int
455
+func (this *ActivityAPIController) PublishActivity() {
456
+	activityID, _ := this.GetInt64("id")
457
+	if activityID <= 0 {
458
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeParamWrong)
459
+		return
460
+	}
461
+
462
+	adminUserInfo := this.GetAdminUserInfo()
463
+	activity, getActivityErr := marketing_tool_service.GetActivityWithID(adminUserInfo.CurrentOrgId, activityID)
464
+	if getActivityErr != nil {
465
+		this.ErrorLog("获取活动失败:%v", getActivityErr)
466
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
467
+		return
468
+	} else if activity == nil || activity.Status == 9 {
469
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityNotExist)
470
+		return
471
+	}
472
+
473
+	if activity.Status == 1 {
474
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityPublishRepeat)
475
+		return
476
+	}
477
+
478
+	activity.Status = 1
479
+	activity.ModifyTime = time.Now().Unix()
480
+	updateErr := marketing_tool_service.SaveActivityAndParagraph(activity, nil)
481
+	if updateErr != nil {
482
+		this.ErrorLog("发布活动失败:%v", updateErr)
483
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
484
+		return
485
+	}
486
+	this.ServeSuccessJSON(nil)
487
+}
488
+
489
+// /api/activity/shareinfo [get]
490
+// @param id:int
491
+func (this *ActivityAPIController) GetActivityShareInfo() {
492
+	activityID, _ := this.GetInt64("id")
493
+	if activityID <= 0 {
494
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeParamWrong)
495
+		return
496
+	}
497
+
498
+	adminUserInfo := this.GetAdminUserInfo()
499
+	activity, getActivityErr := marketing_tool_service.GetActivityWithID(adminUserInfo.CurrentOrgId, activityID)
500
+	if getActivityErr != nil {
501
+		this.ErrorLog("获取活动失败:%v", getActivityErr)
502
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
503
+		return
504
+	} else if activity == nil {
505
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeActivityNotExist)
506
+		return
507
+	}
508
+
509
+	wxShareModel, getWxShareErr := marketing_tool_service.GetActivityWxShareByActivityID(activity.Id)
510
+	if getWxShareErr != nil || wxShareModel == nil {
511
+		this.ErrorLog("获取活动微信分享信息失败:%v", getWxShareErr)
512
+		this.ServeFailJSONWithSGJErrorCode(enums.ErrorCodeDataException)
513
+		return
514
+	}
515
+
516
+	this.ServeSuccessJSON(map[string]interface{}{
517
+		"url": wxShareModel.ShortURL,
518
+	})
519
+}

+ 179 - 0
controllers/mpwechat/material_controller.go View File

@@ -0,0 +1,179 @@
1
+package mpwechat
2
+
3
+import (
4
+	base_ctl "SCRM/controllers"
5
+	"SCRM/enums"
6
+	"SCRM/models"
7
+	"SCRM/service/wechat_service"
8
+	"bytes"
9
+	"encoding/json"
10
+	"mime/multipart"
11
+	"sync"
12
+	"time"
13
+
14
+	"io"
15
+	"io/ioutil"
16
+
17
+	"github.com/astaxie/beego"
18
+	"github.com/astaxie/beego/httplib"
19
+)
20
+
21
+func MpMaterialCtlRegistRouters() {
22
+	beego.Router("/api/mpwechat/media/add_material", &MpMaterialAPIController{}, "Post:AddMaterial")
23
+	beego.Router("/api/mpwechat/media/material", &MpMaterialAPIController{}, "Get:GetMaterials")
24
+}
25
+
26
+type MpMaterialAPIController struct {
27
+	base_ctl.BaseAuthAPIController
28
+}
29
+
30
+type MultipartFormField struct {
31
+	IsFile   bool
32
+	Name     string
33
+	FileName string
34
+	Value    io.Reader
35
+}
36
+
37
+var mediaBufferPool = sync.Pool{
38
+	New: func() interface{} {
39
+		return bytes.NewBuffer(make([]byte, 0, 10<<20)) // 10MB
40
+	},
41
+}
42
+
43
+func (c MpMaterialAPIController) AddMaterial() {
44
+
45
+	adminUserInfo := c.GetAdminUserInfo()
46
+
47
+	authorization, err := wechat_service.GetAuthorizationByOrgID(adminUserInfo.CurrentOrgId)
48
+	if err != nil {
49
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "上传失败:("+err.Error()+")")
50
+		return
51
+	}
52
+	if authorization == nil || authorization.AuthorizerStatus != 1 {
53
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:(公众号未授权)")
54
+		return
55
+	}
56
+
57
+	file, head, err := c.GetFile("file")
58
+	if err != nil {
59
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err.Error()+")")
60
+		return
61
+	}
62
+	defer file.Close()
63
+
64
+	var fields = []MultipartFormField{
65
+		{
66
+			IsFile:   true,
67
+			Name:     "media",
68
+			FileName: head.Filename,
69
+			Value:    file,
70
+		},
71
+	}
72
+
73
+	buffer := mediaBufferPool.Get().(*bytes.Buffer)
74
+	buffer.Reset()
75
+	defer mediaBufferPool.Put(buffer)
76
+
77
+	multipartWriter := multipart.NewWriter(buffer)
78
+	for i := 0; i < len(fields); i++ {
79
+		if field := &fields[i]; field.IsFile {
80
+			partWriter, err3 := multipartWriter.CreateFormFile(field.Name, field.FileName)
81
+			if err3 != nil {
82
+				c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err3.Error()+")")
83
+				return
84
+			}
85
+			if _, err3 = io.Copy(partWriter, field.Value); err3 != nil {
86
+				c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err3.Error()+")")
87
+				return
88
+			}
89
+		} else {
90
+			partWriter, err3 := multipartWriter.CreateFormField(field.Name)
91
+			if err3 != nil {
92
+				c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err3.Error()+")")
93
+				return
94
+			}
95
+			if _, err3 = io.Copy(partWriter, field.Value); err3 != nil {
96
+				c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err3.Error()+")")
97
+				return
98
+			}
99
+		}
100
+	}
101
+
102
+	if err = multipartWriter.Close(); err != nil {
103
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err.Error()+")")
104
+		return
105
+	}
106
+	requestBodyBytes := buffer.Bytes()
107
+	requestBodyType := multipartWriter.FormDataContentType()
108
+	url := "https://api.weixin.qq.com/cgi-bin/material/add_material?type=image&access_token=" + authorization.AuthorizerAccessToken
109
+	res := httplib.Post(url)
110
+	res.Header("Content-Type", requestBodyType)
111
+	response, err := res.Body(requestBodyBytes).Response()
112
+	if err != nil {
113
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err.Error()+")")
114
+		return
115
+	}
116
+	respBody, err := ioutil.ReadAll(response.Body)
117
+	if err != nil {
118
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err.Error()+")")
119
+		return
120
+	}
121
+
122
+	var resMaterial wechat_service.ResAddMaterial
123
+	err = json.Unmarshal(respBody, &resMaterial)
124
+	if err != nil {
125
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err.Error()+")")
126
+		return
127
+	}
128
+	if resMaterial.ErrCode != 0 {
129
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+resMaterial.ErrMsg+")")
130
+		return
131
+	}
132
+	mediaID := resMaterial.MediaID
133
+	mediaUrl := resMaterial.URL
134
+	var media models.WechatMedias
135
+	media.UserOrgId = adminUserInfo.CurrentOrgId
136
+	media.MediaId = mediaID
137
+	media.MediaUrl = mediaUrl
138
+	media.Msgtype = "image"
139
+	media.MediaTitle = head.Filename
140
+	media.CreatedTime = time.Now().Unix()
141
+	media.UpdatedTime = time.Now().Unix()
142
+	media.MediaStatus = 1
143
+
144
+	err = wechat_service.SaveMaterial(&media)
145
+	if err != nil {
146
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "上传失败:("+err.Error()+")")
147
+		return
148
+	}
149
+
150
+	returnData := make(map[string]interface{}, 0)
151
+	returnData["media"] = media
152
+	c.ServeSuccessJSON(returnData)
153
+	return
154
+}
155
+
156
+func (c *MpMaterialAPIController) GetMaterials() {
157
+	page, _ := c.GetInt64("page", 1)
158
+	limit, _ := c.GetInt64("limit", 10)
159
+	mediaType := c.GetString("media_type")
160
+	if page <= 0 {
161
+		page = 1
162
+	}
163
+	if limit <= 0 {
164
+		limit = 10
165
+	}
166
+
167
+	adminUserInfo := c.GetAdminUserInfo()
168
+	medias, total, err := wechat_service.GetPageMaterials(adminUserInfo.CurrentOrgId, page, limit, mediaType)
169
+	if err != nil {
170
+		c.ServeFailJsonSend(enums.ErrorCodeDBUpdate, "拉取失败:("+err.Error()+")")
171
+		return
172
+	}
173
+
174
+	returnData := make(map[string]interface{}, 0)
175
+	returnData["medias"] = medias
176
+	returnData["total"] = total
177
+	c.ServeSuccessJSON(returnData)
178
+	return
179
+}

+ 86 - 0
controllers/mpwechat/menu_controller.go View File

@@ -15,6 +15,7 @@ import (
15 15
 func MpMenusCtlRegistRouters() {
16 16
 	beego.Router("/api/mpwechat/menus", &MpMenusAPIController{}, "Get:GetMenus")
17 17
 	beego.Router("/api/mpwechat/savemenus", &MpMenusAPIController{}, "Put:SaveMenus")
18
+	beego.Router("/api/mpwechat/deletemenus", &MpMenusAPIController{}, "Post:DeleteMenu")
18 19
 }
19 20
 
20 21
 type MpMenusAPIController struct {
@@ -34,6 +35,7 @@ func (c *MpMenusAPIController) GetMenus() {
34 35
 		buttons = nil
35 36
 	}
36 37
 
38
+	mediaIDs := make([]string, 0)
37 39
 	var menus wechat_service.SelfMenuInfo
38 40
 	if buttons != nil {
39 41
 		err = json.Unmarshal([]byte(buttons.ButtonFileds), &menus)
@@ -48,15 +50,50 @@ func (c *MpMenusAPIController) GetMenus() {
48 50
 		}
49 51
 
50 52
 		for index, menu := range menus.Button {
53
+			if menu.Type == "media_id" && len(menu.MediaId) > 0 {
54
+				mediaIDs = append(mediaIDs, menu.MediaId)
55
+			}
51 56
 			if _, exist := msgMap[menu.Key]; exist {
52 57
 				menus.Button[index].Message = msgMap[menu.Key]
53 58
 			}
54 59
 			for sindex, sub := range menus.Button[index].SubButton {
60
+				if sub.Type == "media_id" && len(sub.MediaId) > 0 {
61
+					mediaIDs = append(mediaIDs, sub.MediaId)
62
+				}
63
+
55 64
 				if _, exist := msgMap[sub.Key]; exist {
56 65
 					menus.Button[index].SubButton[sindex].Message = msgMap[sub.Key]
57 66
 				}
58 67
 			}
59 68
 		}
69
+		if len(mediaIDs) > 0 {
70
+			medias, err := wechat_service.FindMeterialsByMediaIDs(adminUserInfo.CurrentOrgId, "image", mediaIDs)
71
+			if err != nil {
72
+				c.ServeFailJsonSend(enums.ErrorCodeDataException, "获取菜单信息失败:("+err.Error()+")")
73
+				return
74
+			}
75
+			if len(medias) > 0 {
76
+				mediaMap := make(map[string]string, 0)
77
+				for _, media := range medias {
78
+					mediaMap[media.MediaId] = media.MediaUrl
79
+				}
80
+				for index, menu := range menus.Button {
81
+					if menu.Type == "media_id" && len(menu.MediaId) > 0 {
82
+						if _, exist := mediaMap[menu.MediaId]; exist {
83
+							menus.Button[index].MediaUrl = mediaMap[menu.MediaId]
84
+						}
85
+					}
86
+					for sindex, sub := range menus.Button[index].SubButton {
87
+						if sub.Type == "media_id" && len(sub.MediaId) > 0 {
88
+							if _, exist := mediaMap[sub.MediaId]; exist {
89
+								menus.Button[index].SubButton[sindex].MediaUrl = mediaMap[sub.MediaId]
90
+							}
91
+						}
92
+					}
93
+				}
94
+			}
95
+
96
+		}
60 97
 
61 98
 	}
62 99
 
@@ -188,3 +225,52 @@ func (c *MpMenusAPIController) SaveMenus() {
188 225
 	c.ServeSuccessJSON(returnData)
189 226
 	return
190 227
 }
228
+
229
+func (c *MpMenusAPIController) DeleteMenu() {
230
+
231
+	adminUserInfo := c.GetAdminUserInfo()
232
+	authorization, err := wechat_service.GetAuthorizationByOrgID(adminUserInfo.CurrentOrgId)
233
+	if err != nil {
234
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "删除菜单失败:("+err.Error()+")")
235
+		return
236
+	}
237
+	if authorization == nil || authorization.AuthorizerStatus != 1 {
238
+		c.ServeFailJsonSend(enums.ErrorCodeDBUpdate, "删除菜单失败:(未授权)")
239
+		return
240
+	}
241
+
242
+	// 授权方公众号类型,0代表订阅号,1代表由历史老帐号升级后的订阅号,2代表服务号
243
+	// 授权方认证类型,-1代表未认证,0代表微信认证,1代表新浪微博认证,2代表腾讯微博认证,3代表已资质认证通过但还未通过名称认证,4代表已资质认证通过、还未通过名称认证,但通过了新浪微博认证,5代表已资质认证通过、还未通过名称认证,但通过了腾讯微博认证
244
+	if (authorization.AuthorizerServiceTypeInfo == 0 || authorization.AuthorizerServiceTypeInfo == 1) && authorization.AuthorizerVerifyTypeInfo == -1 {
245
+		c.ServeFailJsonSend(enums.ErrorCodeDBUpdate, "删除菜单失败:(未认证的订阅号没有自定义菜单相关接口的权限)")
246
+		return
247
+	}
248
+
249
+	button, err := wechat_service.GetMenusByOrgID(adminUserInfo.CurrentOrgId)
250
+	if err != nil {
251
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "删除菜单失败:("+err.Error()+")")
252
+		return
253
+	}
254
+	if button == nil || button.ButtonStatus != 1 {
255
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "删除菜单失败:(未设置菜单)")
256
+		return
257
+	}
258
+
259
+	err = wechat_service.DeleteMpWechatMenus(authorization.AuthorizerAccessToken)
260
+	if err != nil {
261
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "删除菜单失败:("+err.Error()+")")
262
+		return
263
+	}
264
+
265
+	err = wechat_service.DeleteButton(adminUserInfo.CurrentOrgId, button.ID)
266
+	if err != nil {
267
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "删除菜单失败:("+err.Error()+")")
268
+		return
269
+	}
270
+
271
+	returnData := make(map[string]interface{}, 0)
272
+	returnData["msg"] = "ok"
273
+	c.ServeSuccessJSON(returnData)
274
+	return
275
+
276
+}

+ 318 - 0
controllers/mpwechat/reply_controller.go View File

@@ -0,0 +1,318 @@
1
+package mpwechat
2
+
3
+import (
4
+	base_ctl "SCRM/controllers"
5
+	"SCRM/enums"
6
+	"SCRM/models"
7
+	"SCRM/service/wechat_service"
8
+	"encoding/json"
9
+	"reflect"
10
+
11
+	"SCRM/utils"
12
+	"time"
13
+
14
+	"github.com/astaxie/beego"
15
+)
16
+
17
+func MpReplyCtlRegistRouters() {
18
+	beego.Router("/api/mpwechat/reply/subscribe", &MpReplyAPIController{}, "Get:GetSubscribe")
19
+	beego.Router("/api/mpwechat/reply/subscribe", &MpReplyAPIController{}, "Put:SaveSubscribe")
20
+	beego.Router("/api/mpwechat/reply/kewords", &MpReplyAPIController{}, "Get:GetKeyWordReplys")
21
+	beego.Router("/api/mpwechat/reply/keword", &MpReplyAPIController{}, "Post:CreateKeyWordReply")
22
+	beego.Router("/api/mpwechat/reply/keword", &MpReplyAPIController{}, "Put:EditKeyWordReply")
23
+	beego.Router("/api/mpwechat/replys", &MpReplyAPIController{}, "Delete:DeleteReplys")
24
+}
25
+
26
+type MpReplyAPIController struct {
27
+	base_ctl.BaseAuthAPIController
28
+}
29
+
30
+func (c *MpReplyAPIController) GetSubscribe() {
31
+
32
+	adminUserInfo := c.GetAdminUserInfo()
33
+
34
+	message, err := wechat_service.GetOrgSubscribeReplyMessages(adminUserInfo.CurrentOrgId)
35
+	if err != nil {
36
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "读取关注回复数据失败:("+err.Error()+")")
37
+		return
38
+	}
39
+
40
+	returnData := make(map[string]interface{}, 0)
41
+	returnData["message"] = message
42
+	c.ServeSuccessJSON(returnData)
43
+	return
44
+}
45
+
46
+func (c *MpReplyAPIController) SaveSubscribe() {
47
+	adminUserInfo := c.GetAdminUserInfo()
48
+	timeNow := time.Now().Unix()
49
+
50
+	dataBody := make(map[string]interface{}, 0)
51
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &dataBody)
52
+	if err != nil {
53
+		utils.ErrorLog(err.Error())
54
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
55
+		return
56
+	}
57
+
58
+	var saveMessage models.AuthorizationMessageManagements
59
+	message, err := wechat_service.GetOrgSubscribeReplyMessages(adminUserInfo.CurrentOrgId)
60
+	if err != nil {
61
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "保存失败:("+err.Error()+")")
62
+		return
63
+	}
64
+	if message != nil {
65
+		saveMessage = *message
66
+	} else {
67
+		saveMessage.CreatedTime = timeNow
68
+		saveMessage.MessageMsgType = "event"
69
+		saveMessage.MessageMsgEvent = "subscribe"
70
+		saveMessage.MessageStatus = 1
71
+		saveMessage.UserOrgId = adminUserInfo.CurrentOrgId
72
+	}
73
+
74
+	if dataBody["message_content"] == nil || reflect.TypeOf(dataBody["message_content"]).String() != "string" {
75
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_content")
76
+		return
77
+	}
78
+	messageContent, _ := dataBody["message_content"].(string)
79
+	saveMessage.MessageContent = messageContent
80
+	saveMessage.UpdatedTime = timeNow
81
+
82
+	err = wechat_service.SaveMessage(&saveMessage)
83
+	if err != nil {
84
+		c.ServeFailJsonSend(enums.ErrorCodeDBUpdate, "保存失败:("+err.Error()+")")
85
+		return
86
+	}
87
+
88
+	returnData := make(map[string]interface{}, 0)
89
+	returnData["message"] = saveMessage
90
+	c.ServeSuccessJSON(returnData)
91
+	return
92
+
93
+}
94
+
95
+func (c *MpReplyAPIController) GetKeyWordReplys() {
96
+	page, _ := c.GetInt64("page", 1)
97
+	limit, _ := c.GetInt64("limit", 10)
98
+	searchKey := c.GetString("search", "")
99
+	if page <= 0 {
100
+		page = 1
101
+	}
102
+	if limit <= 0 {
103
+		limit = 10
104
+	}
105
+
106
+	adminUserInfo := c.GetAdminUserInfo()
107
+	messages, total, err := wechat_service.GetPageKeyWordMessages(adminUserInfo.CurrentOrgId, page, limit, searchKey)
108
+	if err != nil {
109
+		c.ServeFailJsonSend(enums.ErrorCodeDBUpdate, "拉取失败:("+err.Error()+")")
110
+		return
111
+	}
112
+
113
+	returnData := make(map[string]interface{}, 0)
114
+	returnData["messages"] = messages
115
+	returnData["total"] = total
116
+	c.ServeSuccessJSON(returnData)
117
+	return
118
+}
119
+
120
+func (c *MpReplyAPIController) CreateKeyWordReply() {
121
+	adminUserInfo := c.GetAdminUserInfo()
122
+	timeNow := time.Now().Unix()
123
+
124
+	dataBody := make(map[string]interface{}, 0)
125
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &dataBody)
126
+	if err != nil {
127
+		utils.ErrorLog(err.Error())
128
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
129
+		return
130
+	}
131
+
132
+	var message models.AuthorizationMessageManagements
133
+
134
+	if dataBody["message_regular_name"] == nil || reflect.TypeOf(dataBody["message_regular_name"]).String() != "string" {
135
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_regular_name")
136
+		return
137
+	}
138
+	messageRegularName, _ := dataBody["message_regular_name"].(string)
139
+	if len(messageRegularName) == 0 {
140
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "规则名称不能为空")
141
+		return
142
+	}
143
+	message.MessageRegularName = messageRegularName
144
+
145
+	if dataBody["message_key_type"] == nil || reflect.TypeOf(dataBody["message_key_type"]).String() != "float64" {
146
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_key_type")
147
+		return
148
+	}
149
+	messageKeyType := int64(dataBody["message_key_type"].(float64))
150
+	if messageKeyType != 1 && messageKeyType != 2 {
151
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "模式选择不正确")
152
+		return
153
+	}
154
+	message.MessageKeyType = messageKeyType
155
+
156
+	if dataBody["message_key_name"] == nil || reflect.TypeOf(dataBody["message_key_name"]).String() != "string" {
157
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_key_name")
158
+		return
159
+	}
160
+	messageKeyName, _ := dataBody["message_key_name"].(string)
161
+	if len(messageKeyName) == 0 {
162
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "关键字不能为空")
163
+		return
164
+	}
165
+	message.MessageKeyName = messageKeyName
166
+
167
+	if dataBody["message_content"] == nil || reflect.TypeOf(dataBody["message_content"]).String() != "string" {
168
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_content")
169
+		return
170
+	}
171
+	messageContent, _ := dataBody["message_content"].(string)
172
+	if len(messageKeyName) == 0 {
173
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "回复内容不能为空")
174
+		return
175
+	}
176
+	message.MessageContent = messageContent
177
+
178
+	message.MessageMsgType = "text"
179
+	message.UpdatedTime = timeNow
180
+	message.CreatedTime = timeNow
181
+	message.MessageStatus = 1
182
+	message.UserOrgId = adminUserInfo.CurrentOrgId
183
+
184
+	err = wechat_service.SaveMessage(&message)
185
+	if err != nil {
186
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "添加回复失败:("+err.Error()+")")
187
+		return
188
+	}
189
+
190
+	returnData := make(map[string]interface{}, 0)
191
+	returnData["message"] = message
192
+	c.ServeSuccessJSON(returnData)
193
+	return
194
+}
195
+
196
+func (c *MpReplyAPIController) EditKeyWordReply() {
197
+	id, _ := c.GetInt64("id", 0)
198
+	if id <= 0 {
199
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误:id")
200
+		return
201
+	}
202
+
203
+	adminUserInfo := c.GetAdminUserInfo()
204
+	timeNow := time.Now().Unix()
205
+
206
+	dataBody := make(map[string]interface{}, 0)
207
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &dataBody)
208
+	if err != nil {
209
+		utils.ErrorLog(err.Error())
210
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
211
+		return
212
+	}
213
+
214
+	message, err := wechat_service.GetMessageByID(adminUserInfo.CurrentOrgId, id)
215
+	if err != nil {
216
+		c.ServeFailJsonSend(enums.ErrorCodeDataException, "保存失败:("+err.Error()+")")
217
+		return
218
+	}
219
+	if message == nil {
220
+		c.ServeFailJsonSend(enums.ErrorCodeDBUpdate, "保存失败:(回复不存在)")
221
+		return
222
+	}
223
+
224
+	if dataBody["message_regular_name"] == nil || reflect.TypeOf(dataBody["message_regular_name"]).String() != "string" {
225
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_regular_name")
226
+		return
227
+	}
228
+	messageRegularName, _ := dataBody["message_regular_name"].(string)
229
+	if len(messageRegularName) == 0 {
230
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "规则名称不能为空")
231
+		return
232
+	}
233
+	message.MessageRegularName = messageRegularName
234
+
235
+	if dataBody["message_key_type"] == nil || reflect.TypeOf(dataBody["message_key_type"]).String() != "float64" {
236
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_key_type")
237
+		return
238
+	}
239
+	messageKeyType := int64(dataBody["message_key_type"].(float64))
240
+	if messageKeyType != 1 && messageKeyType != 2 {
241
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "模式选择不正确")
242
+		return
243
+	}
244
+	message.MessageKeyType = messageKeyType
245
+
246
+	if dataBody["message_key_name"] == nil || reflect.TypeOf(dataBody["message_key_name"]).String() != "string" {
247
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_key_name")
248
+		return
249
+	}
250
+	messageKeyName, _ := dataBody["message_key_name"].(string)
251
+	if len(messageKeyName) == 0 {
252
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "关键字不能为空")
253
+		return
254
+	}
255
+	message.MessageKeyName = messageKeyName
256
+
257
+	if dataBody["message_content"] == nil || reflect.TypeOf(dataBody["message_content"]).String() != "string" {
258
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "缺少参数:message_content")
259
+		return
260
+	}
261
+	messageContent, _ := dataBody["message_content"].(string)
262
+	if len(messageKeyName) == 0 {
263
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "回复内容不能为空")
264
+		return
265
+	}
266
+	message.MessageContent = messageContent
267
+
268
+	message.UpdatedTime = timeNow
269
+
270
+	err = wechat_service.SaveMessage(message)
271
+	if err != nil {
272
+		c.ServeFailJsonSend(enums.ErrorCodeDBCreate, "编辑回复失败:("+err.Error()+")")
273
+		return
274
+	}
275
+
276
+	returnData := make(map[string]interface{}, 0)
277
+	returnData["message"] = message
278
+	c.ServeSuccessJSON(returnData)
279
+	return
280
+}
281
+
282
+func (c *MpReplyAPIController) DeleteReplys() {
283
+	adminUserInfo := c.GetAdminUserInfo()
284
+
285
+	dataBody := make(map[string]interface{}, 0)
286
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &dataBody)
287
+	if err != nil {
288
+		utils.ErrorLog(err.Error())
289
+		c.ServeFailJsonSend(enums.ErrorCodeParamWrong, "参数错误")
290
+		return
291
+	}
292
+
293
+	idsInters := dataBody["ids"].([]interface{})
294
+	if len(idsInters) == 0 {
295
+		if err != nil {
296
+			c.ServeFailJsonSend(enums.ErrorCodeDBDelete, "删除失败:(没有选择回复)")
297
+			return
298
+		}
299
+	}
300
+
301
+	ids := make([]int64, 0)
302
+	for _, idsInter := range idsInters {
303
+		id := int64(idsInter.(float64))
304
+		ids = append(ids, id)
305
+	}
306
+
307
+	err = wechat_service.DeleteMessages(adminUserInfo.CurrentOrgId, ids)
308
+	if err != nil {
309
+		c.ServeFailJsonSend(enums.ErrorCodeDBDelete, "删除失败:("+err.Error()+")")
310
+		return
311
+	}
312
+
313
+	returnData := make(map[string]interface{}, 0)
314
+	returnData["msg"] = "ok"
315
+
316
+	c.ServeSuccessJSON(returnData)
317
+	return
318
+}

+ 2 - 0
controllers/mpwechat/router_controller.go View File

@@ -3,4 +3,6 @@ package mpwechat
3 3
 func RegisterRouters() {
4 4
 	MpWechatCtlRegistRouters()
5 5
 	MpMenusCtlRegistRouters()
6
+	MpReplyCtlRegistRouters()
7
+	MpMaterialCtlRegistRouters()
6 8
 }

+ 6 - 2
enums/error_code.go View File

@@ -44,7 +44,9 @@ const ( // ErrorCode
44 44
 	ErrorCodeCannotRemoveRole       = 9006
45 45
 	ErrorCodeRoleMobileIsSuperAdmin = 9007
46 46
 
47
-	ErrorCodeActivityNotExist = 9100
47
+	ErrorCodeActivityNotExist              = 9100
48
+	ErrorCodeActivityPublishedCannotModify = 9101
49
+	ErrorCodeActivityPublishRepeat         = 9102
48 50
 
49 51
 	ErrorCodeGetQiniuUpToken = 1001
50 52
 
@@ -99,7 +101,9 @@ var ErrCodeMsgs = map[int]string{
99 101
 	ErrorCodeCannotRemoveRole:       "存在该角色的管理员,不能删除该角色",
100 102
 	ErrorCodeRoleMobileIsSuperAdmin: "该手机号已注册为超级管理员",
101 103
 
102
-	ErrorCodeActivityNotExist: "活动不存在",
104
+	ErrorCodeActivityNotExist:              "活动不存在",
105
+	ErrorCodeActivityPublishedCannotModify: "已发布的活动不可更改",
106
+	ErrorCodeActivityPublishRepeat:         "该活动已发布",
103 107
 
104 108
 	ErrorCodeGetQiniuUpToken: "获取七牛uptoken失败",
105 109
 

+ 9 - 9
jobcron/open_wechat_job.go View File

@@ -155,26 +155,26 @@ func RequestMpWechatAccessToken() {
155 155
 		var responseBytes []byte
156 156
 		responseBytes, err = util.PostJSON(uri, ReqRefreshToken)
157 157
 		if err != nil {
158
-			utils.ErrorLog(" error: %s", err)
159
-			return
158
+			utils.ErrorLog("error: %s", err)
159
+			continue
160 160
 		}
161 161
 
162 162
 		var res wechat_service.MPResult
163 163
 		err = json.Unmarshal(responseBytes, &res)
164 164
 		if err != nil {
165
-			utils.ErrorLog(" error: %s", err)
166
-			return
165
+			utils.ErrorLog("%s, error: %s", item.AuthorizerAppid, err)
166
+			continue
167 167
 		}
168 168
 
169 169
 		if res.ErrCode > 0 {
170
-			utils.ErrorLog(" error: %s", res.ErrMsg)
171
-			return
170
+			utils.ErrorLog("%s, error: %s", item.AuthorizerAppid, res.ErrMsg)
171
+			continue
172 172
 		}
173 173
 
174 174
 		var accessToken wechat_service.RefreshToken
175 175
 		err = json.Unmarshal(responseBytes, &accessToken)
176 176
 		if err != nil {
177
-			utils.ErrorLog("Unmarshal error: %s", err)
177
+			utils.ErrorLog("%s, Unmarshal error: %s", item.AuthorizerAppid, err)
178 178
 			continue
179 179
 		}
180 180
 
@@ -184,14 +184,14 @@ func RequestMpWechatAccessToken() {
184 184
 		info.UpdatedTime = time.Now().Unix()
185 185
 		err = wechat_service.SaveAuthorizationInfo(&info)
186 186
 		if err != nil {
187
-			utils.ErrorLog("Save error: %s", err)
187
+			utils.ErrorLog("%s,Save error: %s", item.AuthorizerAppid, err)
188 188
 			continue
189 189
 		}
190 190
 
191 191
 		atKey := fmt.Sprintf("Patient:AccessToken[%s]", item.AuthorizerAppid)
192 192
 		_, err = redisClient.Set(atKey, accessToken.AuthorizerAccessToken, 0).Result()
193 193
 		if err != nil {
194
-			utils.ErrorLog("Save redis error: %s", err)
194
+			utils.ErrorLog("%s,Save redis error: %s", item.AuthorizerAppid, err)
195 195
 			continue
196 196
 		}
197 197
 	}

+ 11 - 11
models/activity_models.go View File

@@ -51,17 +51,17 @@ func (ActivityParagraph) TableName() string {
51 51
 }
52 52
 
53 53
 type ActivityUser struct {
54
-	Id          int    `gorm:"PRIMARY_KEY;AUTO_INCREMENT"` // 记录 ID
55
-	ActivityId  int    `gorm:"column:activity_id"`         // 活动 ID
56
-	UserId      int    `gorm:"column:user_id"`             // 参与者 ID
57
-	IDCardNo    string `gorm:"column:idcard_no"`           // 身份证号
58
-	Mobile      string
59
-	Realname    string
60
-	Avatar      string
61
-	AvatarThumb string
62
-	Status      int8  // 状态 0.无效 1.有效
63
-	CreateTime  int64 `gorm:"column:ctime"` // 报名时间
64
-	ModifyTime  int64 `gorm:"column:mtime"` // 修改时间
54
+	Id          int    `gorm:"PRIMARY_KEY;AUTO_INCREMENT" json:"id"` // 记录 ID
55
+	ActivityId  int    `gorm:"column:activity_id" json:"-"`          // 活动 ID
56
+	UserId      int    `gorm:"column:user_id" json:"-"`              // 参与者 ID
57
+	IDCardNo    string `gorm:"column:idcard_no" json:"idcard_no"`    // 身份证号
58
+	Mobile      string `json:"mobile"`
59
+	Realname    string `json:"real_name"`
60
+	Avatar      string `json:"avatar"`
61
+	AvatarThumb string `json:"avatar_thumb"`
62
+	Status      int8   `json:"-"`                         // 状态 0.无效 1.有效
63
+	CreateTime  int64  `gorm:"column:ctime" json:"ctime"` // 报名时间
64
+	ModifyTime  int64  `gorm:"column:mtime" json:"-"`     // 修改时间
65 65
 }
66 66
 
67 67
 func (ActivityUser) TableName() string {

+ 17 - 0
models/wechat_models.go View File

@@ -72,3 +72,20 @@ type AuthorizationButtons struct {
72 72
 func (AuthorizationButtons) TableName() string {
73 73
 	return "sgj_patient_authorization_buttons"
74 74
 }
75
+
76
+type WechatMedias struct {
77
+	ID               int64  `gorm:"column:id" json:"id" form:"id"`
78
+	UserOrgId        int64  `gorm:"column:user_org_id" json:"user_org_id" form:"user_org_id"`
79
+	Msgtype          string `gorm:"column:msgtype" json:"msgtype" form:"msgtype"`
80
+	MediaId          string `gorm:"column:media_id" json:"media_id" form:"media_id"`
81
+	MediaTitle       string `gorm:"column:media_title" json:"media_title" form:"media_title"`
82
+	MediaDescription string `gorm:"column:media_description" json:"media_description" form:"media_description"`
83
+	MediaUrl         string `gorm:"column:media_url" json:"media_url" form:"media_url"`
84
+	UpdatedTime      int64  `gorm:"column:updated_time" json:"updated_time" form:"updated_time"`
85
+	MediaStatus      int64  `gorm:"column:media_status" json:"media_status" form:"media_status"`
86
+	CreatedTime      int64  `gorm:"column:created_time" json:"created_time" form:"created_time"`
87
+}
88
+
89
+func (WechatMedias) TableName() string {
90
+	return "sgj_patient_wechat_medias"
91
+}

+ 5 - 0
routers/router.go View File

@@ -9,6 +9,7 @@ import (
9 9
 	"SCRM/controllers/members"
10 10
 	"SCRM/controllers/mpwechat"
11 11
 	"SCRM/controllers/role"
12
+	"SCRM/controllers/kefu"
12 13
 
13 14
 	"github.com/astaxie/beego"
14 15
 	"github.com/astaxie/beego/plugins/cors"
@@ -32,5 +33,9 @@ func init() {
32 33
 	marketing_tool.RegisterRouters()
33 34
 	article.RegisterRouters()
34 35
 	mpwechat.RegisterRouters()
36
+<<<<<<< .mine
35 37
 	staff.RegisterRouters()
38
+=======
39
+	kefu.RegisterRouters()
40
+>>>>>>> .theirs
36 41
 }

+ 110 - 0
service/marketing_tool_service/activity_service.go View File

@@ -164,6 +164,116 @@ func SaveActivityAndParagraph(activity *models.Activity, paragraph *models.Activ
164 164
 func SaveActivityWxShare(model *models.ActivityWxShare) error {
165 165
 	return service.PatientWriteDB().Save(model).Error
166 166
 }
167
+<<<<<<< .mine
167 168
 
168 169
 
169 170
 
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+=======
224
+
225
+func GetValidActivitySignupUsers(activityID int64, keyWord string, page int) ([]*models.ActivityUser, int, error) {
226
+	var users []*models.ActivityUser
227
+	countPerPage := 10
228
+	var totalCount int
229
+	db := service.PatientReadDB().Model(&models.ActivityUser{}).Where("activity_id = ? AND status = 1", activityID)
230
+	if len(keyWord) > 0 {
231
+		likeParam := "%" + keyWord + "%"
232
+		db = db.Where("mobile LIKE ? OR realname LIKE ?", likeParam, likeParam)
233
+	}
234
+	err := db.
235
+		Count(&totalCount).
236
+		Order("id asc").Limit(countPerPage).Offset((page - 1) * countPerPage).
237
+		Find(&users).
238
+		Error
239
+
240
+	if err == nil {
241
+		return users, totalCount, nil
242
+	} else {
243
+		return nil, 0, err
244
+	}
245
+}
246
+
247
+func DeleteActivity(orgID int64, activityID int64) error {
248
+	tx := service.PatientWriteDB().Begin()
249
+	deleteActivityErr := tx.Model(&models.Activity{}).Where("user_org_id = ? AND id = ?", orgID, activityID).Updates(map[string]interface{}{
250
+		"status": 9,
251
+		"mtime":  time.Now().Unix(),
252
+	}).Error
253
+	if deleteActivityErr != nil {
254
+		tx.Rollback()
255
+		return deleteActivityErr
256
+	}
257
+
258
+	// deletePErr := tx.Model(&models.ActivityParagraph{}).Where("activity_id = ?", activityID).Updates(map[string]interface{}{
259
+	// 	"status": 0,
260
+	// 	"mtime":  time.Now().Unix(),
261
+	// }).Error
262
+	// if deletePErr != nil {
263
+	// 	tx.Rollback()
264
+	// 	return deletePErr
265
+	// }
266
+
267
+	// deleteShareErr := tx.Model(&models.ActivityWxShare{}).Where("activity_id = ?", activityID).Updates(map[string]interface{}{
268
+	// 	"status": 0,
269
+	// 	"mtime":  time.Now().Unix(),
270
+	// }).Error
271
+	// if deleteShareErr != nil {
272
+	// 	tx.Rollback()
273
+	// 	return deleteShareErr
274
+	// }
275
+
276
+	tx.Commit()
277
+	return nil
278
+}
279
+>>>>>>> .theirs

+ 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
+}

+ 26 - 0
service/wechat_service/material_service.go View File

@@ -0,0 +1,26 @@
1
+package wechat_service
2
+
3
+import (
4
+	"SCRM/models"
5
+	"SCRM/service"
6
+)
7
+
8
+func SaveMaterial(m *models.WechatMedias) (err error) {
9
+	err = service.PatientWriteDB().Save(m).Error
10
+	return err
11
+}
12
+
13
+func FindMeterialsByMediaIDs(orgID int64, mediaType string, mediaIDs []string) (medias []models.WechatMedias, err error) {
14
+	err = service.PatientReadDB().Model(&models.WechatMedias{}).Where("media_id IN (?) and user_org_id=? AND msgtype=? and media_status=1", mediaIDs, orgID, mediaType).Limit(len(mediaIDs)).Find(&medias).Error
15
+	return
16
+}
17
+
18
+func GetPageMaterials(orgID, page, limit int64, mediaType string) (medias []*models.WechatMedias, total int64, err error) {
19
+	db := service.PatientReadDB().Model(&models.WechatMedias{}).Where("user_org_id=? and msgtype=? and media_status=1", orgID, mediaType)
20
+
21
+	offset := (page - 1) * limit
22
+
23
+	err = db.Count(&total).Order("id desc").Offset(offset).Limit(limit).Find(&medias).Error
24
+
25
+	return
26
+}

+ 4 - 0
service/wechat_service/menu_service.go View File

@@ -61,3 +61,7 @@ func SaveButtons(menu *Buttons) (err error) {
61 61
 	tx.Commit()
62 62
 	return
63 63
 }
64
+func DeleteButton(orgID, id int64) (err error) {
65
+	err = service.PatientWriteDB().Model(&models.AuthorizationButtons{}).Where("id=? and user_org_id=?", id, orgID).Update(map[string]interface{}{"ButtonStatus": 2, "UpdatedTime": time.Now().Unix()}).Error
66
+	return
67
+}

+ 44 - 2
service/wechat_service/messages_service.go View File

@@ -3,6 +3,7 @@ package wechat_service
3 3
 import (
4 4
 	"SCRM/models"
5 5
 	"SCRM/service"
6
+	"time"
6 7
 
7 8
 	"github.com/jinzhu/gorm"
8 9
 )
@@ -18,8 +19,8 @@ func GetTextReplyMessagesByKey(userOrgID int64, keywrods string) (messages []*mo
18 19
 	return
19 20
 }
20 21
 
21
-//GetSubscribeReplyMessagesByOrgID 通过user_org_id(机构ID)取信息
22
-func GetSubscribeReplyMessagesByOrgID(orgID int64) (*models.AuthorizationMessageManagements, error) {
22
+//GetOrgSubscribeReplyMessages 通过user_org_id(机构ID)取信息
23
+func GetOrgSubscribeReplyMessages(orgID int64) (*models.AuthorizationMessageManagements, error) {
23 24
 
24 25
 	var message models.AuthorizationMessageManagements
25 26
 
@@ -51,3 +52,44 @@ func GetOrgAllClickMessages(orgID int64) (messages []*models.AuthorizationMessag
51 52
 	err = service.PatientReadDB().Where("user_org_id=? AND message_msg_type='event' AND message_msg_event='click'", orgID).Find(&messages).Error
52 53
 	return
53 54
 }
55
+
56
+func SaveMessage(m *models.AuthorizationMessageManagements) (err error) {
57
+	err = service.PatientWriteDB().Save(m).Error
58
+	return
59
+}
60
+
61
+func GetPageKeyWordMessages(orgID, page, limit int64, search string) (messages []*models.AuthorizationMessageManagements, total int64, err error) {
62
+	db := service.PatientReadDB().Model(&models.AuthorizationMessageManagements{}).Where("user_org_id=? and message_msg_type='text' and message_status=1", orgID)
63
+
64
+	if len(search) > 0 {
65
+		searchKey := "%" + search + "%"
66
+		db = db.Where("message_key_name LIKE ?", searchKey)
67
+	}
68
+	offset := (page - 1) * limit
69
+
70
+	err = db.Count(&total).Order("id desc").Offset(offset).Limit(limit).Find(&messages).Error
71
+
72
+	return
73
+}
74
+
75
+//GetMessageByID 通过ID取信息
76
+func GetMessageByID(orgID, id int64) (*models.AuthorizationMessageManagements, error) {
77
+	var message models.AuthorizationMessageManagements
78
+	err := service.PatientReadDB().Where("id=? and user_org_id=? and message_status=1", id, orgID).First(&message).Error
79
+	if err == gorm.ErrRecordNotFound {
80
+		return nil, nil
81
+	}
82
+	if err != nil {
83
+		return nil, err
84
+	}
85
+	return &message, nil
86
+}
87
+
88
+func DeleteMessages(orgID int64, ids []int64) (err error) {
89
+	if len(ids) == 1 {
90
+		err = service.PatientWriteDB().Model(&models.AuthorizationMessageManagements{}).Where("id =? and user_org_id =?", ids[0], orgID).Update(map[string]interface{}{"MessageStatus": 2, "UpdatedTime": time.Now().Unix()}).Error
91
+	} else {
92
+		err = service.PatientWriteDB().Model(&models.AuthorizationMessageManagements{}).Where("id IN (?) and user_org_id =?", ids, orgID).Update(map[string]interface{}{"MessageStatus": 2, "UpdatedTime": time.Now().Unix()}).Error
93
+	}
94
+	return
95
+}

+ 9 - 0
service/wechat_service/open_wechat_model.go View File

@@ -82,6 +82,14 @@ type MPResult struct {
82 82
 	ErrMsg  string `json:"errmsg"`
83 83
 }
84 84
 
85
+//ResAddMaterial 永久性素材上传返回的结果
86
+type ResAddMaterial struct {
87
+	MPResult
88
+
89
+	MediaID string `json:"media_id"`
90
+	URL     string `json:"url"`
91
+}
92
+
85 93
 //ComponentAccessToken 第三方平台主动向微信服务器请求第三方平台自己的component_access_token返回的数据结构
86 94
 type ComponentAccessToken struct {
87 95
 	ComponentAccessToken string `json:"component_access_token"`
@@ -159,6 +167,7 @@ type SelfMenuButton struct {
159 167
 	Message   string           `json:"message,omitempty"`
160 168
 	URL       string           `json:"url,omitempty"`
161 169
 	MediaId   string           `json:"media_id,omitempty"`
170
+	MediaUrl  string           `json:"media_url,omitempty"`
162 171
 	SubButton []SelfMenuButton `json:"sub_button,omitempty"`
163 172
 }
164 173
 

+ 7 - 6
service/wechat_service/open_wechat_service.go View File

@@ -260,7 +260,7 @@ func SendSubscribeTextMessage(appid string, ToUserName string, FromUserName stri
260 260
 		return
261 261
 	}
262 262
 
263
-	message, err := GetSubscribeReplyMessagesByOrgID(arthorizer.UserOrgId)
263
+	message, err := GetOrgSubscribeReplyMessages(arthorizer.UserOrgId)
264 264
 	if err != nil {
265 265
 		utils.ErrorLog("SendSubscribeTextMessage error:%s", err)
266 266
 		Ctx.WriteString("success")
@@ -479,17 +479,18 @@ func SendMpWechatMenus(AuthorizerAccessToken string, jsonData []byte) (err error
479 479
 
480 480
 }
481 481
 
482
-func DeleteMpWechatMenus(AuthorizerAccessToken string) (bool, error) {
482
+func DeleteMpWechatMenus(AuthorizerAccessToken string) (err error) {
483 483
 	uri := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=%s", AuthorizerAccessToken)
484 484
 
485 485
 	b := httplib.Get(uri)
486 486
 	var result MPResult
487
-	err := b.ToJSON(&result)
487
+	err = b.ToJSON(&result)
488 488
 	if err != nil {
489
-		return false, err
489
+		return
490 490
 	}
491 491
 	if result.ErrCode > 0 {
492
-		return false, errors.New(result.ErrMsg)
492
+		err = errors.New(result.ErrMsg)
493
+		return
493 494
 	}
494
-	return true, nil
495
+	return
495 496
 }

+ 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
+}