作者 yangfu

add im

1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: companyCustomerId  
5 - description: 公司客户id  
6 - type:  
7 - primitive: int64  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: customerCode  
5 - description: 公司客户Code  
6 - type:  
7 - primitive: string  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: customerName  
5 - description: 客户名称  
6 - type:  
7 - primitive: string  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: companyProductId  
5 - description: 产品Id  
6 - type:  
7 - primitive: int64  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: productCode  
5 - description: 产品Code  
6 - type:  
7 - primitive: string  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: productName  
5 - description: 产品名称  
6 - type:  
7 - primitive: string  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: companyRegionId  
5 - description: 区域ID  
6 - type:  
7 - primitive: int64  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: parentRegionId  
5 - description: 上继区域  
6 - type:  
7 - primitive: int64  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: regionCode  
5 - description: 区域Code  
6 - type:  
7 - primitive: string  
1 -version: v1  
2 -kind: Attribute  
3 -metadata:  
4 - name: regionName  
5 - description: 区域名称  
6 - type:  
7 - primitive: string  
  1 +version: v1
  2 +kind: Schema
  3 +metadata:
  4 + name: im
  5 + description: 冗余附加数据
  6 + attributes:
  7 + - name: accid
  8 + description: 网易云信ID
  9 + type:
  10 + primitive: string
  11 + - name: imToken
  12 + description: 网易云信Token
  13 + type:
  14 + primitive: string
  15 + - name: csAccountId
  16 + description: 系统分配客服ID
  17 + type:
  18 + primitive: string
@@ -14,6 +14,10 @@ metadata: @@ -14,6 +14,10 @@ metadata:
14 description: 手机认证 14 description: 手机认证
15 type: 15 type:
16 schemal: phoneAuth 16 schemal: phoneAuth
  17 + - name: im
  18 + description: IM信息
  19 + type:
  20 + schemal: im
