作者 yangfu

add im

version: v1
kind: Attribute
metadata:
name: companyCustomerId
description: 公司客户id
type:
primitive: int64
version: v1
kind: Attribute
metadata:
name: customerCode
description: 公司客户Code
type:
primitive: string
version: v1
kind: Attribute
metadata:
name: customerName
description: 客户名称
type:
primitive: string
version: v1
kind: Attribute
metadata:
name: companyProductId
description: 产品Id
type:
primitive: int64
version: v1
kind: Attribute
metadata:
name: productCode
description: 产品Code
type:
primitive: string
version: v1
kind: Attribute
metadata:
name: productName
description: 产品名称
type:
primitive: string
version: v1
kind: Attribute
metadata:
name: companyRegionId
description: 区域ID
type:
primitive: int64
version: v1
kind: Attribute
metadata:
name: parentRegionId
description: 上继区域
type:
primitive: int64
version: v1
kind: Attribute
metadata:
name: regionCode
description: 区域Code
type:
primitive: string
version: v1
kind: Attribute
metadata:
name: regionName
description: 区域名称
type:
primitive: string
version: v1
kind: Schema
metadata:
name: im
description: 冗余附加数据
attributes:
- name: accid
description: 网易云信ID
type:
primitive: string
- name: imToken
description: 网易云信Token
type:
primitive: string
- name: csAccountId
description: 系统分配客服ID
type:
primitive: string
... ...
... ... @@ -14,6 +14,10 @@ metadata:
description: 手机认证
type:
schemal: phoneAuth
- name: im
description: IM信息
type:
schemal: im
- ref: createAt
required: true
- ref: updateAt
... ...
... ... @@ -31,9 +31,9 @@ metadata:
type:
array: int64
- name: collectedMenus
description: 收藏的菜单(工作台)
description: 收藏的菜单(工作台)(菜单编码列表)
type:
array: menu
array: string
- name: cooperationInfo
description: 共创信息 (共创用户有效)
type:
... ...
package domain
// 冗余附加数据
type Im struct {
// 网易云信ID
Accid string `json:"accid"`
// 网易云信Token
ImToken string `json:"imToken"`
// 系统分配客服ID
CsAccountId string `json:"csAccountId"`
}
... ...
package domainService
import (
"fmt"
pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
"gitlab.fjmaimaimai.com/mmm-go-pp/terms/pkg/domain"
"gitlab.fjmaimaimai.com/mmm-go-pp/terms/pkg/infrastructure/im"
)
type PgImService struct {
transactionContext *pgTransaction.TransactionContext
}
func (s *PgImService) InitOrUpdateUserIMInfo(userId int64, name string) (imInfo *domain.Im, err error) {
var (
//ImInfoRepository, _ = factory.CreateImInfoRepository(ctx)
checkImRequest *im.CheckImRequest = &im.CheckImRequest{}
IsCreated = false
checkImResponse *im.CheckImResponse
)
var errFind error
//imInfo, errFind = ImInfoRepository.FindOne(map[string]interface{}{"user_id": userId})
// 异常
//if errFind != nil && errFind != domain.QueryNoRow {
// err = errFind
// return
//}
// 不存在
//if errFind == domain.QueryNoRow {
// imInfo = &domain.Im{
// UserId: userId,
// CreateTime: time.Now(),
// }
//}
// 已存在
if errFind == nil && imInfo != nil {
IsCreated = true
}
if len(imInfo.Accid) == 0 {
//id, _ := utils.NewSnowflakeId()
//imInfo.ImId = fmt.Sprintf("%v", id)
}
checkImRequest = &im.CheckImRequest{
UserId: userId,
ImId: imInfo.Accid,
Uname: name,
CustomerImId: fmt.Sprintf("%v", imInfo.CsAccountId),
IsCreated: IsCreated,
}
if checkImResponse, err = CheckIm(checkImRequest); err != nil {
return
}
if len(imInfo.CsAccountId) == 0 {
imInfo.CsAccountId = getRandomCustomerAccount(userId)
}
imInfo.ImToken = checkImResponse.ImToken
//if _, err = ImInfoRepository.Save(imInfo); err != nil {
// return
//}
return
}
// 检查ImToken
func CheckIm(request *im.CheckImRequest) (rsp *im.CheckImResponse, err error) {
var ()
rsp = &im.CheckImResponse{}
if !request.IsCreated {
if err = imCreate(request, rsp); err != nil {
return
}
} else {
if err = imUpdate(request, rsp); err != nil {
return
}
}
if err = imRefreshToken(request, rsp); err != nil {
return
}
return
}
//create
func imCreate(request *im.CheckImRequest, rsp *im.CheckImResponse) (err error) {
var (
param im.UserCreate = im.UserCreate{
Accid: request.ImId,
Name: request.Uname,
Icon: request.Icon,
}
out *im.UserTokenResult
)
if out, err = im.CallCreate(param); err != nil {
return
}
if out.Code != 200 || (out.Info.Accid != request.ImId) {
return im.ErrorFailCall
}
rsp.ImToken = out.Info.Token
return
}
//update user info
func imUpdate(request *im.CheckImRequest, rsp *im.CheckImResponse) (err error) {
var (
param im.UserUpdate = im.UserUpdate{
Accid: request.ImId,
Name: request.Uname,
Icon: request.Icon,
}
out *im.BaseResp
)
if out, err = im.CallUpdate(param); err != nil {
return
}
if out.Code != 200 {
return im.ErrorFailCall
}
return
}
//refresh token
func imRefreshToken(request *im.CheckImRequest, rsp *im.CheckImResponse) (err error) {
var (
param im.UserRefreshToken = im.UserRefreshToken{
Accid: request.ImId,
}
out *im.UserTokenResult
)
if out, err = im.CallRefreshToken(param); err != nil {
return
}
if out.Code != 200 || (out.Info.Accid != request.ImId) {
return im.ErrorFailCall
}
rsp.ImToken = out.Info.Token
return
}
// 获取客服id
func getRandomCustomerAccount(userId int64) (acid string) {
//ImCustomerServiceRepository, _ := factory.CreateImCustomerServiceRepository(ctx)
//total, customers, err := ImCustomerServiceRepository.Find(map[string]interface{}{"sortById": domain.ASC})
//if err != nil {
// log.Error(err)
// return 0
//}
//if total == 0 {
// return 0
//}
//index := userId % total
//if int(index) < len(customers) {
// acid, _ = strconv.ParseInt(customers[index].ImId, 10, 64)
// return
//}
//acid, _ = strconv.ParseInt(customers[0].ImId, 10, 64)
return
}
... ...
package im
import (
"encoding/json"
)
//func init() {
// InitImClient(constant.IM_SERVICE_ADDRESS, constant.IM_APP_KEY, constant.IM_APP_SECRET)
//}
type RequestParam interface {
Format() map[string]string
GetPath() string
Valid() error
}
//接口
func CallCreate(v UserCreate) (*UserTokenResult, error) {
var result UserTokenResult
btData, err := DefaultImClient.Call(v)
if err != nil {
return nil, err
}
err = json.Unmarshal(btData, &result)
if err != nil {
return nil, err
}
return &result, nil
}
func CallRefreshToken(v UserRefreshToken) (*UserTokenResult, error) {
var result UserTokenResult
btData, err := DefaultImClient.Call(v)
if err != nil {
return nil, err
}
err = json.Unmarshal(btData, &result)
if err != nil {
return nil, err
}
return &result, nil
}
func CallUpdate(v UserUpdate) (*BaseResp, error) {
var result BaseResp
btData, err := DefaultImClient.Call(v)
if err != nil {
return nil, err
}
err = json.Unmarshal(btData, &result)
if err != nil {
return nil, err
}
return &result, nil
}
/*CheckIm */
type CheckImRequest struct {
UserId int64
ImId string
Uname string
Icon string
CustomerImId string
IsCreated bool
}
type CheckImResponse struct {
ImToken string //net im token
CsAccount int64 //客服id
}
... ...
package im
import (
"testing"
)
const (
IM_SERVICE_ADDRESS = "https://api.netease.im/nimserver"
IM_APP_KEY = "be7c0639c10e6a69f86ce3b4fa8dc8ec" //"ebf3ae278ee1b346773b99be5080f6a9"
IM_APP_SECRET = "9c5b60346613" //"67ea92e1ea45"
)
func TestCallCreate(t *testing.T) {
InitImClient(IM_SERVICE_ADDRESS, IM_APP_KEY, IM_APP_SECRET)
token, err := CallCreate(UserCreate{Accid: "1"})
if err != nil {
t.Fatal(err)
}
if token == nil {
t.Fatal("token is nil")
}
t.Log(token.Code, token.Info)
}
func TestCallRefreshToken(t *testing.T) {
InitImClient(IM_SERVICE_ADDRESS, IM_APP_KEY, IM_APP_SECRET)
token, err := CallRefreshToken(UserRefreshToken{Accid: "1"})
if err != nil {
t.Fatal(err)
}
if token == nil {
t.Fatal("token is nil")
}
t.Log(token.Code, token.Info)
}
func TestCallUpdate(t *testing.T) {
InitImClient(IM_SERVICE_ADDRESS, IM_APP_KEY, IM_APP_SECRET)
token, err := CallUpdate(UserUpdate{Accid: "1", Name: "tip tok"})
if err != nil {
t.Fatal(err)
}
if token == nil {
t.Fatal("token is nil")
}
t.Log(token.Code)
}
... ...
package im
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
var DefaultImClient Client
var ErrorFailCall = fmt.Errorf(" imclient call failed")
func InitImClient(baseUrl, appKey, appSecret string) {
DefaultImClient = Client{
baseUrl: baseUrl,
appKey: appKey,
appSecret: appSecret,
}
}
type Client struct {
baseUrl string
appKey string
appSecret string
}
func (i Client) Call(param RequestParam) ([]byte, error) {
return i.httpDo(param.GetPath(), param.Format())
}
func (i Client) buildHeader() http.Header {
var h = http.Header{}
curTime := strconv.FormatInt(time.Now().Unix(), 10)
nonce := strconv.FormatInt(time.Now().Unix()+rand.Int63n(5000), 10)
checkSum := buildCheckSum(i.appSecret, nonce, curTime)
h.Set("Content-Type", "application/x-www-form-urlencoded")
h.Set("AppKey", i.appKey)
h.Set("Nonce", nonce)
h.Set("CurTime", curTime)
h.Set("CheckSum", checkSum)
return h
}
func (i Client) httpDo(path string, posts map[string]string) ([]byte, error) {
client := http.Client{
Timeout: 5 * time.Second, //请求超时时间5秒
}
reqURL := i.baseUrl + path
params := url.Values{}
for k, v := range posts {
params.Add(k, v)
}
req, err := http.NewRequest("POST", reqURL, strings.NewReader(params.Encode()))
if err != nil {
return nil, err
}
req.Header = i.buildHeader()
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
func buildCheckSum(appSecret string, nonce string, curTime string) string {
str := []byte(appSecret + nonce + curTime)
sh := sha1.New()
sh.Write(str)
result := hex.EncodeToString(sh.Sum(nil))
return strings.ToLower(result)
}
... ...
package im
import (
"fmt"
)
var (
_ RequestParam = UserCreate{}
_ RequestParam = UserUpdate{}
_ RequestParam = UserRefreshToken{}
)
type BaseResp struct {
Code int `json:"code"`
}
// TokenInfo 云通信Token
type TokenInfo struct {
Token string `json:"token"`
Accid string `json:"accid"`
Name string `json:"name"`
}
type UserTokenResult struct {
BaseResp
Info TokenInfo `json:"info"`
}
// 创建网易云通信ID
type UserCreate struct {
Accid string //网易云通信ID,最大长度32字符
Name string //ID昵称,最大长度64字符。
Props string //json属性,开发者可选填,最大长度1024字符
Icon string //ID头像URL,开发者可选填,最大长度1024字符
/**
云通信ID可以指定登录token值,最大长度128字符,
并更新,如果未指定,会自动生成token,并在
创建成功后返回
**/
Token string
Sign string //签名
Email string
Birth string
Mobile string
Gender int //0未知,1男,2女
Ex string //扩展字段
}
func (p UserCreate) Format() map[string]string {
return map[string]string{
"accid": p.Accid,
"name": p.Name,
"props": p.Props,
"icon": p.Icon,
"token": p.Token,
"sign": p.Sign,
"email": p.Email,
"birth": p.Birth,
"mobile": p.Mobile,
"gender": fmt.Sprintf("%d", p.Gender),
"ex": p.Ex,
}
}
func (p UserCreate) GetPath() string {
return "/user/create.action"
}
func (p UserCreate) Valid() error {
return nil
}
// 重置网易云通信token
type UserRefreshToken struct {
Accid string //网易云通信ID,最大长度32字符,必须保证一个 APP内唯一
}
func (p UserRefreshToken) Format() map[string]string {
return map[string]string{
"accid": p.Accid,
}
}
func (p UserRefreshToken) GetPath() string {
return "/user/refreshToken.action"
}
func (p UserRefreshToken) Valid() error {
return nil
}
// 更新网易云通信token
type UserUpdate struct {
Accid string
Name string //这边网易云要有昵称以手机号码为昵称
Icon string //icon默认头像
Sign string //签名
Email string
Birth string
Mobile string
Gender int //0未知,1男,2女
Ex string //扩展字段
}
func (u UserUpdate) Format() map[string]string {
return map[string]string{
"accid": u.Accid,
"name": u.Name,
"icon": u.Icon,
"sign": u.Sign,
"email": u.Email,
"birth": u.Birth,
"mobile": u.Mobile,
"gender": fmt.Sprintf("%d", u.Gender),
"ex": u.Ex,
}
}
func (u UserUpdate) GetPath() string {
return "/user/refreshToken.action"
}
func (u UserUpdate) Valid() error {
return nil
}
... ...