✨ 初始化项目
This commit is contained in:
108
internal/conv/conv.go
Normal file
108
internal/conv/conv.go
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Email:1711788888@qq.com
|
||||
* @Date: 2021/8/31 11:16 下午
|
||||
* @Desc: TODO
|
||||
*/
|
||||
|
||||
package conv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stringInterface interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
type errorInterface interface {
|
||||
Error() string
|
||||
}
|
||||
|
||||
func String(any interface{}) string {
|
||||
switch v := any.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case string:
|
||||
return v
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case int8:
|
||||
return strconv.Itoa(int(v))
|
||||
case int16:
|
||||
return strconv.Itoa(int(v))
|
||||
case int32:
|
||||
return strconv.Itoa(int(v))
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(v, 10)
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
case bool:
|
||||
return strconv.FormatBool(v)
|
||||
case []byte:
|
||||
return string(v)
|
||||
case time.Time:
|
||||
return v.String()
|
||||
case *time.Time:
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return v.String()
|
||||
default:
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if i, ok := v.(stringInterface); ok {
|
||||
return i.String()
|
||||
}
|
||||
|
||||
if i, ok := v.(errorInterface); ok {
|
||||
return i.Error()
|
||||
}
|
||||
|
||||
var (
|
||||
rv = reflect.ValueOf(v)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
|
||||
switch kind {
|
||||
case reflect.Chan,
|
||||
reflect.Map,
|
||||
reflect.Slice,
|
||||
reflect.Func,
|
||||
reflect.Ptr,
|
||||
reflect.Interface,
|
||||
reflect.UnsafePointer:
|
||||
if rv.IsNil() {
|
||||
return ""
|
||||
}
|
||||
case reflect.String:
|
||||
return rv.String()
|
||||
}
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
return String(rv.Elem().Interface())
|
||||
}
|
||||
|
||||
if b, e := json.Marshal(v); e != nil {
|
||||
return fmt.Sprint(v)
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
}
|
146
internal/core/client.go
Normal file
146
internal/core/client.go
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Email:1711788888@qq.com
|
||||
* @Date: 2021/8/27 11:31 上午
|
||||
* @Desc: TODO
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"git.echol.cn/loser/http"
|
||||
|
||||
"git.echol.cn/loser/tencent-im/internal/enum"
|
||||
"git.echol.cn/loser/tencent-im/internal/sign"
|
||||
"git.echol.cn/loser/tencent-im/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseUrl = "https://console.tim.qq.com"
|
||||
defaultVersion = "v4"
|
||||
defaultContentType = "json"
|
||||
defaultExpiration = 3600
|
||||
)
|
||||
|
||||
var invalidResponse = NewError(enum.InvalidResponseCode, "invalid response")
|
||||
|
||||
type Client interface {
|
||||
// Get GET请求
|
||||
Get(serviceName string, command string, data interface{}, resp interface{}) error
|
||||
// Post POST请求
|
||||
Post(serviceName string, command string, data interface{}, resp interface{}) error
|
||||
// Put PUT请求
|
||||
Put(serviceName string, command string, data interface{}, resp interface{}) error
|
||||
// Patch PATCH请求
|
||||
Patch(serviceName string, command string, data interface{}, resp interface{}) error
|
||||
// Delete DELETE请求
|
||||
Delete(serviceName string, command string, data interface{}, resp interface{}) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
client *http.Client
|
||||
opt *Options
|
||||
userSig string
|
||||
userSigExpireAt int64
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
AppId int // 应用SDKAppID,可在即时通信 IM 控制台 的应用卡片中获取。
|
||||
AppSecret string // 密钥信息,可在即时通信 IM 控制台 的应用详情页面中获取,具体操作请参见 获取密钥
|
||||
UserId string // 用户ID
|
||||
Expiration int // UserSig过期时间
|
||||
}
|
||||
|
||||
func NewClient(opt *Options) Client {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
c := new(client)
|
||||
c.opt = opt
|
||||
c.client = http.NewClient()
|
||||
c.client.SetContentType(http.ContentTypeJson)
|
||||
c.client.SetBaseUrl(defaultBaseUrl)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Get GET请求
|
||||
func (c *client) Get(serviceName string, command string, data interface{}, resp interface{}) error {
|
||||
return c.request(http.MethodGet, serviceName, command, data, resp)
|
||||
}
|
||||
|
||||
// Post POST请求
|
||||
func (c *client) Post(serviceName string, command string, data interface{}, resp interface{}) error {
|
||||
return c.request(http.MethodPost, serviceName, command, data, resp)
|
||||
}
|
||||
|
||||
// Put PUT请求
|
||||
func (c *client) Put(serviceName string, command string, data interface{}, resp interface{}) error {
|
||||
return c.request(http.MethodPut, serviceName, command, data, resp)
|
||||
}
|
||||
|
||||
// Patch PATCH请求
|
||||
func (c *client) Patch(serviceName string, command string, data interface{}, resp interface{}) error {
|
||||
return c.request(http.MethodPatch, serviceName, command, data, resp)
|
||||
}
|
||||
|
||||
// Delete DELETE请求
|
||||
func (c *client) Delete(serviceName string, command string, data interface{}, resp interface{}) error {
|
||||
return c.request(http.MethodDelete, serviceName, command, data, resp)
|
||||
}
|
||||
|
||||
// request Request请求
|
||||
func (c *client) request(method, serviceName, command string, data, resp interface{}) error {
|
||||
res, err := c.client.Request(method, c.buildUrl(serviceName, command), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = res.Scan(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r, ok := resp.(types.ActionBaseRespInterface); ok {
|
||||
if r.GetActionStatus() == enum.FailActionStatus {
|
||||
return NewError(r.GetErrorCode(), r.GetErrorInfo())
|
||||
}
|
||||
|
||||
if r.GetErrorCode() != enum.SuccessCode {
|
||||
return NewError(r.GetErrorCode(), r.GetErrorInfo())
|
||||
}
|
||||
} else if r, ok := resp.(types.BaseRespInterface); ok {
|
||||
if r.GetErrorCode() != enum.SuccessCode {
|
||||
return NewError(r.GetErrorCode(), r.GetErrorInfo())
|
||||
}
|
||||
} else {
|
||||
return invalidResponse
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildUrl 构建一个请求URL
|
||||
func (c *client) buildUrl(serviceName string, command string) string {
|
||||
format := "/%s/%s/%s?sdkappid=%d&identifier=%s&usersig=%s&random=%d&contenttype=%s"
|
||||
random := rand.Int31()
|
||||
userSig := c.getUserSig()
|
||||
return fmt.Sprintf(format, defaultVersion, serviceName, command, c.opt.AppId, c.opt.UserId, userSig, random, defaultContentType)
|
||||
}
|
||||
|
||||
// getUserSig 获取签名
|
||||
func (c *client) getUserSig() string {
|
||||
now, expiration := time.Now(), c.opt.Expiration
|
||||
|
||||
if expiration <= 0 {
|
||||
expiration = defaultExpiration
|
||||
}
|
||||
|
||||
if c.userSig == "" || c.userSigExpireAt <= now.Unix() {
|
||||
c.userSig, _ = sign.GenUserSig(c.opt.AppId, c.opt.AppSecret, c.opt.UserId, expiration)
|
||||
c.userSigExpireAt = now.Add(time.Duration(expiration) * time.Second).Unix()
|
||||
}
|
||||
|
||||
return c.userSig
|
||||
}
|
38
internal/core/error.go
Normal file
38
internal/core/error.go
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Email:1711788888@qq.com
|
||||
* @Date: 2021/8/27 1:12 下午
|
||||
* @Desc: TODO
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
Code() int
|
||||
Message() string
|
||||
}
|
||||
|
||||
type respError struct {
|
||||
code int
|
||||
message string
|
||||
}
|
||||
|
||||
func NewError(code int, message string) Error {
|
||||
return &respError{
|
||||
code: code,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *respError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func (e *respError) Code() int {
|
||||
return e.code
|
||||
}
|
||||
|
||||
func (e *respError) Message() string {
|
||||
return e.message
|
||||
}
|
170
internal/entity/mesage.go
Normal file
170
internal/entity/mesage.go
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Author:1711788888@qq.com
|
||||
* @Date: 2021/9/3 18:19
|
||||
* @Desc: 基础消息实体
|
||||
*/
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"git.echol.cn/loser/tencent-im/internal/enum"
|
||||
"git.echol.cn/loser/tencent-im/internal/types"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidMsgContent = errors.New("invalid message content")
|
||||
errInvalidMsgLifeTime = errors.New("invalid message life time")
|
||||
errNotSetMsgContent = errors.New("message content is not set")
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
sender string // 发送方UserId
|
||||
lifeTime int // 消息离线保存时长(单位:秒),最长为7天(604800秒)
|
||||
random uint32 // 消息随机数,由随机函数产生
|
||||
body []*types.MsgBody // 消息体
|
||||
offlinePush *offlinePush // 推送实体
|
||||
}
|
||||
|
||||
// SetSender 设置发送方UserId
|
||||
func (m *Message) SetSender(userId string) {
|
||||
m.sender = userId
|
||||
}
|
||||
|
||||
// GetSender 获取发送者
|
||||
func (m *Message) GetSender() string {
|
||||
return m.sender
|
||||
}
|
||||
|
||||
// SetLifeTime 设置消息离线保存时长
|
||||
func (m *Message) SetLifeTime(lifeTime int) {
|
||||
m.lifeTime = lifeTime
|
||||
}
|
||||
|
||||
// GetLifeTime 获取消息离线保存时长
|
||||
func (m *Message) GetLifeTime() int {
|
||||
return m.lifeTime
|
||||
}
|
||||
|
||||
// SetRandom 设置消息随机数
|
||||
func (m *Message) SetRandom(random uint32) {
|
||||
m.random = random
|
||||
}
|
||||
|
||||
// GetRandom 获取消息随机数
|
||||
func (m *Message) GetRandom() uint32 {
|
||||
if m.random == 0 {
|
||||
m.random = rand.Uint32()
|
||||
}
|
||||
|
||||
return m.random
|
||||
}
|
||||
|
||||
// AddContent 添加消息内容(添加会累加之前的消息内容)
|
||||
func (m *Message) AddContent(msgContent ...interface{}) {
|
||||
if m.body == nil {
|
||||
m.body = make([]*types.MsgBody, 0)
|
||||
}
|
||||
|
||||
if len(msgContent) > 0 {
|
||||
var msgType string
|
||||
for _, content := range msgContent {
|
||||
switch content.(type) {
|
||||
case types.MsgTextContent, *types.MsgTextContent:
|
||||
msgType = enum.MsgText
|
||||
case types.MsgLocationContent, *types.MsgLocationContent:
|
||||
msgType = enum.MsgLocation
|
||||
case types.MsgFaceContent, *types.MsgFaceContent:
|
||||
msgType = enum.MsgFace
|
||||
case types.MsgCustomContent, *types.MsgCustomContent:
|
||||
msgType = enum.MsgCustom
|
||||
case types.MsgSoundContent, *types.MsgSoundContent:
|
||||
msgType = enum.MsgSound
|
||||
case types.MsgImageContent, *types.MsgImageContent:
|
||||
msgType = enum.MsgImage
|
||||
case types.MsgFileContent, *types.MsgFileContent:
|
||||
msgType = enum.MsgFile
|
||||
case types.MsgVideoContent, *types.MsgVideoContent:
|
||||
msgType = enum.MsgVideo
|
||||
default:
|
||||
msgType = ""
|
||||
}
|
||||
|
||||
m.body = append(m.body, &types.MsgBody{
|
||||
MsgType: msgType,
|
||||
MsgContent: content,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetContent 设置消息内容(设置会冲掉之前的消息内容)
|
||||
func (m *Message) SetContent(msgContent ...interface{}) {
|
||||
if m.body != nil {
|
||||
m.body = m.body[0:0]
|
||||
}
|
||||
m.AddContent(msgContent...)
|
||||
}
|
||||
|
||||
// GetBody 获取消息体
|
||||
func (m *Message) GetBody() []*types.MsgBody {
|
||||
return m.body
|
||||
}
|
||||
|
||||
// OfflinePush 新建离线推送对象
|
||||
func (m *Message) OfflinePush() *offlinePush {
|
||||
if m.offlinePush == nil {
|
||||
m.offlinePush = newOfflinePush()
|
||||
}
|
||||
|
||||
return m.offlinePush
|
||||
}
|
||||
|
||||
// GetOfflinePushInfo 获取离线推送消息
|
||||
func (m *Message) GetOfflinePushInfo() *types.OfflinePushInfo {
|
||||
if m.offlinePush == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &types.OfflinePushInfo{
|
||||
PushFlag: m.offlinePush.pushFlag,
|
||||
Title: m.offlinePush.title,
|
||||
Desc: m.offlinePush.desc,
|
||||
Ext: m.offlinePush.ext,
|
||||
AndroidInfo: m.offlinePush.androidInfo,
|
||||
ApnsInfo: m.offlinePush.apnsInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// CheckLifeTimeArgError 检测参数错误
|
||||
func (m *Message) CheckLifeTimeArgError() error {
|
||||
if m.body != nil && len(m.body) > 0 {
|
||||
for _, item := range m.body {
|
||||
if item.MsgType == "" {
|
||||
return errInvalidMsgContent
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return errNotSetMsgContent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckBodyArgError 检测参数错误
|
||||
func (m *Message) CheckBodyArgError() error {
|
||||
if m.body != nil && len(m.body) > 0 {
|
||||
for _, item := range m.body {
|
||||
if item.MsgType == "" {
|
||||
return errInvalidMsgContent
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return errNotSetMsgContent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
150
internal/entity/offline_push.go
Normal file
150
internal/entity/offline_push.go
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Author:1711788888@qq.com
|
||||
* @Date: 2021/9/3 18:47
|
||||
* @Desc: 离线推送
|
||||
*/
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/tencent-im/internal/conv"
|
||||
"git.echol.cn/loser/tencent-im/internal/types"
|
||||
)
|
||||
|
||||
type offlinePush struct {
|
||||
pushFlag int // 推送标识。0表示推送,1表示不离线推送。
|
||||
title string // 离线推送标题。该字段为 iOS 和 Android 共用。
|
||||
desc string // 离线推送内容。
|
||||
ext string // 离线推送透传内容。
|
||||
androidInfo *types.AndroidInfo // Android离线推送消息
|
||||
apnsInfo *types.ApnsInfo // IOS离线推送消息
|
||||
}
|
||||
|
||||
func newOfflinePush() *offlinePush {
|
||||
return &offlinePush{}
|
||||
}
|
||||
|
||||
// SetPushFlag 设置推送消息
|
||||
func (o *offlinePush) SetPushFlag(pushFlag types.PushFlag) {
|
||||
o.pushFlag = int(pushFlag)
|
||||
}
|
||||
|
||||
// SetTitle 设置离线推送标题
|
||||
func (o *offlinePush) SetTitle(title string) {
|
||||
o.title = title
|
||||
}
|
||||
|
||||
// SetDesc 设置离线推送内容
|
||||
func (o *offlinePush) SetDesc(desc string) {
|
||||
o.desc = desc
|
||||
}
|
||||
|
||||
// SetExt 设置离线推送透传内容
|
||||
func (o *offlinePush) SetExt(ext interface{}) {
|
||||
o.ext = conv.String(ext)
|
||||
}
|
||||
|
||||
// SetAndroidSound 设置Android离线推送声音文件路径
|
||||
func (o *offlinePush) SetAndroidSound(sound string) {
|
||||
if o.androidInfo == nil {
|
||||
o.androidInfo = &types.AndroidInfo{}
|
||||
}
|
||||
o.androidInfo.Sound = sound
|
||||
}
|
||||
|
||||
// SetAndroidHuaWeiChannelId 设置华为手机 EMUI 10.0 及以上的通知渠道字段
|
||||
func (o *offlinePush) SetAndroidHuaWeiChannelId(channelId string) {
|
||||
if o.androidInfo == nil {
|
||||
o.androidInfo = &types.AndroidInfo{}
|
||||
}
|
||||
o.androidInfo.HuaWeiChannelID = channelId
|
||||
}
|
||||
|
||||
// SetAndroidXiaoMiChannelId 设置小米手机 MIUI 10 及以上的通知类别(Channel)适配字段
|
||||
func (o *offlinePush) SetAndroidXiaoMiChannelId(channelId string) {
|
||||
if o.androidInfo == nil {
|
||||
o.androidInfo = &types.AndroidInfo{}
|
||||
}
|
||||
o.androidInfo.XiaoMiChannelID = channelId
|
||||
}
|
||||
|
||||
// SetAndroidOppoChannelId 设置OPPO手机 Android 8.0 及以上的 NotificationChannel 通知适配字段
|
||||
func (o *offlinePush) SetAndroidOppoChannelId(channelId string) {
|
||||
if o.androidInfo == nil {
|
||||
o.androidInfo = &types.AndroidInfo{}
|
||||
}
|
||||
o.androidInfo.OPPOChannelID = channelId
|
||||
}
|
||||
|
||||
// SetAndroidGoogleChannelId 设置Google 手机 Android 8.0 及以上的通知渠道字段
|
||||
func (o *offlinePush) SetAndroidGoogleChannelId(channelId string) {
|
||||
if o.androidInfo == nil {
|
||||
o.androidInfo = &types.AndroidInfo{}
|
||||
}
|
||||
o.androidInfo.GoogleChannelID = channelId
|
||||
}
|
||||
|
||||
// SetAndroidVivoClassification 设置VIVO 手机推送消息分类,“0”代表运营消息,“1”代表系统消息,不填默认为1
|
||||
func (o *offlinePush) SetAndroidVivoClassification(classification types.VivoClassification) {
|
||||
if o.androidInfo == nil {
|
||||
o.androidInfo = &types.AndroidInfo{}
|
||||
}
|
||||
o.androidInfo.VIVOClassification = int(classification)
|
||||
}
|
||||
|
||||
// SetAndroidHuaWeiImportance 设置华为推送通知消息分类
|
||||
func (o *offlinePush) SetAndroidHuaWeiImportance(importance types.HuaWeiImportance) {
|
||||
if o.androidInfo == nil {
|
||||
o.androidInfo = &types.AndroidInfo{}
|
||||
}
|
||||
o.androidInfo.HuaWeiImportance = string(importance)
|
||||
}
|
||||
|
||||
// SetAndroidExtAsHuaweiIntentParam 设置在控制台配置华为推送为“打开应用内指定页面”的前提下,传“1”表示将透传内容 Ext 作为 Intent 的参数,“0”表示将透传内容 Ext 作为 Action 参数。不填默认为0。
|
||||
func (o *offlinePush) SetAndroidExtAsHuaweiIntentParam(param types.HuaweiIntentParam) {
|
||||
if o.androidInfo == nil {
|
||||
o.androidInfo = &types.AndroidInfo{}
|
||||
}
|
||||
o.androidInfo.ExtAsHuaweiIntentParam = int(param)
|
||||
}
|
||||
|
||||
// SetApnsBadgeMode 设置IOS徽章计数模式
|
||||
func (o *offlinePush) SetApnsBadgeMode(badgeMode types.BadgeMode) {
|
||||
if o.apnsInfo == nil {
|
||||
o.apnsInfo = &types.ApnsInfo{}
|
||||
}
|
||||
o.apnsInfo.BadgeMode = int(badgeMode)
|
||||
}
|
||||
|
||||
// SetApnsTitle 设置APNs推送的标题
|
||||
func (o *offlinePush) SetApnsTitle(title string) {
|
||||
if o.apnsInfo == nil {
|
||||
o.apnsInfo = &types.ApnsInfo{}
|
||||
}
|
||||
o.apnsInfo.Title = title
|
||||
}
|
||||
|
||||
// SetApnsSubTitle 设置APNs推送的子标题
|
||||
func (o *offlinePush) SetApnsSubTitle(subTitle string) {
|
||||
if o.apnsInfo == nil {
|
||||
o.apnsInfo = &types.ApnsInfo{}
|
||||
}
|
||||
o.apnsInfo.SubTitle = subTitle
|
||||
}
|
||||
|
||||
// SetApnsImage 设置APNs携带的图片地址
|
||||
func (o *offlinePush) SetApnsImage(image string) {
|
||||
if o.apnsInfo == nil {
|
||||
o.apnsInfo = &types.ApnsInfo{}
|
||||
}
|
||||
o.apnsInfo.Image = image
|
||||
}
|
||||
|
||||
// SetApnsMutableContent 设置iOS10的推送扩展开关
|
||||
func (o *offlinePush) SetApnsMutableContent(mutable types.MutableContent) {
|
||||
if o.apnsInfo == nil {
|
||||
o.apnsInfo = &types.ApnsInfo{}
|
||||
}
|
||||
o.apnsInfo.MutableContent = int(mutable)
|
||||
}
|
330
internal/entity/user.go
Normal file
330
internal/entity/user.go
Normal file
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Email:1711788888@qq.com
|
||||
* @Date: 2021/8/30 4:23 下午
|
||||
* @Desc: 用户
|
||||
*/
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.echol.cn/loser/tencent-im/internal/core"
|
||||
"git.echol.cn/loser/tencent-im/internal/enum"
|
||||
"git.echol.cn/loser/tencent-im/internal/types"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
userId string
|
||||
attrs map[string]interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
// SetUserId 设置用户账号
|
||||
func (u *User) SetUserId(userId string) {
|
||||
u.userId = userId
|
||||
}
|
||||
|
||||
// GetUserId 获取用户账号
|
||||
func (u *User) GetUserId() string {
|
||||
return u.userId
|
||||
}
|
||||
|
||||
// SetNickname 设置昵称
|
||||
func (u *User) SetNickname(nickname string) {
|
||||
u.SetAttr(enum.StandardAttrNickname, nickname)
|
||||
}
|
||||
|
||||
// GetNickname 获取昵称
|
||||
func (u *User) GetNickname() (nickname string, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrNickname); exist {
|
||||
nickname = v.(string)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetGender 设置性别
|
||||
func (u *User) SetGender(gender types.GenderType) {
|
||||
u.SetAttr(enum.StandardAttrGender, gender)
|
||||
}
|
||||
|
||||
// GetGender 获取性别
|
||||
func (u *User) GetGender() (gender types.GenderType, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrGender); exist {
|
||||
gender = types.GenderType(v.(string))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetBirthday 设置生日
|
||||
func (u *User) SetBirthday(birthday time.Time) {
|
||||
b, _ := strconv.Atoi(birthday.Format("20060102"))
|
||||
u.SetAttr(enum.StandardAttrBirthday, b)
|
||||
}
|
||||
|
||||
// GetBirthday 获取昵称
|
||||
func (u *User) GetBirthday() (birthday time.Time, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrBirthday); exist {
|
||||
if val := v.(string); val != "" {
|
||||
birthday, _ = time.Parse("20060102", val)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetLocation 设置所在地
|
||||
func (u *User) SetLocation(country uint32, province uint32, city uint32, region uint32) {
|
||||
var (
|
||||
str string
|
||||
location = []uint32{country, province, city, region}
|
||||
builder strings.Builder
|
||||
)
|
||||
|
||||
builder.Grow(16)
|
||||
|
||||
for _, v := range location {
|
||||
str = strconv.Itoa(int(v))
|
||||
|
||||
if len(str) > 4 {
|
||||
u.SetError(enum.InvalidParamsCode, "invalid location params")
|
||||
break
|
||||
}
|
||||
|
||||
builder.WriteString(strings.Repeat("0", 4-len(str)))
|
||||
builder.WriteString(str)
|
||||
}
|
||||
|
||||
u.SetAttr(enum.StandardAttrLocation, builder.String())
|
||||
}
|
||||
|
||||
// GetLocation 获取所在地
|
||||
func (u *User) GetLocation() (country uint32, province uint32, city uint32, region uint32, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.attrs[enum.StandardAttrLocation]; exist {
|
||||
str := v.(string)
|
||||
|
||||
if len(str) != 16 {
|
||||
exist = false
|
||||
return
|
||||
}
|
||||
|
||||
if c, err := strconv.Atoi(str[0:4]); err != nil || c < 0 {
|
||||
exist = false
|
||||
return
|
||||
} else {
|
||||
country = uint32(c)
|
||||
}
|
||||
|
||||
if c, err := strconv.Atoi(str[4:8]); err != nil || c < 0 {
|
||||
exist = false
|
||||
return
|
||||
} else {
|
||||
province = uint32(c)
|
||||
}
|
||||
|
||||
if c, err := strconv.Atoi(str[8:12]); err != nil || c < 0 {
|
||||
exist = false
|
||||
return
|
||||
} else {
|
||||
city = uint32(c)
|
||||
}
|
||||
|
||||
if c, err := strconv.Atoi(str[12:16]); err != nil || c < 0 {
|
||||
exist = false
|
||||
return
|
||||
} else {
|
||||
region = uint32(c)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetSignature 设置个性签名
|
||||
func (u *User) SetSignature(signature string) {
|
||||
u.SetAttr(enum.StandardAttrSignature, signature)
|
||||
}
|
||||
|
||||
// GetSignature 获取个性签名
|
||||
func (u *User) GetSignature() (signature string, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrSignature); exist {
|
||||
signature = v.(string)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetAllowType 设置加好友验证方式
|
||||
func (u *User) SetAllowType(allowType types.AllowType) {
|
||||
u.SetAttr(enum.StandardAttrAllowType, allowType)
|
||||
}
|
||||
|
||||
// GetAllowType 获取加好友验证方式
|
||||
func (u *User) GetAllowType() (allowType types.AllowType, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrAllowType); exist {
|
||||
allowType = types.AllowType(v.(string))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetLanguage 设置语言
|
||||
func (u *User) SetLanguage(language uint) {
|
||||
u.SetAttr(enum.StandardAttrLanguage, language)
|
||||
}
|
||||
|
||||
// GetLanguage 获取语言
|
||||
func (u *User) GetLanguage() (language uint, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrLanguage); exist {
|
||||
language = uint(v.(float64))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetAvatar 设置头像URL
|
||||
func (u *User) SetAvatar(avatar string) {
|
||||
u.SetAttr(enum.StandardAttrAvatar, avatar)
|
||||
}
|
||||
|
||||
// GetAvatar 获取头像URL
|
||||
func (u *User) GetAvatar() (avatar string, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrAvatar); exist {
|
||||
avatar = v.(string)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetMsgSettings 设置消息设置
|
||||
func (u *User) SetMsgSettings(settings uint) {
|
||||
u.SetAttr(enum.StandardAttrMsgSettings, settings)
|
||||
}
|
||||
|
||||
// GetMsgSettings 获取消息设置
|
||||
func (u *User) GetMsgSettings() (settings uint, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrMsgSettings); exist {
|
||||
settings = uint(v.(float64))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetAdminForbidType 设置管理员禁止加好友标识
|
||||
func (u *User) SetAdminForbidType(forbidType types.AdminForbidType) {
|
||||
u.SetAttr(enum.StandardAttrAdminForbidType, forbidType)
|
||||
}
|
||||
|
||||
// GetAdminForbidType 获取管理员禁止加好友标识
|
||||
func (u *User) GetAdminForbidType() (forbidType types.AdminForbidType, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrAdminForbidType); exist {
|
||||
forbidType = types.AdminForbidType(v.(string))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetLevel 设置等级
|
||||
func (u *User) SetLevel(level uint) {
|
||||
u.SetAttr(enum.StandardAttrLevel, level)
|
||||
}
|
||||
|
||||
// GetLevel 获取等级
|
||||
func (u *User) GetLevel() (level uint, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrLevel); exist {
|
||||
level = uint(v.(float64))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetRole 设置角色
|
||||
func (u *User) SetRole(role uint) {
|
||||
u.SetAttr(enum.StandardAttrRole, role)
|
||||
}
|
||||
|
||||
// GetRole 获取角色
|
||||
func (u *User) GetRole() (role uint, exist bool) {
|
||||
var v interface{}
|
||||
|
||||
if v, exist = u.GetAttr(enum.StandardAttrRole); exist {
|
||||
role = uint(v.(float64))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetCustomAttr 设置自定义属性
|
||||
func (u *User) SetCustomAttr(name string, value interface{}) {
|
||||
u.SetAttr(fmt.Sprintf("%s_%s", enum.CustomAttrPrefix, name), value)
|
||||
}
|
||||
|
||||
// GetCustomAttr 获取自定义属性
|
||||
func (u *User) GetCustomAttr(name string) (val interface{}, exist bool) {
|
||||
val, exist = u.GetAttr(fmt.Sprintf("%s_%s", enum.CustomAttrPrefix, name))
|
||||
return
|
||||
}
|
||||
|
||||
// IsValid 检测用户是否有效
|
||||
func (u *User) IsValid() bool {
|
||||
return u.err == nil
|
||||
}
|
||||
|
||||
// SetError 设置异常错误
|
||||
func (u *User) SetError(code int, message string) {
|
||||
if code != enum.SuccessCode {
|
||||
u.err = core.NewError(code, message)
|
||||
}
|
||||
}
|
||||
|
||||
// GetError 获取异常错误
|
||||
func (u *User) GetError() error {
|
||||
return u.err
|
||||
}
|
||||
|
||||
// SetAttr 设置属性
|
||||
func (u *User) SetAttr(name string, value interface{}) {
|
||||
if u.attrs == nil {
|
||||
u.attrs = make(map[string]interface{})
|
||||
}
|
||||
u.attrs[name] = value
|
||||
}
|
||||
|
||||
// GetAttr 获取属性
|
||||
func (u *User) GetAttr(name string) (value interface{}, exist bool) {
|
||||
value, exist = u.attrs[name]
|
||||
return
|
||||
}
|
||||
|
||||
// GetAttrs 获取所有属性
|
||||
func (u *User) GetAttrs() map[string]interface{} {
|
||||
return u.attrs
|
||||
}
|
16
internal/enum/code.go
Normal file
16
internal/enum/code.go
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @Author: wanglin
|
||||
* @Author: wanglin@vspn.com
|
||||
* @Date: 2021/11/3 10:45
|
||||
* @Desc: TODO
|
||||
*/
|
||||
|
||||
package enum
|
||||
|
||||
const (
|
||||
SuccessActionStatus = "OK" // 成功状态
|
||||
FailActionStatus = "FAIL" // 失败状态
|
||||
SuccessCode = 0 // 成功
|
||||
InvalidParamsCode = -1 // 无效参数(自定义)
|
||||
InvalidResponseCode = -2 // 无效响应(自定义)
|
||||
)
|
95
internal/enum/enum.go
Normal file
95
internal/enum/enum.go
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Email:1711788888@qq.com
|
||||
* @Date: 2021/8/28 1:14 上午
|
||||
* @Desc: TODO
|
||||
*/
|
||||
|
||||
package enum
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/tencent-im/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// 消息类型
|
||||
MsgText = "TIMTextElem" // 消息元素
|
||||
MsgLocation = "TIMLocationElem" // 地理位置消息元素
|
||||
MsgFace = "TIMFaceElem" // 表情消息元素
|
||||
MsgCustom = "TIMCustomElem" // 自定义消息元素
|
||||
MsgSound = "TIMSoundElem" // 语音消息元素
|
||||
MsgImage = "TIMImageElem" // 图像消息元素
|
||||
MsgFile = "TIMFileElem" // 文件消息元素
|
||||
MsgVideo = "TIMVideoFileElem" // 视频消息元素
|
||||
|
||||
// 图片格式
|
||||
ImageFormatJPG = 1 // JPG格式
|
||||
ImageFormatGIF = 2 // GIF格式
|
||||
ImageFormatPNG = 3 // PNG格式
|
||||
ImageFormatBMP = 4 // BMP格式
|
||||
ImageFormatOTHER = 255 // 其他格式
|
||||
|
||||
// 图片类型
|
||||
ImageTypeOriginal = 1 // 原图
|
||||
ImageTypePic = 2 // 大图
|
||||
ImageTypeThumb = 3 // 缩略图
|
||||
|
||||
// 标准资料字段
|
||||
StandardAttrNickname = "Tag_Profile_IM_Nick" // 昵称
|
||||
StandardAttrGender = "Tag_Profile_IM_Gender" // 性别
|
||||
StandardAttrBirthday = "Tag_Profile_IM_BirthDay" // 生日
|
||||
StandardAttrLocation = "Tag_Profile_IM_Location" // 所在地
|
||||
StandardAttrSignature = "Tag_Profile_IM_SelfSignature" // 个性签名
|
||||
StandardAttrAllowType = "Tag_Profile_IM_AllowType" // 加好友验证方式
|
||||
StandardAttrLanguage = "Tag_Profile_IM_Language" // 语言
|
||||
StandardAttrAvatar = "Tag_Profile_IM_Image" // 头像URL
|
||||
StandardAttrMsgSettings = "Tag_Profile_IM_MsgSettings" // 消息设置
|
||||
StandardAttrAdminForbidType = "Tag_Profile_IM_AdminForbidType" // 管理员禁止加好友标识
|
||||
StandardAttrLevel = "Tag_Profile_IM_Level" // 等级
|
||||
StandardAttrRole = "Tag_Profile_IM_Role" // 角色
|
||||
|
||||
// 自定义属性前缀
|
||||
CustomAttrPrefix = "Tag_Profile_Custom" // 自定义属性前缀
|
||||
|
||||
// 性别类型
|
||||
GenderTypeUnknown types.GenderType = "Gender_Type_Unknown" // 没设置性别
|
||||
GenderTypeFemale types.GenderType = "Gender_Type_Female" // 女性
|
||||
GenderTypeMale types.GenderType = "Gender_Type_Male" // 男性
|
||||
|
||||
// 加好友验证方式
|
||||
AllowTypeNeedConfirm types.AllowType = "AllowType_Type_NeedConfirm" // 需要经过自己确认对方才能添加自己为好友
|
||||
AllowTypeAllowAny types.AllowType = "AllowType_Type_AllowAny" // 允许任何人添加自己为好友
|
||||
AllowTypeDenyAny types.AllowType = "AllowType_Type_DenyAny" // 不允许任何人添加自己为好友
|
||||
|
||||
// 管理员禁止加好友标识类型
|
||||
AdminForbidTypeNone types.AdminForbidType = "AdminForbid_Type_None" // 默认值,允许加好友
|
||||
AdminForbidTypeSendOut types.AdminForbidType = "AdminForbid_Type_SendOut" // 禁止该用户发起加好友请求
|
||||
|
||||
// 同步至其他设备
|
||||
SyncOtherMachineYes types.SyncOtherMachine = 1 // 把消息同步到From_Account在线终端和漫游上
|
||||
SyncOtherMachineNo types.SyncOtherMachine = 2 // 消息不同步至From_Account
|
||||
|
||||
// 推送标识
|
||||
PushFlagYes types.PushFlag = 0 // 正常推送
|
||||
PushFlagNo types.PushFlag = 1 // 不离线推送
|
||||
|
||||
// 华为推送通知消息分类
|
||||
HuaWeiImportanceLow types.HuaWeiImportance = "LOW" // LOW类消息
|
||||
HuaWeiImportanceNormal types.HuaWeiImportance = "NORMAL" // NORMAL类消息
|
||||
|
||||
// 华为推送为“打开应用内指定页面”的前提下透传参数行为
|
||||
HuaweiIntentParamAction types.HuaweiIntentParam = 0 // 将透传内容Ext作为Action参数
|
||||
HuaweiIntentParamIntent types.HuaweiIntentParam = 1 // 将透传内容Ext作为Intent参数
|
||||
|
||||
// VIVO手机推送消息分类
|
||||
VivoClassificationOperation types.VivoClassification = 0 // 运营类消息
|
||||
VivoClassificationSystem types.VivoClassification = 1 // 系统类消息
|
||||
|
||||
// IOS徽章计数模式
|
||||
BadgeModeNormal types.BadgeMode = 0 // 本条消息需要计数
|
||||
BadgeModeIgnore types.BadgeMode = 1 // 本条消息不需要计数
|
||||
|
||||
// iOS10的推送扩展开关
|
||||
MutableContentNormal types.MutableContent = 0 // 关闭iOS10的推送扩展
|
||||
MutableContentEnable types.MutableContent = 1 // 开启iOS10的推送扩展
|
||||
)
|
63
internal/random/random.go
Normal file
63
internal/random/random.go
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Author:1711788888@qq.com
|
||||
* @Date: 2021/5/12 10:35
|
||||
* @Desc: 随机数类库
|
||||
*/
|
||||
|
||||
package random
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
AlphaStr = iota // 字母字
|
||||
AlphaLowerStr // 小写字母
|
||||
AlphaUpperStr // 大写字母
|
||||
NumericStr // 数字
|
||||
NoZeroNumericStr // 无0数字
|
||||
)
|
||||
|
||||
// GenStr 生成指定长度的字符串
|
||||
func GenStr(mode, length int) string {
|
||||
var (
|
||||
pos int
|
||||
lastStr string
|
||||
seedStr string
|
||||
)
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
switch mode {
|
||||
case AlphaStr:
|
||||
seedStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
case AlphaLowerStr:
|
||||
seedStr = "abcdefghijklmnopqrstuvwxyz"
|
||||
case AlphaUpperStr:
|
||||
seedStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
case NumericStr:
|
||||
seedStr = "0123456789"
|
||||
case NoZeroNumericStr:
|
||||
seedStr = "123456789"
|
||||
}
|
||||
|
||||
seedLen := len(seedStr)
|
||||
for i := 0; i < length; i++ {
|
||||
pos = rand.Intn(seedLen)
|
||||
lastStr += seedStr[pos : pos+1]
|
||||
}
|
||||
|
||||
return lastStr
|
||||
}
|
||||
|
||||
// GenNumeric 生成指定范围的数字
|
||||
func GenNumeric(min int, max int) int {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
if min < max {
|
||||
return rand.Intn(max-min) + min
|
||||
} else {
|
||||
return rand.Intn(min-max) + max
|
||||
}
|
||||
}
|
30
internal/sign/base64.go
Normal file
30
internal/sign/base64.go
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Author:1711788888@qq.com
|
||||
* @Date: 2021/5/27 19:15
|
||||
* @Desc: BASE64
|
||||
*/
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// base64Encode base64 encode a string.
|
||||
func base64Encode(data []byte) string {
|
||||
str := base64.StdEncoding.EncodeToString(data)
|
||||
str = strings.Replace(str, "+", "*", -1)
|
||||
str = strings.Replace(str, "/", "-", -1)
|
||||
str = strings.Replace(str, "=", "_", -1)
|
||||
return str
|
||||
}
|
||||
|
||||
// base64Decode base64 decode a string.
|
||||
func base64Decode(str string) ([]byte, error) {
|
||||
str = strings.Replace(str, "_", "=", -1)
|
||||
str = strings.Replace(str, "-", "/", -1)
|
||||
str = strings.Replace(str, "*", "+", -1)
|
||||
return base64.StdEncoding.DecodeString(str)
|
||||
}
|
178
internal/sign/sign.go
Normal file
178
internal/sign/sign.go
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Author:1711788888@qq.com
|
||||
* @Date: 2021/5/27 19:11
|
||||
* @Desc: Transmission signature.
|
||||
*/
|
||||
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenUserSig gen a user sign.
|
||||
func GenUserSig(sdkAppId int, key string, userid string, expire int) (string, error) {
|
||||
return genUserSig(sdkAppId, key, userid, expire, nil)
|
||||
}
|
||||
|
||||
// GenPrivateMapKey gen a private map.
|
||||
func GenPrivateMapKey(sdkAppId int, key string, userid string, expire int, roomId uint32, privilegeMap uint32) (string, error) {
|
||||
var userBuf []byte = genUserBuf(userid, sdkAppId, roomId, expire, privilegeMap, 0, "")
|
||||
return genUserSig(sdkAppId, key, userid, expire, userBuf)
|
||||
}
|
||||
|
||||
// GenPrivateMapKeyWithRoomId gen a private map with room id.
|
||||
func GenPrivateMapKeyWithRoomId(sdkAppId int, key string, userid string, expire int, roomId string, privilegeMap uint32) (string, error) {
|
||||
var userBuf []byte = genUserBuf(userid, sdkAppId, 0, expire, privilegeMap, 0, roomId)
|
||||
return genUserSig(sdkAppId, key, userid, expire, userBuf)
|
||||
}
|
||||
|
||||
// genUserBuf gen a user buffer.
|
||||
func genUserBuf(account string, dwSdkappid int, dwAuthID uint32,
|
||||
dwExpTime int, dwPrivilegeMap uint32, dwAccountType uint32, roomStr string) []byte {
|
||||
|
||||
offset := 0
|
||||
length := 1 + 2 + len(account) + 20 + len(roomStr)
|
||||
if len(roomStr) > 0 {
|
||||
length = length + 2
|
||||
}
|
||||
|
||||
userBuf := make([]byte, length)
|
||||
|
||||
// ver
|
||||
if len(roomStr) > 0 {
|
||||
userBuf[offset] = 1
|
||||
} else {
|
||||
userBuf[offset] = 0
|
||||
}
|
||||
|
||||
offset++
|
||||
userBuf[offset] = (byte)((len(account) & 0xFF00) >> 8)
|
||||
offset++
|
||||
userBuf[offset] = (byte)(len(account) & 0x00FF)
|
||||
offset++
|
||||
|
||||
for ; offset < len(account)+3; offset++ {
|
||||
userBuf[offset] = account[offset-3]
|
||||
}
|
||||
|
||||
// dwSdkAppid
|
||||
userBuf[offset] = (byte)((int64(dwSdkappid) & 0xFF000000) >> 24)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((dwSdkappid & 0x00FF0000) >> 16)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((dwSdkappid & 0x0000FF00) >> 8)
|
||||
offset++
|
||||
userBuf[offset] = (byte)(dwSdkappid & 0x000000FF)
|
||||
offset++
|
||||
|
||||
// dwAuthId
|
||||
userBuf[offset] = (byte)((dwAuthID & 0xFF000000) >> 24)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((dwAuthID & 0x00FF0000) >> 16)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((dwAuthID & 0x0000FF00) >> 8)
|
||||
offset++
|
||||
userBuf[offset] = (byte)(dwAuthID & 0x000000FF)
|
||||
offset++
|
||||
|
||||
// dwExpTime now+300;
|
||||
currTime := time.Now().Unix()
|
||||
var expire = currTime + int64(dwExpTime)
|
||||
userBuf[offset] = (byte)((expire & 0xFF000000) >> 24)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((expire & 0x00FF0000) >> 16)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((expire & 0x0000FF00) >> 8)
|
||||
offset++
|
||||
userBuf[offset] = (byte)(expire & 0x000000FF)
|
||||
offset++
|
||||
|
||||
// dwPrivilegeMap
|
||||
userBuf[offset] = (byte)((dwPrivilegeMap & 0xFF000000) >> 24)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((dwPrivilegeMap & 0x00FF0000) >> 16)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((dwPrivilegeMap & 0x0000FF00) >> 8)
|
||||
offset++
|
||||
userBuf[offset] = (byte)(dwPrivilegeMap & 0x000000FF)
|
||||
offset++
|
||||
|
||||
// dwAccountType
|
||||
userBuf[offset] = (byte)((dwAccountType & 0xFF000000) >> 24)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((dwAccountType & 0x00FF0000) >> 16)
|
||||
offset++
|
||||
userBuf[offset] = (byte)((dwAccountType & 0x0000FF00) >> 8)
|
||||
offset++
|
||||
userBuf[offset] = (byte)(dwAccountType & 0x000000FF)
|
||||
offset++
|
||||
|
||||
if len(roomStr) > 0 {
|
||||
userBuf[offset] = (byte)((len(roomStr) & 0xFF00) >> 8)
|
||||
offset++
|
||||
userBuf[offset] = (byte)(len(roomStr) & 0x00FF)
|
||||
offset++
|
||||
|
||||
for ; offset < length; offset++ {
|
||||
userBuf[offset] = roomStr[offset-(length-len(roomStr))]
|
||||
}
|
||||
}
|
||||
|
||||
return userBuf
|
||||
}
|
||||
|
||||
// hmacSha256 encrypt with HMAC SHA256.
|
||||
func hmacSha256(sdkAppId int, key string, identifier string, currTime int64, expire int, base64UserBuf *string) string {
|
||||
var contentToBeSigned string
|
||||
contentToBeSigned = "TLS.identifier:" + identifier + "\n"
|
||||
contentToBeSigned += "TLS.sdkappid:" + strconv.Itoa(sdkAppId) + "\n"
|
||||
contentToBeSigned += "TLS.time:" + strconv.FormatInt(currTime, 10) + "\n"
|
||||
contentToBeSigned += "TLS.expire:" + strconv.Itoa(expire) + "\n"
|
||||
if nil != base64UserBuf {
|
||||
contentToBeSigned += "TLS.userbuf:" + *base64UserBuf + "\n"
|
||||
}
|
||||
|
||||
h := hmac.New(sha256.New, []byte(key))
|
||||
h.Write([]byte(contentToBeSigned))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// genUserSig gen a sign
|
||||
func genUserSig(sdkAppId int, key string, identifier string, expire int, userBuf []byte) (string, error) {
|
||||
currTime := time.Now().Unix()
|
||||
var sigDoc map[string]interface{}
|
||||
sigDoc = make(map[string]interface{})
|
||||
sigDoc["TLS.ver"] = "2.0"
|
||||
sigDoc["TLS.identifier"] = identifier
|
||||
sigDoc["TLS.sdkappid"] = sdkAppId
|
||||
sigDoc["TLS.expire"] = expire
|
||||
sigDoc["TLS.time"] = currTime
|
||||
var base64UserBuf string
|
||||
if nil != userBuf {
|
||||
base64UserBuf = base64.StdEncoding.EncodeToString(userBuf)
|
||||
sigDoc["TLS.userbuf"] = base64UserBuf
|
||||
sigDoc["TLS.sig"] = hmacSha256(sdkAppId, key, identifier, currTime, expire, &base64UserBuf)
|
||||
} else {
|
||||
sigDoc["TLS.sig"] = hmacSha256(sdkAppId, key, identifier, currTime, expire, nil)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(sigDoc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
w := zlib.NewWriter(&b)
|
||||
_, _ = w.Write(data)
|
||||
_ = w.Close()
|
||||
return base64Encode(b.Bytes()), nil
|
||||
}
|
30
internal/types/interface.go
Normal file
30
internal/types/interface.go
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Email:1711788888@qq.com
|
||||
* @Date: 2021/8/27 12:54 下午
|
||||
* @Desc: TODO
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
type BaseRespInterface interface {
|
||||
GetErrorCode() int
|
||||
GetErrorInfo() string
|
||||
}
|
||||
|
||||
func (r *BaseResp) GetErrorCode() int {
|
||||
return r.ErrorCode
|
||||
}
|
||||
|
||||
func (r *BaseResp) GetErrorInfo() string {
|
||||
return r.ErrorInfo
|
||||
}
|
||||
|
||||
type ActionBaseRespInterface interface {
|
||||
BaseRespInterface
|
||||
GetActionStatus() string
|
||||
}
|
||||
|
||||
func (r *ActionBaseResp) GetActionStatus() string {
|
||||
return r.ActionStatus
|
||||
}
|
171
internal/types/types.go
Normal file
171
internal/types/types.go
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* @Author: Echo
|
||||
* @Author:1711788888@qq.com
|
||||
* @Date: 2021/5/28 19:24
|
||||
* @Desc: TODO
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
type (
|
||||
BaseResp struct {
|
||||
ErrorCode int `json:"ErrorCode"`
|
||||
ErrorInfo string `json:"ErrorInfo"`
|
||||
ErrorDisplay string `json:"ErrorDisplay,omitempty"`
|
||||
}
|
||||
|
||||
ActionBaseResp struct {
|
||||
BaseResp
|
||||
ActionStatus string `json:"ActionStatus"`
|
||||
}
|
||||
|
||||
// AndroidInfo Android离线推送消息
|
||||
AndroidInfo struct {
|
||||
Sound string `json:"Sound,omitempty"` // (选填)Android 离线推送声音文件路径。
|
||||
HuaWeiChannelID string `json:"HuaWeiChannelID,omitempty"` // (选填)华为手机 EMUI 10.0 及以上的通知渠道字段。该字段不为空时,会覆盖控制台配置的 ChannelID 值;该字段为空时,不会覆盖控制台配置的 ChannelID 值。
|
||||
XiaoMiChannelID string `json:"XiaoMiChannelID,omitempty"` // (选填)小米手机 MIUI 10 及以上的通知类别(Channel)适配字段。该字段不为空时,会覆盖控制台配置的 ChannelID 值;该字段为空时,不会覆盖控制台配置的 ChannelID 值。
|
||||
OPPOChannelID string `json:"OPPOChannelID,omitempty"` // (选填)OPPO 手机 Android 8.0 及以上的 NotificationChannel 通知适配字段。该字段不为空时,会覆盖控制台配置的 ChannelID 值;该字段为空时,不会覆盖控制台配置的 ChannelID 值。
|
||||
GoogleChannelID string `json:"GoogleChannelID,omitempty"` // (选填)Google 手机 Android 8.0 及以上的通知渠道字段。Google 推送新接口(上传证书文件)支持 channel id,旧接口(填写服务器密钥)不支持。
|
||||
VIVOClassification int `json:"VIVOClassification,omitempty"` // (选填)VIVO 手机推送消息分类,“0”代表运营消息,“1”代表系统消息,不填默认为1。
|
||||
HuaWeiImportance string `json:"HuaWeiImportance,omitempty"` // (选填)华为推送通知消息分类,取值为 LOW、NORMAL,不填默认为 NORMAL。
|
||||
ExtAsHuaweiIntentParam int `json:"ExtAsHuaweiIntentParam,omitempty"` // (选填)在控制台配置华为推送为“打开应用内指定页面”的前提下,传“1”表示将透传内容 Ext 作为 Intent 的参数,“0”表示将透传内容 Ext 作为 Action 参数。不填默认为0。两种传参区别可参见 华为推送文档。
|
||||
}
|
||||
|
||||
// ApnsInfo IOS离线推送消息
|
||||
ApnsInfo struct {
|
||||
BadgeMode int `json:"BadgeMode,omitempty"` // (选填)这个字段缺省或者为0表示需要计数,为1表示本条消息不需要计数,即右上角图标数字不增加。
|
||||
Title string `json:"Title,omitempty"` // (选填)该字段用于标识 APNs 推送的标题,若填写则会覆盖最上层 Title。
|
||||
SubTitle string `json:"SubTitle,omitempty"` // (选填)该字段用于标识 APNs 推送的子标题。
|
||||
Image string `json:"Image,omitempty"` // (选填)该字段用于标识 APNs 携带的图片地址,当客户端拿到该字段时,可以通过下载图片资源的方式将图片展示在弹窗上。
|
||||
MutableContent int `json:"MutableContent,omitempty"` // (选填)为1表示开启 iOS 10 的推送扩展,默认为0。
|
||||
}
|
||||
|
||||
// OfflinePushInfo 离线推送消息
|
||||
OfflinePushInfo struct {
|
||||
PushFlag int `json:"PushFlag,omitempty"` // (选填)推送标识。0表示推送,1表示不离线推送。
|
||||
Title string `json:"Title,omitempty"` // (选填)离线推送标题。该字段为 iOS 和 Android 共用。
|
||||
Desc string `json:"Desc,omitempty"` // (选填)离线推送内容。该字段会覆盖上面各种消息元素 TIMMsgElement 的离线推送展示文本。若发送的消息只有一个 TIMCustomElem 自定义消息元素,该 Desc 字段会覆盖 TIMCustomElem 中的 Desc 字段。如果两个 Desc 字段都不填,将收不到该自定义消息的离线推送。
|
||||
Ext string `json:"Ext,omitempty"` // (选填)离线推送透传内容。由于国内各 Android 手机厂商的推送平台要求各不一样,请保证此字段为 JSON 格式,否则可能会导致收不到某些厂商的离线推送。
|
||||
AndroidInfo *AndroidInfo `json:"AndroidInfo,omitempty"` // (选填)Android 离线推送消息
|
||||
ApnsInfo *ApnsInfo `json:"ApnsInfo,omitempty"` // (选填)IOS离线推送消息
|
||||
}
|
||||
|
||||
// MsgBody 消息内容
|
||||
MsgBody struct {
|
||||
MsgType string `json:"MsgType"`
|
||||
MsgContent interface{} `json:"MsgContent"`
|
||||
}
|
||||
|
||||
// TagPair 标签对
|
||||
TagPair struct {
|
||||
Tag string `json:"Tag"` // 标签
|
||||
Value interface{} `json:"Value"` // 标签值
|
||||
}
|
||||
|
||||
// MsgTextContent 文本消息内容
|
||||
MsgTextContent struct {
|
||||
Text string `json:"Text"` // (必填)消息内容。当接收方为 iOS 或 Android 后台在线时,作为离线推送的文本展示。
|
||||
}
|
||||
|
||||
// MsgLocationContent 地理位置消息元素
|
||||
MsgLocationContent struct {
|
||||
Desc string `json:"Desc"` // (必填)地理位置描述信息
|
||||
Latitude float64 `json:"Latitude"` // (必填)纬度
|
||||
Longitude float64 `json:"Longitude"` // (必填)经度
|
||||
}
|
||||
|
||||
// MsgFaceContent 表情消息元素
|
||||
MsgFaceContent struct {
|
||||
Index int `json:"Index"` // (必填)表情索引,用户自定义
|
||||
Data string `json:"Data"` // (选填)额外数据
|
||||
}
|
||||
|
||||
// MsgCustomContent 自定义消息元素
|
||||
MsgCustomContent struct {
|
||||
Desc string `json:"Desc"` // (选填)自定义消息描述信息。当接收方为 iOS 或 Android 后台在线时,做离线推送文本展示。 若发送自定义消息的同时设置了 OfflinePushInfo.Desc 字段,此字段会被覆盖,请优先填 OfflinePushInfo.Desc 字段。
|
||||
Data string `json:"Data"` // (必填)自定义消息数据。 不作为 APNs 的 payload 字段下发,故从 payload 中无法获取 Data 字段
|
||||
Ext string `json:"Ext"` // (选填)扩展字段。当接收方为 iOS 系统且应用处在后台时,此字段作为 APNs 请求包 Payloads 中的 Ext 键值下发,Ext 的协议格式由业务方确定,APNs 只做透传。
|
||||
Sound string `json:"Sound"` // (选填)自定义 APNs 推送铃音。
|
||||
}
|
||||
|
||||
// MsgSoundContent 语音消息元素
|
||||
MsgSoundContent struct {
|
||||
UUID string `json:"UUID"` // (必填)语音的唯一标识,类型为 String。客户端用于索引语音的键值。无法通过该字段下载相应的语音。若需要获取该语音,请升级 IM SDK 版本至4.X。
|
||||
Url string `json:"Url"` // (必填)语音下载地址,可通过该 URL 地址直接下载相应语音
|
||||
Size int `json:"Size"` // (必填)语音数据大小,单位:字节。
|
||||
Second int `json:"Second"` // (必填)语音时长,单位:秒。
|
||||
DownloadFlag int `json:"Download_Flag"` // (必填)语音下载方式标记。目前 Download_Flag 取值只能为2,表示可通过Url字段值的 URL 地址直接下载语音。
|
||||
}
|
||||
|
||||
// MsgImageContent 图像消息元素
|
||||
MsgImageContent struct {
|
||||
UUID string `json:"UUID"` // (必填)图片序列号。后台用于索引图片的键值。
|
||||
ImageFormat int `json:"ImageFormat"` // (必填)图片格式。JPG = 1,GIF = 2,PNG = 3,BMP = 4,其他 = 255。
|
||||
ImageInfos []*ImageInfo `json:"ImageInfoArray"` // (必填)原图、缩略图或者大图下载信息。
|
||||
}
|
||||
|
||||
// MsgFileContent 文件消息元素
|
||||
MsgFileContent struct {
|
||||
Url string `json:"Url"` // (必填)文件下载地址,可通过该 URL 地址直接下载相应文件
|
||||
UUID string `json:"UUID"` // (必填)文件的唯一标识,客户端用于索引文件的键值。
|
||||
FileSize int `json:"FileSize"` // (必填)文件数据大小,单位:字节
|
||||
FileName string `json:"FileName"` // (必填)文件名称
|
||||
DownloadFlag int `json:"Download_Flag"` // (必填)文件下载方式标记。目前 Download_Flag 取值只能为2,表示可通过Url字段值的 URL 地址直接下载文件。
|
||||
}
|
||||
|
||||
// MsgVideoContent 视频消息元素
|
||||
MsgVideoContent struct {
|
||||
VideoUUID string `json:"VideoUUID"` // (必填)视频的唯一标识,客户端用于索引视频的键值。
|
||||
VideoUrl string `json:"VideoUrl"` // (必填)视频下载地址。可通过该 URL 地址直接下载相应视频
|
||||
VideoSize int `json:"VideoSize"` // (必填)视频数据大小,单位:字节
|
||||
VideoSecond int `json:"VideoSecond"` // (必填)视频时长,单位:秒
|
||||
VideoFormat string `json:"VideoFormat"` // (必填)视频格式,例如 mp4
|
||||
VideoDownloadFlag int `json:"VideoDownloadFlag"` // (必填)视频下载方式标记。目前 VideoDownloadFlag 取值只能为2,表示可通过VideoUrl字段值的 URL 地址直接下载视频。
|
||||
ThumbUrl string `json:"ThumbUrl"` // (必填)视频缩略图下载地址。可通过该 URL 地址直接下载相应视频缩略图。
|
||||
ThumbUUID string `json:"ThumbUUID"` // (必填)视频缩略图的唯一标识,客户端用于索引视频缩略图的键值。
|
||||
ThumbSize int `json:"ThumbSize"` // (必填)缩略图大小,单位:字节
|
||||
ThumbWidth int `json:"ThumbWidth"` // (必填)缩略图宽度
|
||||
ThumbHeight int `json:"ThumbHeight"` // (必填)缩略图高度
|
||||
ThumbFormat string `json:"ThumbFormat"` // (必填)缩略图格式,例如 JPG、BMP 等
|
||||
ThumbDownloadFlag int `json:"ThumbDownloadFlag"` // (必填)视频缩略图下载方式标记。目前 ThumbDownloadFlag 取值只能为2,表示可通过ThumbUrl字段值的 URL 地址直接下载视频缩略图。
|
||||
}
|
||||
|
||||
// ImageInfo 图片下载信息
|
||||
ImageInfo struct {
|
||||
Type int `json:"Type"` // (必填)图片类型: 1-原图,2-大图,3-缩略图。
|
||||
Size int `json:"Size"` // (必填)图片数据大小,单位:字节。
|
||||
Width int `json:"Width"` // (必填)图片宽度。
|
||||
Height int `json:"Height"` // (必填)图片高度。
|
||||
Url string `json:"URL"` // (必填)图片下载地址。
|
||||
}
|
||||
|
||||
// GenderType 性别类型
|
||||
GenderType string
|
||||
|
||||
// AllowType 加好友验证方式
|
||||
AllowType string
|
||||
|
||||
// AdminForbidType 管理员禁止加好友标识类型
|
||||
AdminForbidType string
|
||||
|
||||
// SyncOtherMachine 同步至其他设备
|
||||
SyncOtherMachine int
|
||||
|
||||
// PushFlag 推送标识
|
||||
PushFlag int
|
||||
|
||||
// HuaWeiImportance 华为推送通知消息分类
|
||||
HuaWeiImportance string
|
||||
|
||||
// HuaweiIntentParam 华为推送为“打开应用内指定页面”的前提下透传参数行为
|
||||
HuaweiIntentParam int
|
||||
|
||||
// VivoClassification VIVO手机推送消息分类
|
||||
VivoClassification int
|
||||
|
||||
// BadgeMode IOS徽章计数模式
|
||||
BadgeMode int
|
||||
|
||||
// MutableContent IOS10的推送扩展开关
|
||||
MutableContent int
|
||||
)
|
Reference in New Issue
Block a user