17 - ref: createAt 21 - ref: createAt
18 required: true 22 required: true
19 - ref: updateAt 23 - ref: updateAt
@@ -31,9 +31,9 @@ metadata: @@ -31,9 +31,9 @@ metadata:
31 type: 31 type:
32 array: int64 32 array: int64
33 - name: collectedMenus 33 - name: collectedMenus
34 - description: 收藏的菜单(工作台) 34 + description: 收藏的菜单(工作台)(菜单编码列表)
35 type: 35 type:
36 - array: menu 36 + array: string
37 - name: cooperationInfo 37 - name: cooperationInfo
38 description: 共创信息 (共创用户有效) 38 description: 共创信息 (共创用户有效)
39 type: 39 type:
  1 +package domain
  2 +
  3 +// 冗余附加数据
  4 +type Im struct {
  5 + // 网易云信ID
  6 + Accid string `json:"accid"`
  7 + // 网易云信Token
  8 + ImToken string `json:"imToken"`
  9 + // 系统分配客服ID
  10 + CsAccountId string `json:"csAccountId"`
  11 +}
  1 +package domainService
  2 +
  3 +import (
  4 + "fmt"
  5 + pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
  6 + "gitlab.fjmaimaimai.com/mmm-go-pp/terms/pkg/domain"
  7 + "gitlab.fjmaimaimai.com/mmm-go-pp/terms/pkg/infrastructure/im"
  8 +)
  9 +
  10 +type PgImService struct {
  11 + transactionContext *pgTransaction.TransactionContext
  12 +}
  13 +
  14 +func (s *PgImService) InitOrUpdateUserIMInfo(userId int64, name string) (imInfo *domain.Im, err error) {
  15 + var (
  16 + //ImInfoRepository, _ = factory.CreateImInfoRepository(ctx)
  17 + checkImRequest *im.CheckImRequest = &im.CheckImRequest{}
  18 + IsCreated = false
  19 + checkImResponse *im.CheckImResponse
  20 + )
  21 + var errFind error
  22 + //imInfo, errFind = ImInfoRepository.FindOne(map[string]interface{}{"user_id": userId})
  23 + // 异常
  24 + //if errFind != nil && errFind != domain.QueryNoRow {
  25 + // err = errFind
  26 + // return
  27 + //}
  28 + // 不存在
  29 + //if errFind == domain.QueryNoRow {
  30 + // imInfo = &domain.Im{
  31 + // UserId: userId,
  32 + // CreateTime: time.Now(),
  33 + // }
  34 + //}
  35 + // 已存在
  36 + if errFind == nil && imInfo != nil {
  37 + IsCreated = true
  38 + }
  39 +
  40 + if len(imInfo.Accid) == 0 {
  41 + //id, _ := utils.NewSnowflakeId()
  42 + //imInfo.ImId = fmt.Sprintf("%v", id)
  43 + }
  44 + checkImRequest = &im.CheckImRequest{
  45 + UserId: userId,
  46 + ImId: imInfo.Accid,
  47 + Uname: name,
  48 + CustomerImId: fmt.Sprintf("%v", imInfo.CsAccountId),
  49 + IsCreated: IsCreated,
  50 + }
  51 + if checkImResponse, err = CheckIm(checkImRequest); err != nil {
  52 + return
  53 + }
  54 + if len(imInfo.CsAccountId) == 0 {
  55 + imInfo.CsAccountId = getRandomCustomerAccount(userId)
  56 + }
  57 + imInfo.ImToken = checkImResponse.ImToken
  58 + //if _, err = ImInfoRepository.Save(imInfo); err != nil {
  59 + // return
  60 + //}
  61 + return
  62 +}
  63 +
  64 +// 检查ImToken
  65 +func CheckIm(request *im.CheckImRequest) (rsp *im.CheckImResponse, err error) {
  66 + var ()
  67 + rsp = &im.CheckImResponse{}
  68 + if !request.IsCreated {
  69 + if err = imCreate(request, rsp); err != nil {
  70 + return
  71 + }
  72 + } else {
  73 + if err = imUpdate(request, rsp); err != nil {
  74 + return
  75 + }
  76 + }
  77 + if err = imRefreshToken(request, rsp); err != nil {
  78 + return
  79 + }
  80 + return
  81 +}
  82 +
  83 +//create
  84 +func imCreate(request *im.CheckImRequest, rsp *im.CheckImResponse) (err error) {
  85 + var (
  86 + param im.UserCreate = im.UserCreate{
  87 + Accid: request.ImId,
  88 + Name: request.Uname,
  89 + Icon: request.Icon,
  90 + }
  91 + out *im.UserTokenResult
  92 + )
  93 + if out, err = im.CallCreate(param); err != nil {
  94 + return
  95 + }
  96 + if out.Code != 200 || (out.Info.Accid != request.ImId) {
  97 + return im.ErrorFailCall
  98 + }
  99 + rsp.ImToken = out.Info.Token
  100 + return
  101 +}
  102 +
  103 +//update user info
  104 +func imUpdate(request *im.CheckImRequest, rsp *im.CheckImResponse) (err error) {
  105 + var (
  106 + param im.UserUpdate = im.UserUpdate{
  107 + Accid: request.ImId,
  108 + Name: request.Uname,
  109 + Icon: request.Icon,
  110 + }
  111 + out *im.BaseResp
  112 + )
  113 + if out, err = im.CallUpdate(param); err != nil {
  114 + return
  115 + }
  116 + if out.Code != 200 {
  117 + return im.ErrorFailCall
  118 + }
  119 + return
  120 +}
  121 +
  122 +//refresh token
  123 +func imRefreshToken(request *im.CheckImRequest, rsp *im.CheckImResponse) (err error) {
  124 + var (
  125 + param im.UserRefreshToken = im.UserRefreshToken{
  126 + Accid: request.ImId,
  127 + }
  128 + out *im.UserTokenResult
  129 + )
  130 + if out, err = im.CallRefreshToken(param); err != nil {
  131 + return
  132 + }
  133 + if out.Code != 200 || (out.Info.Accid != request.ImId) {
  134 + return im.ErrorFailCall
  135 + }
  136 + rsp.ImToken = out.Info.Token
  137 + return
  138 +}
  139 +
  140 +// 获取客服id
  141 +func getRandomCustomerAccount(userId int64) (acid string) {
  142 + //ImCustomerServiceRepository, _ := factory.CreateImCustomerServiceRepository(ctx)
  143 + //total, customers, err := ImCustomerServiceRepository.Find(map[string]interface{}{"sortById": domain.ASC})
  144 + //if err != nil {
  145 + // log.Error(err)
  146 + // return 0
  147 + //}
  148 + //if total == 0 {
  149 + // return 0
  150 + //}
  151 + //index := userId % total
  152 + //if int(index) < len(customers) {
  153 + // acid, _ = strconv.ParseInt(customers[index].ImId, 10, 64)
  154 + // return
  155 + //}
  156 + //acid, _ = strconv.ParseInt(customers[0].ImId, 10, 64)
  157 + return
  158 +}
  1 +package im
  2 +
  3 +import (
  4 + "encoding/json"
  5 +)
  6 +
  7 +//func init() {
  8 +// InitImClient(constant.IM_SERVICE_ADDRESS, constant.IM_APP_KEY, constant.IM_APP_SECRET)
  9 +//}
  10 +
  11 +type RequestParam interface {
  12 + Format() map[string]string
  13 + GetPath() string
  14 + Valid() error
  15 +}
  16 +
  17 +//接口
  18 +func CallCreate(v UserCreate) (*UserTokenResult, error) {
  19 + var result UserTokenResult
  20 + btData, err := DefaultImClient.Call(v)
  21 + if err != nil {
  22 + return nil, err
  23 + }
  24 + err = json.Unmarshal(btData, &result)
  25 + if err != nil {
  26 + return nil, err
  27 + }
  28 + return &result, nil
  29 +}
  30 +func CallRefreshToken(v UserRefreshToken) (*UserTokenResult, error) {
  31 + var result UserTokenResult
  32 + btData, err := DefaultImClient.Call(v)
  33 + if err != nil {
  34 + return nil, err
  35 + }
  36 + err = json.Unmarshal(btData, &result)
  37 + if err != nil {
  38 + return nil, err
  39 + }
  40 + return &result, nil
  41 +}
  42 +func CallUpdate(v UserUpdate) (*BaseResp, error) {
  43 + var result BaseResp
  44 + btData, err := DefaultImClient.Call(v)
  45 + if err != nil {
  46 + return nil, err
  47 + }
  48 + err = json.Unmarshal(btData, &result)
  49 + if err != nil {
  50 + return nil, err
  51 + }
  52 + return &result, nil
  53 +}
  54 +
  55 +/*CheckIm */
  56 +type CheckImRequest struct {
  57 + UserId int64
  58 + ImId string
  59 + Uname string
  60 + Icon string
  61 + CustomerImId string
  62 + IsCreated bool
  63 +}
  64 +type CheckImResponse struct {
  65 + ImToken string //net im token
  66 + CsAccount int64 //客服id
  67 +}
  1 +package im
  2 +
  3 +import (
  4 + "testing"
  5 +)
  6 +
  7 +const (
  8 + IM_SERVICE_ADDRESS = "https://api.netease.im/nimserver"
  9 + IM_APP_KEY = "be7c0639c10e6a69f86ce3b4fa8dc8ec" //"ebf3ae278ee1b346773b99be5080f6a9"
  10 + IM_APP_SECRET = "9c5b60346613" //"67ea92e1ea45"
  11 +)
  12 +
  13 +func TestCallCreate(t *testing.T) {
  14 + InitImClient(IM_SERVICE_ADDRESS, IM_APP_KEY, IM_APP_SECRET)
  15 + token, err := CallCreate(UserCreate{Accid: "1"})
  16 + if err != nil {
  17 + t.Fatal(err)
  18 + }
  19 + if token == nil {
  20 + t.Fatal("token is nil")
  21 + }
  22 + t.Log(token.Code, token.Info)
  23 +}
  24 +
  25 +func TestCallRefreshToken(t *testing.T) {
  26 + InitImClient(IM_SERVICE_ADDRESS, IM_APP_KEY, IM_APP_SECRET)
  27 + token, err := CallRefreshToken(UserRefreshToken{Accid: "1"})
  28 + if err != nil {
  29 + t.Fatal(err)
  30 + }
  31 + if token == nil {
  32 + t.Fatal("token is nil")
  33 + }
  34 + t.Log(token.Code, token.Info)
  35 +}
  36 +
  37 +func TestCallUpdate(t *testing.T) {
  38 + InitImClient(IM_SERVICE_ADDRESS, IM_APP_KEY, IM_APP_SECRET)
  39 + token, err := CallUpdate(UserUpdate{Accid: "1", Name: "tip tok"})
  40 + if err != nil {
  41 + t.Fatal(err)
  42 + }
  43 + if token == nil {
  44 + t.Fatal("token is nil")
  45 + }
  46 + t.Log(token.Code)
  47 +}
  1 +package im
  2 +
  3 +import (
  4 + "crypto/sha1"
  5 + "encoding/hex"
  6 + "fmt"
  7 + "io/ioutil"
  8 + "math/rand"
  9 + "net/http"
  10 + "net/url"
  11 + "strconv"
  12 + "strings"
  13 + "time"
  14 +)
  15 +
  16 +var DefaultImClient Client
  17 +
  18 +var ErrorFailCall = fmt.Errorf(" imclient call failed")
  19 +
  20 +func InitImClient(baseUrl, appKey, appSecret string) {
  21 + DefaultImClient = Client{
  22 + baseUrl: baseUrl,
  23 + appKey: appKey,
  24 + appSecret: appSecret,
  25 + }
  26 +}
  27 +
  28 +type Client struct {
  29 + baseUrl string
  30 + appKey string
  31 + appSecret string
  32 +}
  33 +
  34 +func (i Client) Call(param RequestParam) ([]byte, error) {
  35 + return i.httpDo(param.GetPath(), param.Format())
  36 +}
  37 +func (i Client) buildHeader() http.Header {
  38 + var h = http.Header{}
  39 + curTime := strconv.FormatInt(time.Now().Unix(), 10)
  40 + nonce := strconv.FormatInt(time.Now().Unix()+rand.Int63n(5000), 10)
  41 + checkSum := buildCheckSum(i.appSecret, nonce, curTime)
  42 + h.Set("Content-Type", "application/x-www-form-urlencoded")
  43 + h.Set("AppKey", i.appKey)
  44 + h.Set("Nonce", nonce)
  45 + h.Set("CurTime", curTime)
  46 + h.Set("CheckSum", checkSum)
  47 + return h
  48 +}
  49 +func (i Client) httpDo(path string, posts map[string]string) ([]byte, error) {
  50 + client := http.Client{
  51 + Timeout: 5 * time.Second, //请求超时时间5秒
  52 + }
  53 + reqURL := i.baseUrl + path
  54 + params := url.Values{}
  55 + for k, v := range posts {
  56 + params.Add(k, v)
  57 + }
  58 + req, err := http.NewRequest("POST", reqURL, strings.NewReader(params.Encode()))
  59 + if err != nil {
  60 + return nil, err
  61 + }
  62 + req.Header = i.buildHeader()
  63 + resp, err := client.Do(req)
  64 + if err != nil {
  65 + return nil, err
  66 + }
  67 + defer resp.Body.Close()
  68 +
  69 + body, err := ioutil.ReadAll(resp.Body)
  70 + if err != nil {
  71 + return nil, err
  72 + }
  73 +
  74 + return body, nil
  75 +}
  76 +
  77 +func buildCheckSum(appSecret string, nonce string, curTime string) string {
  78 + str := []byte(appSecret + nonce + curTime)
  79 + sh := sha1.New()
  80 + sh.Write(str)
  81 + result := hex.EncodeToString(sh.Sum(nil))
  82 + return strings.ToLower(result)
  83 +}
  1 +package im
  2 +
  3 +import (
  4 + "fmt"
  5 +)
  6 +
  7 +var (
  8 + _ RequestParam = UserCreate{}
  9 + _ RequestParam = UserUpdate{}
  10 + _ RequestParam = UserRefreshToken{}
  11 +)
  12 +
  13 +type BaseResp struct {
  14 + Code int `json:"code"`
  15 +}
  16 +
  17 +// TokenInfo 云通信Token
  18 +type TokenInfo struct {
  19 + Token string `json:"token"`
  20 + Accid string `json:"accid"`
  21 + Name string `json:"name"`
  22 +}
  23 +type UserTokenResult struct {
  24 + BaseResp
  25 + Info TokenInfo `json:"info"`
  26 +}
  27 +
  28 +// 创建网易云通信ID
  29 +type UserCreate struct {
  30 + Accid string //网易云通信ID,最大长度32字符
  31 + Name string //ID昵称,最大长度64字符。
  32 + Props string //json属性,开发者可选填,最大长度1024字符
  33 + Icon string //ID头像URL,开发者可选填,最大长度1024字符
  34 + /**
  35 + 云通信ID可以指定登录token值,最大长度128字符,
  36 + 并更新,如果未指定,会自动生成token,并在
  37 + 创建成功后返回
  38 + **/
  39 + Token string
  40 + Sign string //签名
  41 + Email string
  42 + Birth string
  43 + Mobile string
  44 + Gender int //0未知,1男,2女
  45 + Ex string //扩展字段
  46 +}
  47 +
  48 +func (p UserCreate) Format() map[string]string {
  49 + return map[string]string{
  50 + "accid": p.Accid,
  51 + "name": p.Name,
  52 + "props": p.Props,
  53 + "icon": p.Icon,
  54 + "token": p.Token,
  55 + "sign": p.Sign,
  56 + "email": p.Email,
  57 + "birth": p.Birth,
  58 + "mobile": p.Mobile,
  59 + "gender": fmt.Sprintf("%d", p.Gender),
  60 + "ex": p.Ex,
  61 + }
  62 +}
  63 +func (p UserCreate) GetPath() string {
  64 + return "/user/create.action"
  65 +}
  66 +func (p UserCreate) Valid() error {
  67 + return nil
  68 +}
  69 +
  70 +// 重置网易云通信token
  71 +type UserRefreshToken struct {
  72 + Accid string //网易云通信ID,最大长度32字符,必须保证一个 APP内唯一
  73 +}
  74 +
  75 +func (p UserRefreshToken) Format() map[string]string {
  76 + return map[string]string{
  77 + "accid": p.Accid,
  78 + }
  79 +}
  80 +func (p UserRefreshToken) GetPath() string {
  81 + return "/user/refreshToken.action"
  82 +}
  83 +func (p UserRefreshToken) Valid() error {
  84 + return nil
  85 +}
  86 +
  87 +// 更新网易云通信token
  88 +type UserUpdate struct {
  89 + Accid string
  90 + Name string //这边网易云要有昵称以手机号码为昵称
  91 + Icon string //icon默认头像
  92 + Sign string //签名
  93 + Email string
  94 + Birth string
  95 + Mobile string
  96 + Gender int //0未知,1男,2女
  97 + Ex string //扩展字段
  98 +}
  99 +
  100 +func (u UserUpdate) Format() map[string]string {
  101 + return map[string]string{
  102 + "accid": u.Accid,
  103 + "name": u.Name,
  104 + "icon": u.Icon,
  105 + "sign": u.Sign,
  106 + "email": u.Email,
  107 + "birth": u.Birth,
  108 + "mobile": u.Mobile,
  109 + "gender": fmt.Sprintf("%d", u.Gender),
  110 + "ex": u.Ex,
  111 + }
  112 +}
  113 +func (u UserUpdate) GetPath() string {
  114 + return "/user/refreshToken.action"
  115 +}
  116 +func (u UserUpdate) Valid() error {
  117 + return nil
  118 +}