first commit
This commit is contained in:
15
pkg/client/configs/mail-template-reset.html
Normal file
15
pkg/client/configs/mail-template-reset.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>{{.Title}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Hello!</p>
|
||||
<p>This is a password reset email.</p>
|
||||
<a href="{{.Url}}">{{.Url}}</a>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
225
pkg/client/game/wz/wz_client.go
Normal file
225
pkg/client/game/wz/wz_client.go
Normal file
@ -0,0 +1,225 @@
|
||||
package client_game_wz
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"servicebase/pkg/utils"
|
||||
|
||||
"github.com/anxpp/common-utils/logg"
|
||||
"github.com/anxpp/common-utils/str"
|
||||
)
|
||||
|
||||
const WZYD_TOKEN = "SZN62jr4"
|
||||
const WZYD_UID = "1702857155"
|
||||
|
||||
func RoleInfo(targetUid string) (targetRole map[string]interface{}, e error) {
|
||||
if len(targetUid) == 0 {
|
||||
e = errors.New("参数不足")
|
||||
return
|
||||
}
|
||||
apiUrl := "https://kohcamp.qq.com/game/koh/profile"
|
||||
token := WZYD_TOKEN // 登录王者营地的token
|
||||
uid := WZYD_UID // 营地ID
|
||||
headerMap := make(map[string]string, 0)
|
||||
headerMap["Content-Type"] = "application/json"
|
||||
headerMap["userId"] = uid
|
||||
headerMap["cCurrentGameId"] = "20001"
|
||||
headerMap["Accept-Language"] = "zh-Hans-CN;q=1, en-CN;q=0.9"
|
||||
headerMap["token"] = token
|
||||
|
||||
paraMap := make(map[string]string, 4)
|
||||
paraMap["CampPreloadHTTPHandledIdentifier"] = "2"
|
||||
paraMap["targetUserId"] = targetUid
|
||||
|
||||
resStr, err2 := utils.HttpDo(http.MethodPost, apiUrl, headerMap, str.ToJsonString(paraMap))
|
||||
|
||||
if err2 != nil {
|
||||
logg.Info("调用王者营地数据查询失败", err2.Error())
|
||||
e = fmt.Errorf("调用王者营地数据查询失败: %v", err2.Error())
|
||||
return
|
||||
}
|
||||
|
||||
logg.Info(resStr)
|
||||
|
||||
resMap := make(map[string]interface{}, 0)
|
||||
json.Unmarshal([]byte(resStr), &resMap)
|
||||
|
||||
if resMap["returnCode"].(float64) != 0 {
|
||||
e = fmt.Errorf("调用王者营地数据查询失败2: %v", resMap["returnMsg"])
|
||||
return
|
||||
}
|
||||
|
||||
// 获取成功
|
||||
data := resMap["data"].(map[string]interface{})
|
||||
// 默认角色ID
|
||||
targetRoleId := data["targetRoleId"].(string)
|
||||
|
||||
//角色列表
|
||||
roleList := data["roleList"].([]interface{})
|
||||
|
||||
// 匹配默认角色
|
||||
for _, item := range roleList {
|
||||
m := item.(map[string]interface{})
|
||||
if m["roleId"].(string) == targetRoleId {
|
||||
targetRole = m
|
||||
}
|
||||
}
|
||||
if targetRole == nil {
|
||||
e = errors.New("未找到角色信息")
|
||||
return
|
||||
}
|
||||
|
||||
// 其他数据
|
||||
head := data["head"].(map[string]interface{})
|
||||
mods := head["mods"].([]interface{})
|
||||
//targetRole["mods"] = mods
|
||||
|
||||
for _, item := range mods {
|
||||
mod := item.(map[string]interface{})
|
||||
modId := mod["modId"].(float64)
|
||||
if modId == 401 {
|
||||
// 总场次
|
||||
targetRole["totalPlayCount"] = mod["content"].(string)
|
||||
} else if modId == 408 {
|
||||
// MVP
|
||||
targetRole["mvpCount"] = mod["content"].(string)
|
||||
} else if modId == 409 {
|
||||
// 胜率
|
||||
targetRole["winRate"] = mod["content"].(string)
|
||||
} else if modId == 201 {
|
||||
// 英雄数
|
||||
targetRole["heroCount"] = mod["content"].(string)
|
||||
} else if modId == 202 {
|
||||
// 皮肤数
|
||||
targetRole["skinCount"] = mod["content"].(string)
|
||||
} else if modId == 702 {
|
||||
// 巅峰赛
|
||||
targetRole["topScore"] = mod["content"].(string)
|
||||
} else if modId == 304 {
|
||||
// 战力
|
||||
targetRole["battleScore"] = mod["content"].(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RoleSkinList(roleId string) (result map[string][]interface{}, e error) {
|
||||
apiUrl := "https://kohcamp.qq.com/game/itempage/skinlist"
|
||||
token := WZYD_TOKEN // 登录王者营地的token
|
||||
uid := WZYD_UID // 营地ID
|
||||
|
||||
if len(roleId) == 0 {
|
||||
e = errors.New("参数不足")
|
||||
return
|
||||
}
|
||||
headerMap := make(map[string]string, 0)
|
||||
headerMap["Content-Type"] = "application/json"
|
||||
headerMap["userId"] = uid
|
||||
headerMap["cCurrentGameId"] = "20001"
|
||||
headerMap["Accept-Language"] = "zh-Hans-CN;q=1, en-CN;q=0.9"
|
||||
headerMap["token"] = token
|
||||
|
||||
paraMap := make(map[string]string, 4)
|
||||
paraMap["roleId"] = roleId
|
||||
|
||||
resStr, err2 := utils.HttpDo(http.MethodPost, apiUrl, headerMap, str.ToJsonString(paraMap))
|
||||
|
||||
if err2 != nil {
|
||||
logg.Info("调用王者营地数据查询失败", err2.Error())
|
||||
e = fmt.Errorf("调用王者营地数据查询失败:%v", err2)
|
||||
return
|
||||
}
|
||||
|
||||
resMap := make(map[string]interface{}, 0)
|
||||
json.Unmarshal([]byte(resStr), &resMap)
|
||||
|
||||
if _, ok := resMap["returnCode"]; ok {
|
||||
e = fmt.Errorf("调用王者营地数据查询失败2: %v", resMap["returnMsg"])
|
||||
return
|
||||
}
|
||||
|
||||
//全部皮肤
|
||||
allSkin := resMap["allSkinConf"].([]interface{})
|
||||
// 用户的皮肤
|
||||
userSkinList := resMap["skinData"].([]interface{})
|
||||
|
||||
resultList := []interface{}{}
|
||||
|
||||
rongyaoList := []interface{}{} // 荣耀典藏
|
||||
xiandingList := []interface{}{} // 限定
|
||||
shishiList := []interface{}{} // 史诗级
|
||||
chuanshuoList := []interface{}{} // 传说
|
||||
wushuangList := []interface{}{} //无双
|
||||
|
||||
for _, item := range userSkinList {
|
||||
skinMap := item.(map[string]interface{})
|
||||
userSkinId := skinMap["skinId"].(float64)
|
||||
|
||||
mySkin := getSkinModel(allSkin, userSkinId)
|
||||
mySkin["acquireTime"] = skinMap["acquireTime"].(string)
|
||||
|
||||
// 去掉隐藏的皮肤
|
||||
if mySkin["isHidden"].(float64) == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 去掉原皮
|
||||
if int64(userSkinId)%100 == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 等级处理
|
||||
classTypeName := mySkin["classTypeName"].([]interface{})
|
||||
|
||||
nameList := []string{}
|
||||
for _, name := range classTypeName {
|
||||
nameList = append(nameList, name.(string))
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("%s(%s)", mySkin["skinName"].(string), mySkin["heroName"].(string))
|
||||
|
||||
if utils.IsInStringList("荣耀典藏", nameList) {
|
||||
rongyaoList = append(rongyaoList, str)
|
||||
}
|
||||
if utils.IsInStringList("限定", nameList) {
|
||||
xiandingList = append(xiandingList, str)
|
||||
}
|
||||
if utils.IsInStringList("史诗品质", nameList) {
|
||||
shishiList = append(shishiList, str)
|
||||
}
|
||||
if utils.IsInStringList("传说品质", nameList) {
|
||||
chuanshuoList = append(chuanshuoList, str)
|
||||
}
|
||||
if utils.IsInStringList("无双", nameList) {
|
||||
wushuangList = append(wushuangList, str)
|
||||
}
|
||||
|
||||
resultList = append(resultList, mySkin)
|
||||
}
|
||||
|
||||
logg.Info("皮肤数量", len(resultList))
|
||||
|
||||
result = make(map[string][]interface{}, 0)
|
||||
// result["totalCount"] = len(resultList)
|
||||
result["allSkin"] = resultList
|
||||
result["荣耀典藏"] = rongyaoList
|
||||
result["限定"] = xiandingList
|
||||
result["史诗品质"] = shishiList
|
||||
result["传说品质"] = chuanshuoList
|
||||
result["无双"] = wushuangList
|
||||
return
|
||||
}
|
||||
|
||||
func getSkinModel(allSkin []interface{}, userSkinId float64) (result map[string]interface{}) {
|
||||
for _, item := range allSkin {
|
||||
skin := item.(map[string]interface{})
|
||||
skinId := skin["skinId"].(float64)
|
||||
if skinId == userSkinId {
|
||||
result = skin
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
89
pkg/client/http.go
Normal file
89
pkg/client/http.go
Normal file
@ -0,0 +1,89 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewClient() *http.Client {
|
||||
tr := &http.Transport{
|
||||
MaxIdleConns: 10,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
DisableCompression: true,
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func Get(url string, headers map[string]string, params map[string]string) (res []byte, e error) {
|
||||
var (
|
||||
client = NewClient()
|
||||
resp *http.Response
|
||||
)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
q := req.URL.Query()
|
||||
for k, v := range params {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
if resp, e = client.Do(req); e != nil {
|
||||
return
|
||||
}
|
||||
defer func(body io.ReadCloser) {
|
||||
_ = body.Close()
|
||||
}(resp.Body)
|
||||
if res, e = io.ReadAll(resp.Body); e != nil {
|
||||
return
|
||||
}
|
||||
if !success(resp.StatusCode) {
|
||||
e = errors.New(fmt.Sprintf("status code error: %s", resp.Status))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Post(url string, headers map[string]string, body map[string]interface{}) (res []byte, e error) {
|
||||
var (
|
||||
client = NewClient()
|
||||
resp *http.Response
|
||||
)
|
||||
j, e := json.Marshal(body)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(j))
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
//q := req.URL.Query()
|
||||
//for k, v := range params {
|
||||
// q.Add(k, v)
|
||||
//}
|
||||
//req.URL.RawQuery = q.Encode()
|
||||
if resp, e = client.Do(req); e != nil {
|
||||
return
|
||||
}
|
||||
defer func(body io.ReadCloser) {
|
||||
_ = body.Close()
|
||||
}(resp.Body)
|
||||
if res, e = io.ReadAll(resp.Body); e != nil {
|
||||
return
|
||||
}
|
||||
if !success(resp.StatusCode) {
|
||||
e = errors.New(fmt.Sprintf("status code error: %s", resp.Status))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// func success(code int) bool {
|
||||
// return code >= 200 && code < 400
|
||||
// }
|
||||
45
pkg/client/http_api/user.go
Normal file
45
pkg/client/http_api/user.go
Normal file
@ -0,0 +1,45 @@
|
||||
package http_api
|
||||
|
||||
import (
|
||||
"servicebase/pkg/log"
|
||||
"servicebase/pkg/res"
|
||||
"servicebase/pkg/utils"
|
||||
"errors"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type InnerLoginReq struct {
|
||||
Mobile string `json:"mobile"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type InnerLoginRes struct {
|
||||
Code string
|
||||
Result res.AccessTokenDTO
|
||||
Msg string
|
||||
}
|
||||
|
||||
func InnerLogin(url, secret, mobile string) (token string, e error) {
|
||||
var req = map[string]string{
|
||||
"mobile": mobile,
|
||||
}
|
||||
sign := utils.CreateApiSign(req, secret)
|
||||
req["Signature"] = sign
|
||||
client := resty.New()
|
||||
_ = client
|
||||
var res InnerLoginRes
|
||||
resp, err := client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(req).
|
||||
SetResult(&res). // or SetResult(AuthSuccess{}).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.InfoF("%++v %v", res, string(resp.Body()))
|
||||
if res.Code == "8000" {
|
||||
return res.Result.AccessToken, nil
|
||||
}
|
||||
return "", errors.New("登录失败:" + res.Msg)
|
||||
}
|
||||
112
pkg/client/mongo.go
Normal file
112
pkg/client/mongo.go
Normal file
@ -0,0 +1,112 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"servicebase/pkg/common/req"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type MongoInitStarter struct {
|
||||
}
|
||||
|
||||
var Client *mongo.Client
|
||||
var DB *mongo.Database
|
||||
|
||||
func (s *MongoInitStarter) Init() error {
|
||||
var (
|
||||
ctx = context.TODO()
|
||||
host = viper.GetString("db.mongo.host")
|
||||
port = viper.GetString("db.mongo.port")
|
||||
username = viper.GetString("db.mongo.username")
|
||||
password = viper.GetString("db.mongo.password")
|
||||
dbname = viper.GetString("db.mongo.dbname")
|
||||
e error
|
||||
)
|
||||
// log.InfoF(ctx, "host=%s port=%s db=%s", host, port, dbname)
|
||||
if Client, e = mongo.Connect(ctx, options.Client().
|
||||
SetAuth(options.Credential{Username: username, Password: password}).
|
||||
ApplyURI(fmt.Sprintf("mongodb://%s:%s", host, port))); e != nil {
|
||||
return e
|
||||
}
|
||||
DB = Client.Database(dbname)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckTable(c context.Context, table string) error {
|
||||
if r, e := DB.ListCollectionNames(c, bson.M{"name": "table"}); e != nil {
|
||||
return e
|
||||
} else if len(r) == 0 {
|
||||
return DB.CreateCollection(c, table)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InsertOne(c context.Context, table string, record interface{}) (*mongo.InsertOneResult, error) {
|
||||
if e := CheckTable(c, table); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return DB.Collection(table).InsertOne(c, record)
|
||||
}
|
||||
|
||||
func SaveOne(c context.Context, table string, filter, record interface{}) (*mongo.UpdateResult, error) {
|
||||
if e := CheckTable(c, table); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return DB.Collection(table).ReplaceOne(c, filter, record, options.Replace().SetUpsert(true))
|
||||
}
|
||||
|
||||
func DeleteOne(c context.Context, table string, record interface{}) (*mongo.DeleteResult, error) {
|
||||
if e := CheckTable(c, table); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return DB.Collection(table).DeleteOne(c, record)
|
||||
}
|
||||
|
||||
func UpdateByID(c context.Context, table string, id interface{}, record interface{}) (*mongo.UpdateResult, error) {
|
||||
if e := CheckTable(c, table); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return DB.Collection(table).UpdateByID(c, id, bson.D{{Key: "$set", Value: record}})
|
||||
}
|
||||
|
||||
func Page(c context.Context, table string, page req.Page, filter bson.D, results interface{}) (int64, error) {
|
||||
if e := CheckTable(c, table); e != nil {
|
||||
return 0, e
|
||||
}
|
||||
cursor, e := DB.Collection(table).Find(
|
||||
c,
|
||||
filter,
|
||||
options.Find().SetSkip(int64(page.Offset())),
|
||||
options.Find().SetLimit(int64(page.Limit())),
|
||||
)
|
||||
if e != nil {
|
||||
// log.ErrorF(c, "db Page error: %v", e)
|
||||
return 0, e
|
||||
}
|
||||
defer func() {
|
||||
if e := cursor.Close(c); e != nil {
|
||||
// log.Error(c, e.Error())
|
||||
}
|
||||
}()
|
||||
if e != nil {
|
||||
return 0, e
|
||||
}
|
||||
cnt, e := DB.Collection(table).CountDocuments(c, filter)
|
||||
if e != nil {
|
||||
return 0, e
|
||||
}
|
||||
return cnt, cursor.All(c, results)
|
||||
}
|
||||
|
||||
func MToD(m bson.M) (d bson.D) {
|
||||
for key, value := range m {
|
||||
d = append(d, bson.E{Key: key, Value: value})
|
||||
}
|
||||
return
|
||||
}
|
||||
132
pkg/client/netease/IMService.go
Normal file
132
pkg/client/netease/IMService.go
Normal file
@ -0,0 +1,132 @@
|
||||
package Netease
|
||||
|
||||
import (
|
||||
"servicebase/pkg/common/HyTools"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
Netease "servicebase/pkg/common/netease"
|
||||
|
||||
"github.com/anxpp/beego"
|
||||
"github.com/anxpp/beego/logs"
|
||||
)
|
||||
|
||||
const (
|
||||
// ChatRoomManagerUserId 聊天室消息管理员ID
|
||||
ChatRoomManagerUserId = "34cc7185d0b60eacf38f616b8aad8c51"
|
||||
SystemNoticeUserId = "x94b992dbb4e4dfca9c971200d797a02"
|
||||
P2pMessagesTypeSystemNotice = "104"
|
||||
TimeDefaultFormat = "Y-m-d H:i:s"
|
||||
)
|
||||
|
||||
type ImService struct {
|
||||
}
|
||||
|
||||
// ImMsgBodyAttach IM消息 body对象
|
||||
type ImMsgBodyAttach struct {
|
||||
TypeCode string `json:"TypeCode"`
|
||||
BizData interface{} `json:"BizData"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
}
|
||||
|
||||
// ChatRoomMsgAttach 聊天室自定义消息
|
||||
type ChatRoomMsgAttach struct {
|
||||
TypeCode string `json:"TypeCode"`
|
||||
BizData interface{} `json:"BizData"`
|
||||
}
|
||||
|
||||
// SendCustomMsgToChatroomByChatroomManager 向聊天室发消息
|
||||
func (s *ImService) SendCustomMsgToChatroomByChatroomManager(messageRoomId string, attachData ChatRoomMsgAttach) error {
|
||||
msgParameter := make(map[string]string)
|
||||
msgParameter["roomid"] = messageRoomId
|
||||
msgParameter["msgId"] = HyTools.GetUUID()
|
||||
msgParameter["fromAccid"] = ChatRoomManagerUserId
|
||||
msgParameter["msgType"] = "100" //自定义消息
|
||||
msgParameter["resendFlag"] = "0"
|
||||
bodyBytes, _ := json.Marshal(attachData)
|
||||
msgParameter["ext"] = ""
|
||||
msgParameter["attach"] = string(bodyBytes)
|
||||
imClient := Netease.NewImClient()
|
||||
return imClient.SendChatroomMsg(msgParameter)
|
||||
}
|
||||
|
||||
// SendCustomerMsgFromAToB 发IM点对点自定义消息 系统通知 用户关系变化
|
||||
func (s *ImService) SendCustomerMsgFromAToB(fromAccid, toAccid, pushContent, payloadJson string, bodyModel ImMsgBodyAttach, optionMap map[string]string) error {
|
||||
msgParameter := make(map[string]string)
|
||||
msgParameter["from"] = fromAccid
|
||||
msgParameter["ope"] = "0" //0=p2p 1=群消息
|
||||
msgParameter["to"] = toAccid
|
||||
msgParameter["type"] = "100" //0 表示文本消息, 1 表示图片, 2 表示语音, 3 表示视频, 4 表示地理位置信息, 6 表示文件, 100 自定义消息类型
|
||||
|
||||
bodyBytes, _ := json.Marshal(bodyModel)
|
||||
|
||||
msgParameter["body"] = string(bodyBytes) //最大长度5000字符,为一个JSON串
|
||||
msgParameter["antispam"] = "false" //对于对接了易盾反垃圾功能的应用,本消息是否需要指定经由易盾检测的内容 只对消息类型为:100 自定义消息类型 的消息生效。
|
||||
|
||||
optionBytes, _ := json.Marshal(optionMap)
|
||||
msgParameter["option"] = string(optionBytes) //发消息时特殊指定的行为选项,JSON格式,可用于指定消息的漫游,存云端历史,发送方多端同步,推送,消息抄送等特殊行为
|
||||
|
||||
//需要推送
|
||||
if optionMap["push"] == "true" {
|
||||
if len(pushContent) > 0 {
|
||||
msgParameter["pushcontent"] = pushContent // ios推送内容,不超过150字符,option选项中允许推送(push=true),此字段可以指定推送内容
|
||||
}
|
||||
|
||||
if len(payloadJson) > 0 {
|
||||
msgParameter["payload"] = payloadJson // ios 推送对应的payload,必须是JSON,不能超过2k字符
|
||||
}
|
||||
}
|
||||
imClient := Netease.NewImClient()
|
||||
return imClient.SendMsg(msgParameter)
|
||||
}
|
||||
|
||||
// SendSystemNoticeToUser 给用户发系统消息
|
||||
// _ = messageService.SendSystemNoticeToUser(userId, "厅补贴到账通知", msgContent, "", "")
|
||||
func (s *ImService) SendSystemNoticeToUser(toAccid, msgTitle, msgText, schemeUrl, imageUrl string) {
|
||||
option := GetDefaultMsgOption()
|
||||
fromUserId := SystemNoticeUserId
|
||||
logs.Info("SendSystemNoticeToUser from " + fromUserId + "to " + toAccid)
|
||||
pushContent := ""
|
||||
payloadJson := ""
|
||||
bizData := make(map[string]string, 0)
|
||||
bizData["Title"] = url.QueryEscape(msgTitle)
|
||||
bizData["Content"] = url.QueryEscape(msgText)
|
||||
bizData["Scheme"] = schemeUrl
|
||||
bizData["ImageUrl"] = imageUrl
|
||||
bizData["CreateTime"] = beego.Date(time.Now(), TimeDefaultFormat)
|
||||
//body的业务数据
|
||||
var bodyAttach ImMsgBodyAttach
|
||||
bodyAttach.TypeCode = P2pMessagesTypeSystemNotice
|
||||
bodyAttach.BizData = bizData
|
||||
bodyAttach.Msg = msgTitle
|
||||
err := s.SendCustomerMsgFromAToB(fromUserId, toAccid, pushContent, payloadJson, bodyAttach, option)
|
||||
if nil != err {
|
||||
logs.Error("通知发送失败!!!!")
|
||||
logs.Info(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// GetDefaultMsgOption 发送消息默认的配置
|
||||
func GetDefaultMsgOption() map[string]string {
|
||||
/*
|
||||
1. roam: 该消息是否需要漫游,默认true(需要app开通漫游消息功能);
|
||||
2. history: 该消息是否存云端历史,默认true;
|
||||
3. sendersync: 该消息是否需要发送方多端同步,默认true;
|
||||
4. push: 该消息是否需要APNS推送或安卓系统通知栏推送,默认true;
|
||||
5. route: 该消息是否需要抄送第三方;默认true (需要app开通消息抄送功能);
|
||||
6. badge:该消息是否需要计入到未读计数中,默认true;
|
||||
7. needPushNick: 推送文案是否需要带上昵称,不设置该参数时默认true;
|
||||
8. persistent: 是否需要存离线消息,不设置该参数时默认true。
|
||||
*/
|
||||
result := make(map[string]string)
|
||||
result["roam"] = "true"
|
||||
result["history"] = "true"
|
||||
result["sendersync"] = "false"
|
||||
result["push"] = "true"
|
||||
result["route"] = "true"
|
||||
result["badge"] = "true"
|
||||
result["needPushNick"] = "false"
|
||||
result["persistent"] = "true"
|
||||
return result
|
||||
}
|
||||
104
pkg/client/netease/MessageService.go
Normal file
104
pkg/client/netease/MessageService.go
Normal file
@ -0,0 +1,104 @@
|
||||
package Netease
|
||||
|
||||
const (
|
||||
// ChatroomCustomMessageTypePresentActivityMsg 聊天室打赏活动物品消息
|
||||
ChatroomCustomMessageTypePresentActivityMsg = "380"
|
||||
// ChatroomCustomMessageTypeFullScreenNoticeMsg 聊天室 全服通知动效
|
||||
ChatroomCustomMessageTypeFullScreenNoticeMsg = "381"
|
||||
)
|
||||
|
||||
const (
|
||||
//聊天室字体颜色 - 黄色
|
||||
CHATROOM_SYSTEM_MSG_COLOR_YELLOW = "#FFD700"
|
||||
//聊天室字体颜色 - 绿色
|
||||
CHATROOM_SYSTEM_MSG_COLOR_GREEN = "#8ADE4D"
|
||||
//聊天室字体颜色 - 系统栏
|
||||
CHATROOM_SYSTEM_MSG_COLOR_SYSTEM_BLUE = "#43EAE1"
|
||||
//聊天室字体颜色 - 紫色
|
||||
CHATROOM_SYSTEM_MSG_COLOR_PURPLE = "#E031FE"
|
||||
// 默认等级的背景颜色
|
||||
SKILL_LEVEL_DEFAULT_BG_COLOR = "#FFD700"
|
||||
//聊天室字体颜色 - 蓝色
|
||||
CHATROOM_SYSTEM_MSG_COLOR_BLUE = "#25C6FD"
|
||||
//聊天室字体颜色 - 青色
|
||||
CHATROOM_SYSTEM_MSG_COLOR_QING = "#00FFCC"
|
||||
//聊天室字体颜色 - 白色
|
||||
CHATROOM_SYSTEM_MSG_COLOR_WHITE = "#FFFFFF"
|
||||
)
|
||||
|
||||
type MessageService struct {
|
||||
}
|
||||
|
||||
type TextAndColor struct {
|
||||
Text string
|
||||
Color string
|
||||
}
|
||||
|
||||
// ChatroomFullScreenNoticeMsgBizData 聊天室活动全服飘屏通知
|
||||
type ChatroomFullScreenNoticeMsgBizData struct {
|
||||
ChatroomId string
|
||||
ContentIcon string
|
||||
ContentText string
|
||||
ContentTextColor string //内容字体颜色
|
||||
ContentBgColor string //背景颜色
|
||||
AnimationApngUrl string //加入播放礼物动效队列
|
||||
BgImgUrl string //背景图
|
||||
TextList []TextAndColor //文字颜色列表
|
||||
StayTime string //停留时长(秒)
|
||||
}
|
||||
|
||||
// ChatroomPresentActivityMsgBizData 聊天室打赏活动消息data数据结构
|
||||
type ChatroomPresentActivityMsgBizData struct {
|
||||
ChatroomId string
|
||||
FromUserId string
|
||||
FromNickName string
|
||||
FromAvatar string
|
||||
FromNickNameColor string //打赏人昵称颜色
|
||||
ActionText string //动作文本 :赠送一个盲盒给
|
||||
ActionTextColor string //动作文本颜色
|
||||
|
||||
ToNickName string
|
||||
ToNickNameColor string //被打赏人昵称颜色
|
||||
|
||||
PresentCount string //物品个数
|
||||
PresentGoodsName string
|
||||
PresentGoodsIcon string
|
||||
PresentGoodsNameColor string
|
||||
|
||||
ResultGoodsIcon string
|
||||
ComboHitCount string //连击次数
|
||||
AnimationApngUrl string //加入播放礼物动效队列 有的话显示播放动画
|
||||
AnimationFormat string //动画格式 SVGA APNG
|
||||
FillMode string //填充模式 1=左右 2=上下
|
||||
|
||||
IsViewFlyView string //是否显示飘屏view 有的话显示连击信息
|
||||
//显示到信息流
|
||||
TextList []TextAndColor //信息流文字颜色列表
|
||||
|
||||
}
|
||||
|
||||
// SendChatroomSystemMsgV2 发送聊天室系统消息 V2
|
||||
func (s *MessageService) SendChatroomSystemMsgV2(messageRoomId string, msgData ChatroomPresentActivityMsgBizData) (err error) {
|
||||
// 发打赏消息
|
||||
var service ImService
|
||||
attachModel := ChatRoomMsgAttach{}
|
||||
//消息类型
|
||||
typeCode := ChatroomCustomMessageTypePresentActivityMsg
|
||||
attachModel.TypeCode = typeCode
|
||||
attachModel.BizData = msgData
|
||||
err = service.SendCustomMsgToChatroomByChatroomManager(messageRoomId, attachModel)
|
||||
return
|
||||
}
|
||||
|
||||
// SendChatroomPresentActivityFullServiceMsg 发送聊天室活动全服
|
||||
func (s *MessageService) SendChatroomPresentActivityFullServiceMsg(messageRoomId string, msgData ChatroomFullScreenNoticeMsgBizData) (err error) {
|
||||
// 发打赏消息
|
||||
var service ImService
|
||||
attachModel := ChatRoomMsgAttach{}
|
||||
//消息类型
|
||||
typeCode := ChatroomCustomMessageTypeFullScreenNoticeMsg
|
||||
attachModel.TypeCode = typeCode
|
||||
attachModel.BizData = msgData
|
||||
err = service.SendCustomMsgToChatroomByChatroomManager(messageRoomId, attachModel)
|
||||
return
|
||||
}
|
||||
43
pkg/client/netease/captcha.go
Normal file
43
pkg/client/netease/captcha.go
Normal file
@ -0,0 +1,43 @@
|
||||
package Netease
|
||||
|
||||
import (
|
||||
"servicebase/pkg/log"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
captcha "github.com/yidun/yidun-golang-sdk/yidun/service/captcha"
|
||||
)
|
||||
|
||||
var (
|
||||
CaptchaId = "28a052c000324d2e992e9e184291c92d"
|
||||
)
|
||||
|
||||
// CaptchaSecondVerify 验证码二次校验请求
|
||||
func CaptchaSecondVerify(captchaId, validate string) (bool, error) {
|
||||
request := captcha.NewCaptchaVerifyRequest()
|
||||
var user string = ""
|
||||
request.SetCaptchaId(captchaId).SetValidate(validate).SetUser(user)
|
||||
secretId := viper.GetString("netease.captcha.secretId")
|
||||
secretKey := viper.GetString("netease.captcha.secretKey")
|
||||
captchaClient := captcha.NewCaptchaVerifyClientWithAccessKey(secretId, secretKey)
|
||||
response, err := captchaClient.Verify(request)
|
||||
if err != nil {
|
||||
log.ErrorF("CaptchaSecondVerify err: %+v", err)
|
||||
return false, err
|
||||
}
|
||||
if response == nil {
|
||||
log.ErrorF("CaptchaSecondVerify response is nil")
|
||||
return false, errors.Errorf("CaptchaSecondVerify response is nil")
|
||||
}
|
||||
respBody, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
log.InfoF("CaptchaSecondVerify response is : %s", string(respBody))
|
||||
if response.Result == nil {
|
||||
log.ErrorF("CaptchaSecondVerify response.Result is nil")
|
||||
return false, errors.Errorf("CaptchaSecondVerify response.Result is nil")
|
||||
}
|
||||
return *response.Result, nil
|
||||
}
|
||||
196
pkg/client/netease/live_person.go
Normal file
196
pkg/client/netease/live_person.go
Normal file
@ -0,0 +1,196 @@
|
||||
package Netease
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"servicebase/pkg/log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tjfoc/gmsm/sm3"
|
||||
)
|
||||
|
||||
const (
|
||||
apiURL = "https://verify.dun.163.com/v1/face/liveness/h5/auth"
|
||||
recheckTokenUrl = "https://verify.dun.163.com/v1/face/liveness/h5/recheck"
|
||||
version = "v1"
|
||||
)
|
||||
|
||||
type ApplyInfoReq struct {
|
||||
Name string
|
||||
CardNo string
|
||||
RedirectUrl string
|
||||
CallBackUrl string
|
||||
DataId string
|
||||
CallbackValidate string
|
||||
EncryptType string
|
||||
}
|
||||
|
||||
type BaseRsp[T any] struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Result T `json:"result"`
|
||||
}
|
||||
|
||||
type H5ApplyLivePersonRsp struct {
|
||||
AuthUrl string `json:"authUrl"`
|
||||
AuthToken string `json:"authToken"`
|
||||
}
|
||||
|
||||
type H5ReCheckLivePersonTokenRsp struct {
|
||||
TaskId string `json:"taskId"`
|
||||
PicType int64 `json:"picType"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status int64 `json:"status"`
|
||||
ReasonType int `json:"reasonType"`
|
||||
IsPayed int64 `json:"isPayed"`
|
||||
SimilarityScore float64 `json:"similarityScore"`
|
||||
FaceMatched int64 `json:"faceMatched"`
|
||||
FaceAttributeInfo interface{} `json:"faceAttributeInfo"`
|
||||
ExtInfo ExtInfoEntity `json:"extInfo"`
|
||||
}
|
||||
|
||||
type ExtInfoEntity struct {
|
||||
SuspectedNonageFlag bool `json:"suspectedNonageFlag"`
|
||||
}
|
||||
|
||||
// H5ApplyLivePerson 请求易盾生成h5人身验证h5 url 和 authToken
|
||||
func H5ApplyLivePerson(ctx context.Context, req *ApplyInfoReq) (*H5ApplyLivePersonRsp, error) {
|
||||
params := url.Values{
|
||||
"name": []string{req.Name},
|
||||
"cardNo": []string{req.CardNo},
|
||||
"redirectUrl": []string{req.RedirectUrl},
|
||||
}
|
||||
rsp, err := apply(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// H5ReCheckLivePersonToken 根据authToken二次验证,并获取相关信息,如正面拍照的活体图片
|
||||
func H5ReCheckLivePersonToken(ctx context.Context, token string) (*H5ReCheckLivePersonTokenRsp, error) {
|
||||
params := url.Values{
|
||||
"authToken": []string{token},
|
||||
}
|
||||
rsp, err := checkToken(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// 生成签名信息
|
||||
func genSignature(params url.Values) string {
|
||||
var paramStr string
|
||||
keys := make([]string, 0, len(params))
|
||||
for k := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
paramStr += key + params[key][0]
|
||||
}
|
||||
paramStr += viper.GetString("netease.captcha.secretKey")
|
||||
if params["signatureMethod"] != nil && params["signatureMethod"][0] == "SM3" {
|
||||
sm3Reader := sm3.New()
|
||||
sm3Reader.Write([]byte(paramStr))
|
||||
return hex.EncodeToString(sm3Reader.Sum(nil))
|
||||
}
|
||||
md5Reader := md5.New()
|
||||
md5Reader.Write([]byte(paramStr))
|
||||
return hex.EncodeToString(md5Reader.Sum(nil))
|
||||
}
|
||||
|
||||
// 请求易盾接口
|
||||
func apply(params url.Values) (*H5ApplyLivePersonRsp, error) {
|
||||
params["secretId"] = []string{viper.GetString("netease.captcha.secretId")}
|
||||
params["businessId"] = []string{viper.GetString("netease.captcha.businessId")}
|
||||
params["version"] = []string{version}
|
||||
params["timestamp"] = []string{strconv.FormatInt(time.Now().UnixNano()/1000000, 10)}
|
||||
params["nonce"] = []string{strconv.FormatInt(rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(10000000000), 10)}
|
||||
params["signature"] = []string{genSignature(params)}
|
||||
|
||||
resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", strings.NewReader(params.Encode()))
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.Errorf("请求网易易盾获取h5人身验证失败,状态码为%d", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
contents, _ := io.ReadAll(resp.Body)
|
||||
log.InfoF("请求网易易盾获取h5人身验证响应: %s", string(contents))
|
||||
var rsp BaseRsp[*H5ApplyLivePersonRsp]
|
||||
err = json.Unmarshal(contents, &rsp)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if rsp.Code != 200 {
|
||||
return nil, errors.Errorf("响应体状态码不是200(%d),信息: %s", rsp.Code, rsp.Msg)
|
||||
}
|
||||
return rsp.Result, nil
|
||||
}
|
||||
|
||||
func checkToken(params url.Values) (*H5ReCheckLivePersonTokenRsp, error) {
|
||||
params["secretId"] = []string{viper.GetString("netease.captcha.secretId")}
|
||||
params["businessId"] = []string{viper.GetString("netease.captcha.businessId")}
|
||||
params["version"] = []string{version}
|
||||
params["timestamp"] = []string{strconv.FormatInt(time.Now().UnixNano()/1000000, 10)}
|
||||
params["nonce"] = []string{strconv.FormatInt(rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(10000000000), 10)}
|
||||
params["signature"] = []string{genSignature(params)}
|
||||
|
||||
resp, err := http.Post(recheckTokenUrl, "application/x-www-form-urlencoded", strings.NewReader(params.Encode()))
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.Errorf("请求网易易盾获取h5人身验证失败,状态码为%d", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
contents, _ := io.ReadAll(resp.Body)
|
||||
log.InfoF("请求网易云盾获取人身核验二次验证token响应: %s", string(contents))
|
||||
var rsp BaseRsp[*H5ReCheckLivePersonTokenRsp]
|
||||
err = json.Unmarshal(contents, &rsp)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if rsp.Code != 200 {
|
||||
return nil, errors.Errorf("响应体状态码不是200(%d),信息: %s", rsp.Code, rsp.Msg)
|
||||
}
|
||||
return rsp.Result, nil
|
||||
}
|
||||
|
||||
var livePersonAndIdCheckFailReason = map[int]string{
|
||||
2: "活体通过,姓名身份证号一致,人脸比对非同一人",
|
||||
3: "活体通过,姓名身份证号不一致",
|
||||
4: "活体不通过",
|
||||
5: "活体检测超时或出现异常",
|
||||
6: "活体通过,查无此身份证",
|
||||
7: "活体通过,库中无此身份证照片",
|
||||
8: "活体通过,人脸照过大",
|
||||
9: "活体通过,权威数据源出现异常",
|
||||
10: "疑似攻击,建议拦截",
|
||||
11: "检测对象为未成年人",
|
||||
}
|
||||
|
||||
func GetLivePersonAndIdCheckFailReason(code int) string {
|
||||
r, ok := livePersonAndIdCheckFailReason[code]
|
||||
if !ok {
|
||||
return fmt.Sprintf("其他原因:ReasonType==%d", code)
|
||||
}
|
||||
return r
|
||||
}
|
||||
78
pkg/client/netease/live_person_test.go
Normal file
78
pkg/client/netease/live_person_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
package Netease
|
||||
|
||||
import (
|
||||
"servicebase/pkg/common/HyTools"
|
||||
"servicebase/pkg/log"
|
||||
"servicebase/pkg/partner/qiniu"
|
||||
"context"
|
||||
"github.com/spf13/viper"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestApplyH5LivePerson(t *testing.T) {
|
||||
log.Init()
|
||||
rst, err := H5ApplyLivePerson(context.Background(), &ApplyInfoReq{
|
||||
Name: "xxx", // 姓名
|
||||
CardNo: "xxxxxxx", // 身份证号码,x需要变成大写X
|
||||
RedirectUrl: "https://www.baidu.com",
|
||||
CallBackUrl: "",
|
||||
DataId: "",
|
||||
CallbackValidate: "",
|
||||
EncryptType: "",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
println(rst.AuthToken)
|
||||
println(rst.AuthUrl)
|
||||
}
|
||||
|
||||
func TestH5ReCheckLivePersonToken(t *testing.T) {
|
||||
log.Init()
|
||||
_, err := H5ReCheckLivePersonToken(context.Background(), "6ZgHU-x9XsRVF8aS7PIFppBho28QEUAR")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestB(t *testing.T) {
|
||||
log.Init()
|
||||
|
||||
// key: CwGN8XGmbEZr7qqJl-y-QodcUYREz8ph_glVKCqp
|
||||
// secret: KbZKt8WwQcZ6II0tygMLsO3KWpm50aMz737VaMV0
|
||||
viper.Set("qiniu.key", "CwGN8XGmbEZr7qqJl-y-QodcUYREz8ph_glVKCqp")
|
||||
|
||||
viper.Set("qiniu.secret", "KbZKt8WwQcZ6II0tygMLsO3KWpm50aMz737VaMV0")
|
||||
|
||||
viper.Set("qiniu.bucket.photo", "ddphoto")
|
||||
|
||||
key := func() string {
|
||||
resp, err := http.Get("xxx")
|
||||
if err != nil {
|
||||
log.ErrorF("获取人脸核身正面照图片错误:%+v", err)
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.ErrorF("获取人脸核身正面照图片错误 http code为%d", resp.StatusCode)
|
||||
return ""
|
||||
}
|
||||
imageData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.ErrorF("")
|
||||
return ""
|
||||
}
|
||||
|
||||
key := "upload/" + HyTools.GetUUID() + ".jpg"
|
||||
//上传文件
|
||||
newKey := qiniu.UploadFile(context.Background(), viper.GetString("qiniu.bucket.photo"), imageData, key)
|
||||
if len(newKey) == 0 {
|
||||
log.ErrorF("获取人脸核身正面照图片上传到七牛oss失败")
|
||||
return ""
|
||||
}
|
||||
return key
|
||||
}()
|
||||
println(key)
|
||||
}
|
||||
84
pkg/client/shengwang_client.go
Normal file
84
pkg/client/shengwang_client.go
Normal file
@ -0,0 +1,84 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"servicebase/pkg/tools"
|
||||
|
||||
rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func GenerateShenWangToken(userNo, RoomId string) (token string, e error) {
|
||||
// Need to set environment variable AGORA_APP_ID
|
||||
appId := viper.GetString("agora.tokenGen.appId")
|
||||
// Need to set environment variable AGORA_APP_CERTIFICATE
|
||||
appCertificate := viper.GetString("agora.tokenGen.appCertificate")
|
||||
channelName := RoomId
|
||||
uidStr := userNo
|
||||
uid := uint32(tools.StrToInt32(userNo))
|
||||
expire := 3600 * 24
|
||||
tokenExpirationInSeconds := uint32(expire)
|
||||
privilegeExpirationInSeconds := uint32(expire)
|
||||
joinChannelPrivilegeExpireInSeconds := uint32(expire)
|
||||
pubAudioPrivilegeExpireInSeconds := uint32(expire)
|
||||
pubVideoPrivilegeExpireInSeconds := uint32(expire)
|
||||
pubDataStreamPrivilegeExpireInSeconds := uint32(expire)
|
||||
|
||||
fmt.Println("App Id:", appId)
|
||||
fmt.Println("App Certificate:", appCertificate)
|
||||
if appId == "" || appCertificate == "" {
|
||||
fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
} else {
|
||||
fmt.Printf("Token with int uid: %s\n", result)
|
||||
}
|
||||
|
||||
result, err = rtctokenbuilder.BuildTokenWithUserAccount(appId, appCertificate, channelName, uidStr, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
} else {
|
||||
fmt.Printf("Token with user account: %s\n", result)
|
||||
}
|
||||
|
||||
result, err = rtctokenbuilder.BuildTokenWithUidAndPrivilege(appId, appCertificate, channelName, uid,
|
||||
tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds, pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
} else {
|
||||
fmt.Printf("Token with int uid and privilege: %s\n", result)
|
||||
}
|
||||
|
||||
result, err = rtctokenbuilder.BuildTokenWithUserAccountAndPrivilege(appId, appCertificate, channelName, uidStr,
|
||||
tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds, pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
} else {
|
||||
fmt.Printf("Token with user account and privilege: %s\n", result)
|
||||
}
|
||||
|
||||
result, err = rtctokenbuilder.BuildTokenWithRtm(appId, appCertificate, channelName, uidStr, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
} else {
|
||||
fmt.Printf("Token with RTM: %s\n", result)
|
||||
}
|
||||
|
||||
result, err = rtctokenbuilder.BuildTokenWithRtm2(appId, appCertificate, channelName, uidStr, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds, pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds, uidStr, tokenExpirationInSeconds)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
} else {
|
||||
fmt.Printf("Token with RTM: %s\n", result)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
11
pkg/client/shengwang_client_test.go
Normal file
11
pkg/client/shengwang_client_test.go
Normal file
@ -0,0 +1,11 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateShenWangToken(t *testing.T) {
|
||||
gotToken, err := GenerateShenWangToken("65945666", "9956050")
|
||||
fmt.Printf("%+v %+v\n", gotToken, err)
|
||||
}
|
||||
311
pkg/client/sms_client.go
Normal file
311
pkg/client/sms_client.go
Normal file
@ -0,0 +1,311 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"servicebase/pkg/helper"
|
||||
"servicebase/pkg/log"
|
||||
"sync"
|
||||
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v3/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// CreateAliSmsClient
|
||||
/**
|
||||
* 使用AK&SK初始化账号Client
|
||||
* @param accessKeyId
|
||||
* @param accessKeySecret
|
||||
* @return Client
|
||||
* @throws Exception
|
||||
*/
|
||||
func CreateAliSmsClient(accessKeyId *string, accessKeySecret *string) (_result *dysmsapi20170525.Client, _err error) {
|
||||
config := &openapi.Config{
|
||||
// 必填,您的 AccessKey ID
|
||||
AccessKeyId: accessKeyId,
|
||||
// 必填,您的 AccessKey Secret
|
||||
AccessKeySecret: accessKeySecret,
|
||||
}
|
||||
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
|
||||
config.Endpoint = tea.String("dysmsapi.aliyuncs.com")
|
||||
_result = &dysmsapi20170525.Client{}
|
||||
_result, _err = dysmsapi20170525.NewClient(config)
|
||||
return _result, _err
|
||||
}
|
||||
|
||||
var (
|
||||
smsSignList = []string{"淮南东东网络科技"}
|
||||
tempCodeList = []string{"SMS_320216048"}
|
||||
smsSignCodeLock sync.Mutex
|
||||
smsSignCodeIndex int64
|
||||
)
|
||||
|
||||
func getSignAndCode() (sign, code string) {
|
||||
smsSignCodeLock.Lock()
|
||||
defer smsSignCodeLock.Unlock()
|
||||
i := smsSignCodeIndex % int64(len(smsSignList))
|
||||
sign = smsSignList[int(i)]
|
||||
code = tempCodeList[int(i)]
|
||||
smsSignCodeIndex++
|
||||
return
|
||||
}
|
||||
|
||||
func SendVerifyCode01(mobile, content string, fn func(_result *dysmsapi20170525.SendSmsResponse, success bool)) (_result *dysmsapi20170525.SendSmsResponse, e error) {
|
||||
if !helper.IsMobile(mobile) {
|
||||
return nil, errors.New("手机号有误!")
|
||||
}
|
||||
sign, code := getSignAndCode()
|
||||
log.InfoF("get sms sign and code: %s %s", sign, code)
|
||||
_result, e = SendMobileMsg01(mobile, sign, code, content)
|
||||
if fn != nil {
|
||||
fn(_result, e == nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SendMobileMsg01(mobile, signName, tempCode, content string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
|
||||
var (
|
||||
ID = viper.GetString("aliyun.sms.accessKeyId")
|
||||
Secret = viper.GetString("aliyun.sms.accessKeySecret")
|
||||
)
|
||||
// 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
|
||||
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378661.html
|
||||
client, _err := CreateAliSmsClient(tea.String(ID), tea.String(Secret))
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
|
||||
PhoneNumbers: tea.String(mobile),
|
||||
SignName: tea.String(signName),
|
||||
TemplateCode: tea.String(tempCode),
|
||||
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", content)),
|
||||
}
|
||||
res, tryErr := func() (_result *dysmsapi20170525.SendSmsResponse, _e error) {
|
||||
defer func() {
|
||||
if r := tea.Recover(recover()); r != nil {
|
||||
_e = r
|
||||
}
|
||||
}()
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
_result, _err = client.SendSmsWithOptions(sendSmsRequest, &util.RuntimeOptions{})
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
if tryErr != nil {
|
||||
var e = &tea.SDKError{}
|
||||
if _t, ok := tryErr.(*tea.SDKError); ok {
|
||||
e = _t
|
||||
} else {
|
||||
e.Message = tea.String(tryErr.Error())
|
||||
}
|
||||
// 如有需要,请打印 error
|
||||
_, _err = util.AssertAsString(e.Message)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
}
|
||||
return res, _err
|
||||
}
|
||||
|
||||
func SendVerifyCodeUsaAndCanada(mobile, content string, fn func(_result *dysmsapi20170525.SendSmsResponse, success bool)) (_result *dysmsapi20170525.SendSmsResponse, e error) {
|
||||
// if !helper.IsMobile(mobile) {
|
||||
// return nil, errors.New("手机号有误!")
|
||||
// }
|
||||
sign, code := getSignAndCode()
|
||||
log.InfoF("SendVerifyCodeUsaAndCanada get sms sign and code: %s %s", sign, code)
|
||||
_result, e = SendMobileMsgUsaAndCanada(mobile, sign, code, content)
|
||||
if fn != nil {
|
||||
fn(_result, e == nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SendVerifyCodeGlobalOther(mobile, content string, fn func(_result *dysmsapi20170525.SendSmsResponse, success bool)) (_result *dysmsapi20170525.SendSmsResponse, e error) {
|
||||
// if !helper.IsMobile(mobile) {
|
||||
// return nil, errors.New("手机号有误!")
|
||||
// }
|
||||
sign, code := getSignAndCode()
|
||||
log.InfoF("SendVerifyCodeUsaAndCanada get sms sign and code: %s %s", sign, code)
|
||||
_result, e = SendMobileMsgGlobalOther(mobile, sign, code, content)
|
||||
if fn != nil {
|
||||
fn(_result, e == nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SendMobileMsgUsaAndCanada 美国/加拿大地区短信发送
|
||||
func SendMobileMsgUsaAndCanada(mobile, signName, tempCode, content string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
|
||||
var (
|
||||
ID = viper.GetString("aliyun.sms.accessKeyId")
|
||||
Secret = viper.GetString("aliyun.sms.accessKeySecret")
|
||||
)
|
||||
// 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
|
||||
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378661.html
|
||||
client, _err := CreateAliSmsClient(tea.String(ID), tea.String(Secret))
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
|
||||
PhoneNumbers: tea.String(mobile),
|
||||
SignName: tea.String(signName),
|
||||
TemplateCode: tea.String(tempCode),
|
||||
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", content)),
|
||||
}
|
||||
res, tryErr := func() (_result *dysmsapi20170525.SendSmsResponse, _e error) {
|
||||
defer func() {
|
||||
if r := tea.Recover(recover()); r != nil {
|
||||
_e = r
|
||||
}
|
||||
}()
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
_result, _err = client.SendSmsWithOptions(sendSmsRequest, &util.RuntimeOptions{})
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
if tryErr != nil {
|
||||
var e = &tea.SDKError{}
|
||||
if _t, ok := tryErr.(*tea.SDKError); ok {
|
||||
e = _t
|
||||
} else {
|
||||
e.Message = tea.String(tryErr.Error())
|
||||
}
|
||||
// 如有需要,请打印 error
|
||||
_, _err = util.AssertAsString(e.Message)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
}
|
||||
return res, _err
|
||||
}
|
||||
|
||||
// SendMobileMsgGlobalOther 全球其他国家
|
||||
func SendMobileMsgGlobalOther(mobile, signName, tempCode, content string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
|
||||
var (
|
||||
// ID AccessKey ID
|
||||
// LTAI5tRfkJnr6Y6NU2T6jWoB
|
||||
//
|
||||
// AccessKey Secret
|
||||
// BcaMWgXkwdxdJXFnCwRQbEl3Q3Xtxl
|
||||
// TODO: 替换为您的AccessKey ID和AccessKey Secret
|
||||
ID = viper.GetString("aliyun.sms.accessKeyId")
|
||||
Secret = viper.GetString("aliyun.sms.accessKeySecret")
|
||||
)
|
||||
// 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
|
||||
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378661.html
|
||||
client, _err := CreateAliSmsClient(tea.String(ID), tea.String(Secret))
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
|
||||
PhoneNumbers: tea.String(mobile),
|
||||
SignName: tea.String(signName),
|
||||
TemplateCode: tea.String(tempCode),
|
||||
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", content)),
|
||||
}
|
||||
res, tryErr := func() (_result *dysmsapi20170525.SendSmsResponse, _e error) {
|
||||
defer func() {
|
||||
if r := tea.Recover(recover()); r != nil {
|
||||
_e = r
|
||||
}
|
||||
}()
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
_result, _err = client.SendSmsWithOptions(sendSmsRequest, &util.RuntimeOptions{})
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
if tryErr != nil {
|
||||
var e = &tea.SDKError{}
|
||||
if _t, ok := tryErr.(*tea.SDKError); ok {
|
||||
e = _t
|
||||
} else {
|
||||
e.Message = tea.String(tryErr.Error())
|
||||
}
|
||||
// 如有需要,请打印 error
|
||||
_, _err = util.AssertAsString(e.Message)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
}
|
||||
return res, _err
|
||||
}
|
||||
|
||||
func SendVerifyHKMacauAndTW(mobile, content string, fn func(_result *dysmsapi20170525.SendSmsResponse, success bool)) (_result *dysmsapi20170525.SendSmsResponse, e error) {
|
||||
if !helper.IsMobile(mobile) {
|
||||
return nil, errors.New("手机号有误!")
|
||||
}
|
||||
sign, code := getSignAndCode()
|
||||
log.InfoF("get sms sign and code: %s %s", sign, code)
|
||||
_result, e = SendMobileMsgHKMacauAndTW(mobile, sign, code, content)
|
||||
if fn != nil {
|
||||
fn(_result, e == nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SendMobileMsgHKMacauAndTW 港澳台
|
||||
func SendMobileMsgHKMacauAndTW(mobile, signName, tempCode, content string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
|
||||
var (
|
||||
ID = viper.GetString("aliyun.sms.accessKeyId")
|
||||
Secret = viper.GetString("aliyun.sms.accessKeySecret")
|
||||
)
|
||||
// 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
|
||||
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378661.html
|
||||
client, _err := CreateAliSmsClient(tea.String(ID), tea.String(Secret))
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
|
||||
PhoneNumbers: tea.String(mobile),
|
||||
SignName: tea.String(signName),
|
||||
TemplateCode: tea.String(tempCode),
|
||||
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", content)),
|
||||
}
|
||||
res, tryErr := func() (_result *dysmsapi20170525.SendSmsResponse, _e error) {
|
||||
defer func() {
|
||||
if r := tea.Recover(recover()); r != nil {
|
||||
_e = r
|
||||
}
|
||||
}()
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
_result, _err = client.SendSmsWithOptions(sendSmsRequest, &util.RuntimeOptions{})
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
if tryErr != nil {
|
||||
var e = &tea.SDKError{}
|
||||
if _t, ok := tryErr.(*tea.SDKError); ok {
|
||||
e = _t
|
||||
} else {
|
||||
e.Message = tea.String(tryErr.Error())
|
||||
}
|
||||
// 如有需要,请打印 error
|
||||
_, _err = util.AssertAsString(e.Message)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
}
|
||||
return res, _err
|
||||
}
|
||||
260
pkg/client/tencent.go
Normal file
260
pkg/client/tencent.go
Normal file
@ -0,0 +1,260 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" // 引入sms
|
||||
)
|
||||
|
||||
func _SmsFullContent(template string, param []interface{}) string {
|
||||
var smsTemplateContentMap = map[string]string{
|
||||
viper.GetString("tencent.smsTemplateCode"): "验证码为:{1},您正在登录,若非本人操作,请勿泄露。",
|
||||
}
|
||||
return fmt.Sprintf(smsTemplateContentMap[template], param...)
|
||||
}
|
||||
|
||||
func _SmsSendVerifyCode(mobile, code string) (*sms.SendSmsResponseParams, error) {
|
||||
return _SmsSendByTemplate(viper.GetString("tencent.smsTemplateCode"), mobile, []string{code})
|
||||
}
|
||||
|
||||
func _SmsSendByTemplate(templateID, mobile string, param []string) (*sms.SendSmsResponseParams, error) {
|
||||
if !strings.HasPrefix(mobile, "+86") && !strings.HasPrefix(mobile, "86") {
|
||||
mobile = "+86" + mobile
|
||||
}
|
||||
credential := common.NewCredential(viper.GetString("tencent.secretId"), viper.GetString("tencent.secretKey"))
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.ReqMethod = "POST"
|
||||
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
|
||||
cpf.SignMethod = "HmacSHA1"
|
||||
client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)
|
||||
request := sms.NewSendSmsRequest()
|
||||
request.SmsSdkAppId = common.StringPtr(viper.GetString("tencent.sdkAppId"))
|
||||
request.SignName = common.StringPtr(viper.GetString("tencent.signName"))
|
||||
request.TemplateId = common.StringPtr(templateID)
|
||||
request.TemplateParamSet = common.StringPtrs(param)
|
||||
request.PhoneNumberSet = common.StringPtrs([]string{mobile})
|
||||
request.SessionContext = common.StringPtr("")
|
||||
request.ExtendCode = common.StringPtr("")
|
||||
request.SenderId = common.StringPtr("")
|
||||
response, err := client.SendSms(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Printf("An API error has returned: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Response, nil
|
||||
// {"SendStatusSet":[{"SerialNo":"4012:319315169316952992330592922","PhoneNumber":"+8615215229221","Fee":1,"SessionContext":"","Code":"Ok","Message":"send success","IsoCode":"CN"}],"RequestId":"82798726-4312-4ea8-926b-b23894a39cf1"}
|
||||
}
|
||||
|
||||
func _SmsQueryStatus(mobile string) (*sms.PullSmsSendStatusByPhoneNumberResponseParams, error) {
|
||||
credential := common.NewCredential(viper.GetString("tencent.secretId"), viper.GetString("tencent.secretKey"))
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.ReqMethod = "POST"
|
||||
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
|
||||
cpf.SignMethod = "HmacSHA1"
|
||||
client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)
|
||||
request := sms.NewPullSmsSendStatusByPhoneNumberRequest()
|
||||
request.SmsSdkAppId = common.StringPtr(viper.GetString("tencent.sdkAppId"))
|
||||
request.Limit = common.Uint64Ptr(10)
|
||||
request.PhoneNumber = common.StringPtr(mobile)
|
||||
request.BeginTime = common.Uint64Ptr(uint64(time.Now().Unix()) - (60 * 60))
|
||||
request.Offset = common.Uint64Ptr(0)
|
||||
response, err := client.PullSmsSendStatusByPhoneNumber(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Printf("An API error has returned: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Response, nil
|
||||
// {"PullSmsSendStatusSet":[{"UserReceiveTime":1695299239,"CountryCode":"86","SubscriberNumber":"15215229221","PhoneNumber":"+8615215229221","SerialNo":"4012:319315169316952992330592922","ReportStatus":"SUCCESS","Description":"DELIVRD","SessionContext":""}],"RequestId":"40476317-2cf4-4c48-bf30-2b33ac14a8b7"}
|
||||
}
|
||||
|
||||
func _SendSms() {
|
||||
/* 必要步骤:
|
||||
* 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
|
||||
* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
|
||||
* 您也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
|
||||
* 以免泄露密钥对危及您的财产安全。
|
||||
* SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */
|
||||
credential := common.NewCredential(
|
||||
// os.Getenv("TENCENTCLOUD_SECRET_ID"),
|
||||
// os.Getenv("TENCENTCLOUD_SECRET_KEY"),
|
||||
viper.GetString("tencent.secretId"),
|
||||
viper.GetString("tencent.secretKey"),
|
||||
)
|
||||
/* 非必要步骤:
|
||||
* 实例化一个客户端配置对象,可以指定超时时间等配置 */
|
||||
cpf := profile.NewClientProfile()
|
||||
|
||||
/* SDK默认使用POST方法。
|
||||
* 如果您一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */
|
||||
cpf.HttpProfile.ReqMethod = "POST"
|
||||
|
||||
/* SDK有默认的超时时间,非必要请不要进行调整
|
||||
* 如有需要请在代码中查阅以获取最新的默认值 */
|
||||
// cpf.HttpProfile.ReqTimeout = 5
|
||||
|
||||
/* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */
|
||||
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
|
||||
|
||||
/* SDK默认用TC3-HMAC-SHA256进行签名,非必要请不要修改这个字段 */
|
||||
cpf.SignMethod = "HmacSHA1"
|
||||
|
||||
/* 实例化要请求产品(以sms为例)的client对象
|
||||
* 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */
|
||||
client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)
|
||||
|
||||
/* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
|
||||
* 您可以直接查询SDK源码确定接口有哪些属性可以设置
|
||||
* 属性可能是基本类型,也可能引用了另一个数据结构
|
||||
* 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */
|
||||
request := sms.NewSendSmsRequest()
|
||||
|
||||
/* 基本类型的设置:
|
||||
* SDK采用的是指针风格指定参数,即使对于基本类型您也需要用指针来对参数赋值。
|
||||
* SDK提供对基本类型的指针引用封装函数
|
||||
* 帮助链接:
|
||||
* 短信控制台: https://console.cloud.tencent.com/smsv2
|
||||
* 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */
|
||||
|
||||
/* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */
|
||||
// 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
|
||||
request.SmsSdkAppId = common.StringPtr(viper.GetString("tencent.sdkAppId"))
|
||||
|
||||
/* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */
|
||||
// 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看
|
||||
request.SignName = common.StringPtr("好出行Trip小程序")
|
||||
|
||||
/* 模板 ID: 必须填写已审核通过的模板 ID */
|
||||
// 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看
|
||||
request.TemplateId = common.StringPtr("1916515")
|
||||
|
||||
/* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空*/
|
||||
request.TemplateParamSet = common.StringPtrs([]string{"6666"})
|
||||
|
||||
/* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
|
||||
* 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/
|
||||
request.PhoneNumberSet = common.StringPtrs([]string{"+8615215229221"})
|
||||
|
||||
/* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回 */
|
||||
request.SessionContext = common.StringPtr("")
|
||||
|
||||
/* 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] */
|
||||
request.ExtendCode = common.StringPtr("")
|
||||
|
||||
/* 国内短信无需填写该项;国际/港澳台短信已申请独立 SenderId 需要填写该字段,默认使用公共 SenderId,无需填写该字段。注:月度使用量达到指定量级可申请独立 SenderId 使用,详情请联系 [腾讯云短信小助手](https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81)。 */
|
||||
request.SenderId = common.StringPtr("")
|
||||
|
||||
// 通过client对象调用想要访问的接口,需要传入请求对象
|
||||
response, err := client.SendSms(request)
|
||||
// 处理异常
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Printf("An API error has returned: %s", err)
|
||||
return
|
||||
}
|
||||
// 非SDK异常,直接失败。实际代码中可以加入其他的处理。
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b, _ := json.Marshal(response.Response)
|
||||
// 打印返回的json字符串
|
||||
fmt.Printf("%s", b)
|
||||
|
||||
// {"SendStatusSet":[{"SerialNo":"4012:319315169316952992330592922","PhoneNumber":"+8615215229221","Fee":1,"SessionContext":"","Code":"Ok","Message":"send success","IsoCode":"CN"}],"RequestId":"82798726-4312-4ea8-926b-b23894a39cf1"}
|
||||
|
||||
/* 当出现以下错误码时,快速解决方案参考
|
||||
* [FailedOperation.SignatureIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.signatureincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
|
||||
* [FailedOperation.TemplateIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.templateincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
|
||||
* [UnauthorizedOperation.SmsSdkAppIdVerifyFail](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunauthorizedoperation.smssdkappidverifyfail-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
|
||||
* [UnsupportedOperation.ContainDomesticAndInternationalPhoneNumber](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunsupportedoperation.containdomesticandinternationalphonenumber-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
|
||||
* 更多错误,可咨询[腾讯云助手](https://tccc.qcloud.com/web/im/index.html#/chat?webAppId=8fa15978f85cb41f7e2ea36920cb3ae1&title=Sms)
|
||||
*/
|
||||
}
|
||||
|
||||
func _PullStatus() {
|
||||
/* 必要步骤:
|
||||
* 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
|
||||
* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
|
||||
* 您也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
|
||||
* 以免泄露密钥对危及您的财产安全。
|
||||
* SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */
|
||||
credential := common.NewCredential(
|
||||
// os.Getenv("TENCENTCLOUD_SECRET_ID"),
|
||||
// os.Getenv("TENCENTCLOUD_SECRET_KEY"),
|
||||
viper.GetString("tencent.secretId"),
|
||||
viper.GetString("tencent.secretKey"),
|
||||
)
|
||||
/* 非必要步骤:
|
||||
* 实例化一个客户端配置对象,可以指定超时时间等配置 */
|
||||
cpf := profile.NewClientProfile()
|
||||
|
||||
/* SDK默认使用POST方法。
|
||||
* 如果您一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */
|
||||
cpf.HttpProfile.ReqMethod = "POST"
|
||||
|
||||
/* SDK有默认的超时时间,非必要请不要进行调整
|
||||
* 如有需要请在代码中查阅以获取最新的默认值 */
|
||||
// cpf.HttpProfile.ReqTimeout = 5
|
||||
|
||||
/* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */
|
||||
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
|
||||
|
||||
/* SDK默认用TC3-HMAC-SHA256进行签名
|
||||
* 非必要请不要修改这个字段 */
|
||||
cpf.SignMethod = "HmacSHA1"
|
||||
|
||||
/* 实例化要请求产品(以sms为例)的client对象
|
||||
* 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */
|
||||
client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)
|
||||
|
||||
/* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
|
||||
* 您可以直接查询SDK源码确定接口有哪些属性可以设置
|
||||
* 属性可能是基本类型,也可能引用了另一个数据结构
|
||||
* 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */
|
||||
request := sms.NewPullSmsSendStatusByPhoneNumberRequest()
|
||||
|
||||
/* 基本类型的设置:
|
||||
* SDK采用的是指针风格指定参数,即使对于基本类型您也需要用指针来对参数赋值。
|
||||
* SDK提供对基本类型的指针引用封装函数
|
||||
* 帮助链接:
|
||||
* 短信控制台: https://console.cloud.tencent.com/smsv2
|
||||
* 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */
|
||||
|
||||
/* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */
|
||||
request.SmsSdkAppId = common.StringPtr(viper.GetString("tencent.sdkAppId"))
|
||||
/* 拉取最大条数,最多100条 */
|
||||
request.Limit = common.Uint64Ptr(10)
|
||||
PhoneNumber := "+8615215229221"
|
||||
request.PhoneNumber = &PhoneNumber
|
||||
beginTime := uint64(time.Now().Unix()) - (60 * 60)
|
||||
request.BeginTime = &beginTime
|
||||
Offset := uint64(0)
|
||||
request.Offset = &Offset
|
||||
|
||||
// 通过client对象调用想要访问的接口,需要传入请求对象
|
||||
response, err := client.PullSmsSendStatusByPhoneNumber(request)
|
||||
// 处理异常
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Printf("An API error has returned: %s", err)
|
||||
return
|
||||
}
|
||||
// 非SDK异常,直接失败。实际代码中可以加入其他的处理。
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b, _ := json.Marshal(response.Response)
|
||||
// 打印返回的json字符串
|
||||
fmt.Printf("%s", b)
|
||||
// {"PullSmsSendStatusSet":[{"UserReceiveTime":1695299239,"CountryCode":"86","SubscriberNumber":"15215229221","PhoneNumber":"+8615215229221","SerialNo":"4012:319315169316952992330592922","ReportStatus":"SUCCESS","Description":"DELIVRD","SessionContext":""}],"RequestId":"40476317-2cf4-4c48-bf30-2b33ac14a8b7"}
|
||||
}
|
||||
74
pkg/client/tencent_test.go
Normal file
74
pkg/client/tencent_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111"
|
||||
)
|
||||
|
||||
func TestSendSms(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "test01",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_SendSms()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "test01",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_PullStatus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmsSendByTemplate(t *testing.T) {
|
||||
type args struct {
|
||||
templateID string
|
||||
mobile string
|
||||
param []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *sms.SendSmsResponseParams
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "01",
|
||||
args: args{
|
||||
templateID: "2319145",
|
||||
mobile: "",
|
||||
param: []string{"6666"},
|
||||
},
|
||||
want: &sms.SendSmsResponseParams{},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := _SmsSendByTemplate(tt.args.templateID, tt.args.mobile, tt.args.param)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SmsSendByTemplate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
// if !reflect.DeepEqual(got, tt.want) {
|
||||
// t.Errorf("SmsSendByTemplate() = %v, want %v", got, tt.want)
|
||||
// }
|
||||
})
|
||||
}
|
||||
}
|
||||
215
pkg/client/wechat_client.go
Normal file
215
pkg/client/wechat_client.go
Normal file
@ -0,0 +1,215 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
UserAppID = ""
|
||||
UserSecret = ""
|
||||
DriverAppID = ""
|
||||
DriverSecret = ""
|
||||
Code2SessionAppURL = "https://api.weixin.qq.com/sns/oauth2/access_token"
|
||||
UserInfoAppURL = "https://api.weixin.qq.com/sns/userinfo"
|
||||
|
||||
Code2SessionJsURL = "https://api.weixin.qq.com/sns/jscode2session"
|
||||
|
||||
SendMessageURL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN"
|
||||
GetAccessTokenURL = "https://api.weixin.qq.com/cgi-bin/token"
|
||||
)
|
||||
|
||||
func WeChatNewAuthJsReqMap(code string) map[string]string {
|
||||
return map[string]string{
|
||||
"appid": UserAppID,
|
||||
"secret": UserSecret,
|
||||
"js_code": code,
|
||||
"grant_type": "authorization_code"}
|
||||
}
|
||||
|
||||
func WeChatNewAuthAppReqMap(code string) map[string]string {
|
||||
return map[string]string{
|
||||
"appid": UserAppID,
|
||||
"secret": UserSecret,
|
||||
"code": code,
|
||||
"grant_type": "authorization_code"}
|
||||
}
|
||||
|
||||
func WeChatGetAccessTokenReq(_type int) map[string]string {
|
||||
switch _type {
|
||||
case 1: // 用户
|
||||
return map[string]string{
|
||||
"grant_type": "client_credential",
|
||||
"appid": UserAppID,
|
||||
"secret": UserSecret,
|
||||
}
|
||||
case 2: // 其他
|
||||
return map[string]string{
|
||||
"grant_type": "client_credential",
|
||||
"appid": DriverAppID,
|
||||
"secret": DriverSecret,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 小程序换取token的返回
|
||||
type WeChatLoginJsRes struct {
|
||||
SessionKey string `json:"session_key"`
|
||||
UnionID string `json:"unionid"`
|
||||
OpenID string `json:"openid"`
|
||||
ErrCode int32 `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
// APP换取token的返回
|
||||
type WeChatLoginAppRes struct {
|
||||
AccessToken string `json:"access_token"` // 接口调用凭证
|
||||
ExpiresIn int32 `json:"expires_in"` // access_token 接口调用凭证超时时间,单位(秒)
|
||||
RefreshToken string `json:"refresh_token"` // 用户刷新 access_token
|
||||
UnionID string `json:"unionid"` // 用户统一标识。针对一个微信开放平台账号下的应用,同一用户的 unionid 是唯一的
|
||||
OpenID string `json:"openid"` // 授权用户唯一标识
|
||||
Scope string `json:"scope"` // 用户授权的作用域,使用逗号(,)分隔
|
||||
ErrCode int32 `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
// APP获取用户信息
|
||||
type WeChatUserInfoAppRes struct {
|
||||
UnionID string `json:"unionid"` // 用户统一标识。针对一个微信开放平台账号下的应用,同一用户的 unionid 是唯一的
|
||||
OpenID string `json:"openid"` // 授权用户唯一标识
|
||||
Nickname string `json:"nickname"` // 普通用户昵称
|
||||
Sex int32 `json:"sex"` // 普通用户性别,1 为男性,2 为女性
|
||||
Province string `json:"province"` // 普通用户个人资料填写的省份
|
||||
City string `json:"city"` // 普通用户个人资料填写的城市
|
||||
Country string `json:"country"` // 国家,如中国为 CN
|
||||
Avatar string `json:"headimgurl"` // 用户头像,最后一个数值代表正方形头像大小(有 0、46、64、96、132 数值可选,0 代表 640*640 正方形头像),用户没有头像时该项为空
|
||||
ErrCode int32 `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
// 换取token所用的请求
|
||||
func WeChatNewAuthReqMapUser(code string) map[string]string {
|
||||
return map[string]string{
|
||||
"appid": UserAppID,
|
||||
"secret": UserSecret,
|
||||
"js_code": code,
|
||||
"grant_type": "authorization_code"}
|
||||
}
|
||||
|
||||
// 小程序换取token的请求
|
||||
func WeChatCode2JsSession(headers map[string]string, params map[string]string) (wxLoginBody WeChatLoginJsRes, e error) {
|
||||
var (
|
||||
client = NewClient()
|
||||
resp *http.Response
|
||||
res []byte
|
||||
)
|
||||
req, _ := http.NewRequest(http.MethodGet, Code2SessionJsURL, nil)
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
q := req.URL.Query()
|
||||
for k, v := range params {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
if resp, e = client.Do(req); e != nil {
|
||||
return
|
||||
}
|
||||
defer func(body io.ReadCloser) {
|
||||
_ = body.Close()
|
||||
}(resp.Body)
|
||||
if res, e = io.ReadAll(resp.Body); e != nil {
|
||||
return
|
||||
}
|
||||
if !success(resp.StatusCode) {
|
||||
e = fmt.Errorf("status code error: %s", resp.Status)
|
||||
}
|
||||
if e = json.Unmarshal(res, &wxLoginBody); e != nil {
|
||||
return
|
||||
}
|
||||
if wxLoginBody.ErrCode != 0 {
|
||||
e = fmt.Errorf("获取微信登录凭证错误,错误码: %d 错误信息:%s", wxLoginBody.ErrCode, wxLoginBody.ErrMsg)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// APP换取token的请求
|
||||
func WeChatCode2AppToken(headers map[string]string, params map[string]string) (wxLoginBody WeChatLoginAppRes, e error) {
|
||||
var (
|
||||
client = NewClient()
|
||||
resp *http.Response
|
||||
res []byte
|
||||
)
|
||||
req, _ := http.NewRequest(http.MethodGet, Code2SessionAppURL, nil)
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
q := req.URL.Query()
|
||||
for k, v := range params {
|
||||
q.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
if resp, e = client.Do(req); e != nil {
|
||||
return
|
||||
}
|
||||
defer func(body io.ReadCloser) {
|
||||
_ = body.Close()
|
||||
}(resp.Body)
|
||||
if res, e = io.ReadAll(resp.Body); e != nil {
|
||||
return
|
||||
}
|
||||
if !success(resp.StatusCode) {
|
||||
e = fmt.Errorf("status code error: %s", resp.Status)
|
||||
}
|
||||
if e = json.Unmarshal(res, &wxLoginBody); e != nil {
|
||||
return
|
||||
}
|
||||
if wxLoginBody.ErrCode != 0 {
|
||||
e = fmt.Errorf("获取微信登录凭证错误,错误码: %d 错误信息:%s", wxLoginBody.ErrCode, wxLoginBody.ErrMsg)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// APP获取用户信息
|
||||
func WeChatUserInfoApp(token, openId string) (wxUserInfoBody WeChatUserInfoAppRes, e error) {
|
||||
var (
|
||||
client = NewClient()
|
||||
resp *http.Response
|
||||
res []byte
|
||||
)
|
||||
req, _ := http.NewRequest(http.MethodGet, UserInfoAppURL, nil)
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("access_token", token)
|
||||
q.Add("openid", openId)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
if resp, e = client.Do(req); e != nil {
|
||||
return
|
||||
}
|
||||
defer func(body io.ReadCloser) {
|
||||
_ = body.Close()
|
||||
}(resp.Body)
|
||||
if res, e = io.ReadAll(resp.Body); e != nil {
|
||||
return
|
||||
}
|
||||
if !success(resp.StatusCode) {
|
||||
e = fmt.Errorf("status code error: %s", resp.Status)
|
||||
}
|
||||
if e = json.Unmarshal(res, &wxUserInfoBody); e != nil {
|
||||
return
|
||||
}
|
||||
if wxUserInfoBody.ErrCode != 0 {
|
||||
e = fmt.Errorf("获取微信登录凭证错误,错误码: %d 错误信息:%s", wxUserInfoBody.ErrCode, wxUserInfoBody.ErrMsg)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func success(code int) bool {
|
||||
return code >= 200 && code < 400
|
||||
}
|
||||
76
pkg/client/weichat_notice.go
Normal file
76
pkg/client/weichat_notice.go
Normal file
@ -0,0 +1,76 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WeGetTokenRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
func WeGetToken() (token string, e error) {
|
||||
gotRes, err := Get(GetAccessTokenURL, nil, WeChatGetAccessTokenReq(1))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var body WeGetTokenRes
|
||||
if e = json.Unmarshal(gotRes, &body); e != nil {
|
||||
return
|
||||
}
|
||||
token = body.AccessToken
|
||||
return
|
||||
}
|
||||
|
||||
type WeSendMessageReq map[string]WeSendMessageItemReq
|
||||
|
||||
type WeSendMessageItemReq struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func WeSendMessage(openID, templateID string, message WeSendMessageReq, token string) (e error) {
|
||||
if len(token) == 0 {
|
||||
if token, e = WeGetToken(); e != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
gotRes, err := Post(strings.ReplaceAll(SendMessageURL, "ACCESS_TOKEN", token), nil, map[string]interface{}{
|
||||
"template_id": templateID,
|
||||
"touser": openID,
|
||||
"data": message,
|
||||
"miniprogram_state": "formal", // developer为开发版;trial为体验版;formal为正式版;默认为正式版
|
||||
"lang": "zh_CN", // 进入小程序查看”的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为 zh_CN
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s\n", string(gotRes))
|
||||
return
|
||||
}
|
||||
|
||||
func WeSendNoticeTakeOrder(openID, company, driver, vehicel string, date time.Time) error {
|
||||
const tid = "Ff5tDsIlBy-t51Usxh_oBvge9WbMbBFVKI020hIpWcA"
|
||||
return WeSendMessage(openID, tid, messageNoticeTakeOrder(company, driver, vehicel, date), "")
|
||||
}
|
||||
|
||||
// messageBookReminder 预约提醒消息
|
||||
func messageNoticeTakeOrder(company, driver, vehicel string, date time.Time) WeSendMessageReq {
|
||||
return WeSendMessageReq{
|
||||
"thing1": WeSendMessageItemReq{Value: company}, // 公司名称
|
||||
"thing2": WeSendMessageItemReq{Value: driver}, // 接单司机
|
||||
"car_number3": WeSendMessageItemReq{Value: vehicel}, // 车牌号
|
||||
"time4": WeSendMessageItemReq{Value: StrFromTime(date)}, // 接单时间
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
TimeFormat = "2006-01-02 15:04:05"
|
||||
DateFormat = "2006-01-02"
|
||||
)
|
||||
|
||||
func StrFromTime(t time.Time) string {
|
||||
return t.Format(TimeFormat)
|
||||
}
|
||||
Reference in New Issue
Block a user