作者 yangfu

增加:网易im

... ... @@ -67,6 +67,11 @@ func Login(header *protocol.RequestHeader, request *protocol.LoginRequest) (rsp
break
}
rsp.AuthCode, _ = utils.GenerateToken(partnerInfo.Id, protocol.AuthCodeExpire*time.Second)
if err = InitOrUpdateUserIMInfo(partnerInfo, transactionContext); err != nil {
log.Error(err)
return
}
err = transactionContext.CommitTransaction()
return
}
... ...
package auth
import (
"fmt"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/log"
"strconv"
"time"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/application/factory"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/domain"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/im"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/pg/transaction"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/utils"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/protocol"
)
// 更新用户 IM INFO
func InitOrUpdateUserIMInfo(partnerInfo *domain.PartnerInfo, ctx *transaction.TransactionContext) (err error) {
var (
ImInfoRepository, _ = factory.CreateImInfoRepository(ctx)
checkImRequest *protocol.CheckImRequest = &protocol.CheckImRequest{}
IsCreated = false
checkImResponse *protocol.CheckImResponse
)
imInfo, e := ImInfoRepository.FindOne(map[string]interface{}{"user_id": partnerInfo.Id})
// 异常
if e != nil && e != domain.QueryNoRow {
err = e
return
}
// 不存在
if e == domain.QueryNoRow {
imInfo = &domain.ImInfo{
UserId: partnerInfo.Id,
CreateTime: time.Now(),
}
}
// 已存在
if e == nil && imInfo != nil {
IsCreated = true
}
if len(imInfo.ImId) == 0 {
id, _ := utils.NewSnowflakeId()
imInfo.ImId = fmt.Sprintf("%v", id)
}
checkImRequest = &protocol.CheckImRequest{
UserId: imInfo.UserId,
ImId: imInfo.ImId,
Uname: partnerInfo.PartnerName,
CustomerImId: fmt.Sprintf("%v", imInfo.CustomerImId),
IsCreated: IsCreated,
}
if checkImResponse, err = CheckIm(checkImRequest); err != nil {
return
}
if imInfo.CustomerImId == 0 {
imInfo.CustomerImId = getRandomCustomerAccount(partnerInfo.Id, ctx)
}
imInfo.ImToken = checkImResponse.ImToken
imInfo.UpdateTime = time.Now()
if _, err = ImInfoRepository.Save(imInfo); err != nil {
return
}
return
}
// 检查ImToken
func CheckIm(request *protocol.CheckImRequest) (rsp *protocol.CheckImResponse, err error) {
var ()
rsp = &protocol.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 *protocol.CheckImRequest, rsp *protocol.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 *protocol.CheckImRequest, rsp *protocol.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 *protocol.CheckImRequest, rsp *protocol.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, ctx *transaction.TransactionContext) (acid int64) {
CustomerServiceRepository, _ := factory.CreateCustomerServiceRepository(ctx)
total, customers, err := CustomerServiceRepository.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
}
... ...
... ... @@ -46,3 +46,13 @@ func CreateOrderBaseRepository(transactionContext *transaction.TransactionContex
func CreateOrderGoodRepository(transactionContext *transaction.TransactionContext) (domain.OrderGoodRepository, error) {
return repository.NewOrderGoodRepository(transactionContext)
}
//CreateImInfoRepository Im信息
func CreateImInfoRepository(transactionContext *transaction.TransactionContext) (domain.ImInfoRepository, error) {
return repository.NewImInfoRepository(transactionContext)
}
//CreateImInfoRepository Im信息
func CreateCustomerServiceRepository(transactionContext *transaction.TransactionContext) (domain.CustomerServiceRepository, error) {
return repository.NewCustomerServiceRepository(transactionContext)
}
... ...
... ... @@ -81,9 +81,10 @@ func orderProducts(order *domain.OrderBase) interface{} {
item["orderCount"] = good.PlanGoodNumber
item["orderAmount"] = good.PlanAmount
item["dividendPercent"] = good.PartnerBonusPercent
item["dividendReceivable"] = good.PartnerBonusHas
item["dividendUnReceive"] = good.PartnerBonusNot
item["dividendExpend"] = good.PartnerBonusExpense
item["dividendReceivable"] = good.PlanPartnerBonus //已收分红
item["dividendReceived"] = good.PartnerBonusHas //已收分红
item["dividendUnReceive"] = good.PartnerBonusNot // 未收分红
item["dividendExpend"] = good.PartnerBonusExpense //分红支出
if len(good.Remark) > 0 {
item["orderUpdateReason"] = good.Remark
}
... ...
package constant
import "os"
var (
IM_SERVICE_ADDRESS = "https://api.netease.im/nimserver"
IM_APP_KEY = "ebf3ae278ee1b346773b99be5080f6a9"
IM_APP_SECRET = "67ea92e1ea45"
)
func init() {
if os.Getenv("IM_APP_KEY") != "" {
IM_APP_KEY = os.Getenv("IM_APP_KEY")
}
if os.Getenv("IM_APP_SECRET") != "" {
IM_APP_SECRET = os.Getenv("IM_APP_SECRET")
}
}
... ...
package domain
import "time"
type CustomerService struct {
// id
Id int64
// 用户id(合伙人Id)
UserId int64
// IM唯一id
ImId string
// IM颁发的token
ImToken string
// 创建时间
CreateTime time.Time
// 更新时间
UpdateTime time.Time
}
type CustomerServiceRepository interface {
Save(dm *CustomerService) (*CustomerService, error)
Remove(dm *CustomerService) (*CustomerService, error)
FindOne(queryOptions map[string]interface{}) (*CustomerService, error)
Find(queryOptions map[string]interface{}) (int64, []*CustomerService, error)
}
func (m *CustomerService) Identify() interface{} {
if m.Id == 0 {
return nil
}
return m.Id
}
... ...
package domain
import "fmt"
//查询参数
const (
ASC = "ASC"
... ... @@ -15,3 +17,7 @@ const (
BonusWaitPay = iota + 1 //等待支付分红
BonusPaid //已经支付分红
)
var (
QueryNoRow = fmt.Errorf("not row found")
)
... ...
package domain
import "time"
type ImInfo struct {
tableName struct{} `pg:"im_info"`
// id
Id int64
// 合伙人Id
UserId int64
// IM唯一id
ImId string
// IM颁发的token
ImToken string
// 客服IM编号
CustomerImId int64
// 是否是客服 true:是 false:否
//IsCustomer bool
// 创建时间
CreateTime time.Time
// 更新时间
UpdateTime time.Time
}
type ImInfoRepository interface {
Save(dm *ImInfo) (*ImInfo, error)
Remove(dm *ImInfo) (*ImInfo, error)
FindOne(queryOptions map[string]interface{}) (*ImInfo, error)
Find(queryOptions map[string]interface{}) (int64, []*ImInfo, error)
}
func (m *ImInfo) Identify() interface{} {
if m.Id == 0 {
return nil
}
return m.Id
}
... ...
package im
import (
"encoding/json"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/constant"
)
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
}
... ...
package im
import (
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/constant"
"testing"
)
func TestCallCreate(t *testing.T) {
InitImClient(constant.IM_SERVICE_ADDRESS, constant.IM_APP_KEY, constant.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(constant.IM_SERVICE_ADDRESS, constant.IM_APP_KEY, constant.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(constant.IM_SERVICE_ADDRESS, constant.IM_APP_KEY, constant.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
}
... ...
... ... @@ -33,6 +33,8 @@ func init() {
(*models.Company)(nil),
(*models.OrderBase)(nil),
(*models.OrderGood)(nil),
(*models.ImInfo)(nil),
(*models.CustomerService)(nil),
} {
err := DB.CreateTable(model, &orm.CreateTableOptions{
Temp: false,
... ...
package models
import "time"
type CustomerService struct {
tableName struct{} `pg:"customer_service"`
// id
Id int64
// 用户id(合伙人Id)
UserId int64
// IM唯一id
ImId string
// IM颁发的token
ImToken string
// 创建时间
CreateTime time.Time
// 更新时间
UpdateTime time.Time
}
... ...
package models
import "time"
type ImInfo struct {
tableName struct{} `pg:"im_info"`
// id
Id int64
// 合伙人Id
UserId int64
// IM唯一id
ImId string
// IM颁发的token
ImToken string
// 客服IM编号
CustomerImId int64
// 是否是客服 true:是 false:否
//IsCustomer bool
// 创建时间
CreateTime time.Time
// 更新时间
UpdateTime time.Time
}
... ...
package repository
import (
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/domain"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/pg/models"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/pg/transaction"
. "gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/utils"
)
type CustomerServiceRepository struct {
transactionContext *transaction.TransactionContext
}
func (repository *CustomerServiceRepository) Save(dm *domain.CustomerService) (*domain.CustomerService, error) {
var (
err error
m = &models.CustomerService{}
tx = repository.transactionContext.PgTx
)
if err = GobModelTransform(m, dm); err != nil {
return nil, err
}
if dm.Identify() == nil {
if err = tx.Insert(m); err != nil {
return nil, err
}
return dm, nil
}
if err = tx.Update(m); err != nil {
return nil, err
}
return dm, nil
}
func (repository *CustomerServiceRepository) Remove(CustomerService *domain.CustomerService) (*domain.CustomerService, error) {
var (
tx = repository.transactionContext.PgTx
CustomerServiceModel = &models.CustomerService{Id: CustomerService.Identify().(int64)}
)
if _, err := tx.Model(CustomerServiceModel).Where("id = ?", CustomerService.Id).Delete(); err != nil {
return CustomerService, err
}
return CustomerService, nil
}
func (repository *CustomerServiceRepository) FindOne(queryOptions map[string]interface{}) (*domain.CustomerService, error) {
tx := repository.transactionContext.PgTx
CustomerServiceModel := new(models.CustomerService)
query := NewQuery(tx.Model(CustomerServiceModel), queryOptions)
query.SetWhere("id = ?", "id")
query.SetWhere("user_id = ?", "user_id")
if err := query.First(); err != nil {
return nil, domain.QueryNoRow
}
if CustomerServiceModel.Id == 0 {
return nil, domain.QueryNoRow
}
return repository.transformPgModelToDomainModel(CustomerServiceModel)
}
func (repository *CustomerServiceRepository) Find(queryOptions map[string]interface{}) (int64, []*domain.CustomerService, error) {
tx := repository.transactionContext.PgTx
var CustomerServiceModels []*models.CustomerService
CustomerServices := make([]*domain.CustomerService, 0)
query := NewQuery(tx.Model(&CustomerServiceModels), queryOptions).
SetOrder("create_time", "sortByCreateTime").
SetOrder("update_time", "sortByUpdateTime").
SetOrder("id", "sortById")
var err error
if query.AffectRow, err = query.SelectAndCount(); err != nil {
return 0, CustomerServices, err
}
for _, CustomerServiceModel := range CustomerServiceModels {
if CustomerService, err := repository.transformPgModelToDomainModel(CustomerServiceModel); err != nil {
return 0, CustomerServices, err
} else {
CustomerServices = append(CustomerServices, CustomerService)
}
}
return int64(query.AffectRow), CustomerServices, nil
}
func (repository *CustomerServiceRepository) transformPgModelToDomainModel(CustomerServiceModel *models.CustomerService) (*domain.CustomerService, error) {
m := &domain.CustomerService{}
err := GobModelTransform(m, CustomerServiceModel)
return m, err
}
func NewCustomerServiceRepository(transactionContext *transaction.TransactionContext) (*CustomerServiceRepository, error) {
if transactionContext == nil {
return nil, ERR_EMPTY_TC
}
return &CustomerServiceRepository{transactionContext: transactionContext}, nil
}
... ...
package repository
import (
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/domain"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/pg/models"
"gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/pg/transaction"
. "gitlab.fjmaimaimai.com/mmm-go/partner/pkg/infrastructure/utils"
)
type ImInfoRepository struct {
transactionContext *transaction.TransactionContext
}
func (repository *ImInfoRepository) Save(dm *domain.ImInfo) (*domain.ImInfo, error) {
var (
err error
m = &models.ImInfo{}
tx = repository.transactionContext.PgTx
)
if err = GobModelTransform(m, dm); err != nil {
return nil, err
}
if dm.Identify() == nil {
if err = tx.Insert(m); err != nil {
return nil, err
}
return dm, nil
}
if err = tx.Update(m); err != nil {
return nil, err
}
return dm, nil
}
func (repository *ImInfoRepository) Remove(ImInfo *domain.ImInfo) (*domain.ImInfo, error) {
var (
tx = repository.transactionContext.PgTx
ImInfoModel = &models.ImInfo{Id: ImInfo.Identify().(int64)}
)
if _, err := tx.Model(ImInfoModel).Where("id = ?", ImInfo.Id).Delete(); err != nil {
return ImInfo, err
}
return ImInfo, nil
}
func (repository *ImInfoRepository) FindOne(queryOptions map[string]interface{}) (*domain.ImInfo, error) {
tx := repository.transactionContext.PgTx
ImInfoModel := new(models.ImInfo)
query := NewQuery(tx.Model(ImInfoModel), queryOptions)
query.SetWhere("id = ?", "id")
query.SetWhere("user_id = ?", "user_id")
if err := query.First(); err != nil {
return nil, domain.QueryNoRow
}
if ImInfoModel.Id == 0 {
return nil, domain.QueryNoRow
}
return repository.transformPgModelToDomainModel(ImInfoModel)
}
func (repository *ImInfoRepository) Find(queryOptions map[string]interface{}) (int64, []*domain.ImInfo, error) {
tx := repository.transactionContext.PgTx
var ImInfoModels []*models.ImInfo
ImInfos := make([]*domain.ImInfo, 0)
query := NewQuery(tx.Model(&ImInfoModels), queryOptions).
SetOrder("im_info.create_time", "sortByCreateTime").
SetOrder("im_info.update_time", "sortByUpdateTime")
var err error
if query.AffectRow, err = query.SelectAndCount(); err != nil {
return 0, ImInfos, err
}
for _, ImInfoModel := range ImInfoModels {
if ImInfo, err := repository.transformPgModelToDomainModel(ImInfoModel); err != nil {
return 0, ImInfos, err
} else {
ImInfos = append(ImInfos, ImInfo)
}
}
return int64(query.AffectRow), ImInfos, nil
}
func (repository *ImInfoRepository) transformPgModelToDomainModel(ImInfoModel *models.ImInfo) (*domain.ImInfo, error) {
m := &domain.ImInfo{}
err := GobModelTransform(m, ImInfoModel)
return m, err
}
func NewImInfoRepository(transactionContext *transaction.TransactionContext) (*ImInfoRepository, error) {
if transactionContext == nil {
return nil, ERR_EMPTY_TC
}
return &ImInfoRepository{transactionContext: transactionContext}, nil
}
... ...
... ... @@ -91,3 +91,17 @@ type AuthCheckSmsCodeRequest struct {
type AuthCheckSmsCodeResponse struct {
CaptchaCertificate string `json:"captchaCertificate"` //短信验证码通过凭证
}
/*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
}
... ...