Commit 735ba425 by chenxianqi

update code

parent 24286a8f
......@@ -100,7 +100,7 @@ func (c *PublicController) Register() {
if user != nil {
// fetchResult
fetchResult, fetchError = utils.GetMiMcToken(strconv.FormatInt(user.ID, 10))
fetchResult, fetchError = utils.CreateMiMcToken(strconv.FormatInt(user.ID, 10))
if err := json.Unmarshal([]byte(fetchResult), &imTokenDto); err != nil {
c.JSON(configs.ResponseFail, "注册失败!", &err)
}
......@@ -126,7 +126,7 @@ func (c *PublicController) Register() {
if accountID, err := c.UserRepository.Add(user); err == nil {
fetchResult, fetchError = utils.GetMiMcToken(strconv.FormatInt(accountID, 10))
fetchResult, fetchError = utils.CreateMiMcToken(strconv.FormatInt(accountID, 10))
if err := json.Unmarshal([]byte(fetchResult), &imTokenDto); err != nil {
c.JSON(configs.ResponseFail, "注册失败!", &err)
}
......@@ -149,7 +149,7 @@ func (c *PublicController) Register() {
admin = c.AdminRepository.GetAdmin(auth.UID)
// fetchResult
fetchResult, fetchError = utils.GetMiMcToken(strconv.FormatInt(admin.ID, 10))
fetchResult, fetchError = utils.CreateMiMcToken(strconv.FormatInt(admin.ID, 10))
// imTokenDto
if err := json.Unmarshal([]byte(fetchResult), &imTokenDto); err != nil {
......
No preview for this file type
package grpcs
import (
context "context"
"crypto/md5"
fmt "fmt"
"log"
"net"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
// 业务实现方法的容器
type server struct{}
// 为server定义 DoMD5 方法 内部处理请求并返回结果
// 参数 (context.Context[固定], *test.Req[相应接口定义的请求参数])
// 返回 (*test.Res[相应接口定义的返回参数,必须用指针], error)
func (s *server) DoMD5(ctx context.Context, in *Req) (*Res, error) {
fmt.Println("MD5方法请求JSON:" + in.JsonStr)
return &Res{BackJson: "MD5 :" + fmt.Sprintf("%x", md5.Sum([]byte(in.JsonStr)))}, nil
}
// Run run grpc server
func Run() {
lis, err := net.Listen("tcp", ":8028") //监听所有网卡8028端口的TCP连接
if err != nil {
log.Fatalf("监听失败: %v", err)
}
s := grpc.NewServer() //创建gRPC服务
/**注册接口服务
* 以定义proto时的service为单位注册,服务中可以有多个方法
* (proto编译时会为每个service生成Register***Server方法)
* 包.注册服务方法(gRpc服务实例,包含接口方法的结构体[指针])
*/
RegisterWaiterServer(s, &server{})
/**如果有可以注册多个接口服务,结构体要实现对应的接口方法
* user.RegisterLoginServer(s, &server{})
* minMovie.RegisterFbiServer(s, &server{})
*/
// 在gRPC服务器上注册反射服务
reflection.Register(s)
// 将监听交给gRPC服务处理
err = s.Serve(lis)
if err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: test.proto
// 定义包名
package grpcs
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// 定义 Req 消息结构
type Req struct {
// 类型 字段 = 标识号
JsonStr string `protobuf:"bytes,1,opt,name=jsonStr,proto3" json:"jsonStr,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Req) Reset() { *m = Req{} }
func (m *Req) String() string { return proto.CompactTextString(m) }
func (*Req) ProtoMessage() {}
func (*Req) Descriptor() ([]byte, []int) {
return fileDescriptor_c161fcfdc0c3ff1e, []int{0}
}
func (m *Req) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Req.Unmarshal(m, b)
}
func (m *Req) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Req.Marshal(b, m, deterministic)
}
func (m *Req) XXX_Merge(src proto.Message) {
xxx_messageInfo_Req.Merge(m, src)
}
func (m *Req) XXX_Size() int {
return xxx_messageInfo_Req.Size(m)
}
func (m *Req) XXX_DiscardUnknown() {
xxx_messageInfo_Req.DiscardUnknown(m)
}
var xxx_messageInfo_Req proto.InternalMessageInfo
func (m *Req) GetJsonStr() string {
if m != nil {
return m.JsonStr
}
return ""
}
// 定义 Res 消息结构
type Res struct {
BackJson string `protobuf:"bytes,1,opt,name=backJson,proto3" json:"backJson,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Res) Reset() { *m = Res{} }
func (m *Res) String() string { return proto.CompactTextString(m) }
func (*Res) ProtoMessage() {}
func (*Res) Descriptor() ([]byte, []int) {
return fileDescriptor_c161fcfdc0c3ff1e, []int{1}
}
func (m *Res) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Res.Unmarshal(m, b)
}
func (m *Res) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Res.Marshal(b, m, deterministic)
}
func (m *Res) XXX_Merge(src proto.Message) {
xxx_messageInfo_Res.Merge(m, src)
}
func (m *Res) XXX_Size() int {
return xxx_messageInfo_Res.Size(m)
}
func (m *Res) XXX_DiscardUnknown() {
xxx_messageInfo_Res.DiscardUnknown(m)
}
var xxx_messageInfo_Res proto.InternalMessageInfo
func (m *Res) GetBackJson() string {
if m != nil {
return m.BackJson
}
return ""
}
func init() {
proto.RegisterType((*Req)(nil), "grpcs.Req")
proto.RegisterType((*Res)(nil), "grpcs.Res")
}
func init() {
proto.RegisterFile("test.proto", fileDescriptor_c161fcfdc0c3ff1e)
}
var fileDescriptor_c161fcfdc0c3ff1e = []byte{
// 130 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e,
0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4d, 0x2f, 0x2a, 0x48, 0x2e, 0x56, 0x92, 0xe7,
0x62, 0x0e, 0x4a, 0x2d, 0x14, 0x92, 0xe0, 0x62, 0xcf, 0x2a, 0xce, 0xcf, 0x0b, 0x2e, 0x29, 0x92,
0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x71, 0x95, 0x14, 0x41, 0x0a, 0x8a, 0x85, 0xa4, 0xb8,
0x38, 0x92, 0x12, 0x93, 0xb3, 0xbd, 0x8a, 0xf3, 0xf3, 0xa0, 0x2a, 0xe0, 0x7c, 0x23, 0x6d, 0x2e,
0xb6, 0xf0, 0xc4, 0xcc, 0x92, 0xd4, 0x22, 0x21, 0x45, 0x2e, 0x56, 0x97, 0x7c, 0x5f, 0x17, 0x53,
0x21, 0x2e, 0x3d, 0xb0, 0xf1, 0x7a, 0x41, 0xa9, 0x85, 0x52, 0x08, 0x76, 0xb1, 0x12, 0x43, 0x12,
0x1b, 0xd8, 0x7a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc3, 0x16, 0xa5, 0x68, 0x8c, 0x00,
0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// WaiterClient is the client API for Waiter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type WaiterClient interface {
// 定义接口 (结构体可以复用)
// 方法 (请求消息结构体) returns (返回消息结构体) {}
DoMD5(ctx context.Context, in *Req, opts ...grpc.CallOption) (*Res, error)
}
type waiterClient struct {
cc grpc.ClientConnInterface
}
func NewWaiterClient(cc grpc.ClientConnInterface) WaiterClient {
return &waiterClient{cc}
}
func (c *waiterClient) DoMD5(ctx context.Context, in *Req, opts ...grpc.CallOption) (*Res, error) {
out := new(Res)
err := c.cc.Invoke(ctx, "/grpcs.Waiter/DoMD5", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// WaiterServer is the server API for Waiter service.
type WaiterServer interface {
// 定义接口 (结构体可以复用)
// 方法 (请求消息结构体) returns (返回消息结构体) {}
DoMD5(context.Context, *Req) (*Res, error)
}
// UnimplementedWaiterServer can be embedded to have forward compatible implementations.
type UnimplementedWaiterServer struct {
}
func (*UnimplementedWaiterServer) DoMD5(ctx context.Context, req *Req) (*Res, error) {
return nil, status.Errorf(codes.Unimplemented, "method DoMD5 not implemented")
}
func RegisterWaiterServer(s *grpc.Server, srv WaiterServer) {
s.RegisterService(&_Waiter_serviceDesc, srv)
}
func _Waiter_DoMD5_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Req)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WaiterServer).DoMD5(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpcs.Waiter/DoMD5",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WaiterServer).DoMD5(ctx, req.(*Req))
}
return interceptor(ctx, in, info, handler)
}
var _Waiter_serviceDesc = grpc.ServiceDesc{
ServiceName: "grpcs.Waiter",
HandlerType: (*WaiterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "DoMD5",
Handler: _Waiter_DoMD5_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "test.proto",
}
syntax = "proto3";
// 定义包名
package grpcs;
// 可以定义多个服务,每个服务内可以定义多个接口
service Waiter {
// 定义接口 (结构体可以复用)
// 方法 (请求消息结构体) returns (返回消息结构体) {}
rpc DoMD5 (Req) returns (Res) {}
}
// 定义 Req 消息结构
message Req {
// 类型 字段 = 标识号
string jsonStr = 1;
}
// 定义 Res 消息结构
message Res {
string backJson = 1;
}
// PSjsonStrbackJson只是随手写的名字,并没有用json
\ No newline at end of file
......@@ -6,11 +6,13 @@ import (
"github.com/Xiaomi-mimc/mimc-go-sdk/util/log"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/toolbox"
_ "github.com/go-sql-driver/mysql"
"kefu_server/controllers"
"kefu_server/db"
_ "kefu_server/routers"
"kefu_server/task"
)
// Initialization log
......@@ -39,7 +41,9 @@ func main() {
initLog()
// init task
// task.Run()
task.Run()
toolbox.StartTask()
defer toolbox.StopTask()
/// Static file configuration
beego.SetStaticPath("/", "public/client")
......@@ -49,8 +53,8 @@ func main() {
// Handling Error
beego.ErrorController(&controllers.ErrorController{})
// init robot
// im.RobotInit()
// grpcserver init
// go grpcs.Run()
// run application
beego.Run()
......
......@@ -15,6 +15,7 @@ type AdminRepositoryInterface interface {
Add(admin *models.Admin, col1 string) (bool, int64, error)
Delete(id int64) (int64, error)
GetAdmins(request *AdminPaginationDto) (*AdminPaginationDto, error)
CheckAdminsLoginTimeOutAndSetOffline(lastMessageUnixTimer int64) int64
}
// AdminPaginationDto a struct
......@@ -46,6 +47,15 @@ func GetAdminRepositoryInstance() *AdminRepository {
return instance
}
// CheckAdminsLoginTimeOutAndSetOffline Check if user login timeout
func (r *AdminRepository) CheckAdminsLoginTimeOutAndSetOffline(lastMessageUnixTimer int64) int64 {
count, _ := r.q.Filter("online__in", 1, 2).Filter("last_activity__lte", lastMessageUnixTimer).Update(orm.Params{
"online": 0,
"current_con_user": 0,
})
return count
}
// GetAdmin get one admin with id
func (r *AdminRepository) GetAdmin(id int64) *models.Admin {
var admin models.Admin
......
......@@ -18,6 +18,8 @@ type ContactRepositoryInterface interface {
DeleteAll(uid int64) (int64, error)
Add(contact *models.Contact) (int64, error)
GetContactWithIds(ids ...int64) (*models.Contact, error)
SetTimeOutContactOffline()
GetTimeOutList() []*models.Contact
}
// ContactRepository struct
......@@ -32,6 +34,27 @@ func GetContactRepositoryInstance() *ContactRepository {
return instance
}
// GetTimeOutList get all timeout List
func (r *ContactRepository) GetTimeOutList(lastMessageUnixTimer int64) []*models.Contact {
var contacts []*models.Contact
_, err := r.o.Raw("SELECT * FROM `contact` WHERE `create_at` <= ? AND `is_session_end` = 0 AND `last_message_type` != 'timeout'", lastMessageUnixTimer).QueryRows(&contacts)
if err != nil {
logs.Warn("GetTimeOutList get all timeout List------------", err)
}
return contacts
}
// SetTimeOutContactOffline set time out user offline
func (r *ContactRepository) SetTimeOutContactOffline(userOffLineUnixTimer int64) {
_, err := r.q.Filter("create_at__lte", userOffLineUnixTimer).Update(orm.Params{
"last_message_type": "timeout",
"is_session_end": 1,
})
if err != nil {
logs.Warn("SetTimeOutContactOffline set time out user offline------------", err)
}
}
// Add add a Contact
func (r *ContactRepository) Add(contact *models.Contact) (int64, error) {
row, err := r.o.Insert(contact)
......
......@@ -17,6 +17,7 @@ type UserRepositoryInterface interface {
Update(id int64, params *orm.Params) (int64, error)
Delete(id int64) (int64, error)
GetOnlineCount() (int64, error)
CheckUsersLoginTimeOutAndSetOffline(lastMessageUnixTimer int64) int64
}
// UserRepository struct
......@@ -31,6 +32,18 @@ func GetUserRepositoryInstance() *UserRepository {
return instance
}
// CheckUsersLoginTimeOutAndSetOffline Check if user login timeout
func (r *UserRepository) CheckUsersLoginTimeOutAndSetOffline(lastMessageUnixTimer int64) int64 {
count, err := r.q.Filter("online__in", 1, 2).Filter("last_activity__lte", lastMessageUnixTimer).Update(orm.Params{
"online": 0,
"is_window": 0,
})
if err != nil {
logs.Warn("CheckUsersLoginTimeOutAndSetOffline Check if user login timeout------------", err)
}
return count
}
// Add create a user
func (r *UserRepository) Add(user *models.User) (int64, error) {
id, err := r.o.Insert(user)
......
......@@ -3,61 +3,56 @@ package task
import (
"encoding/base64"
"encoding/json"
"kefu_server/robot"
"kefu_server/models"
"kefu_server/services"
"kefu_server/utils"
"strconv"
"time"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/orm"
"github.com/astaxie/beego/toolbox"
)
// 定时任务
// Timed task
func appTask() {
o := orm.NewOrm()
// 任务调度(1分钟会执行一次)
checkOnLineTk := toolbox.NewTask("checkOnLine", "0 */1 * * * *", func() error {
im.RobotInit()
userOffLineUnixTimer := time.Now().Unix() - (60 * 10) // 用户最后活动时间T出在线状态规则
adminOffLineUnixTimer := time.Now().Unix() - (60 * 30) // 最后回复消息时间清理回话规则
lastMessageUnixTimer := time.Now().Unix() - (60 * 8) // 判断用户是否超过一定时间不使用,强制其下线
uqs := o.QueryTable(new(models.User))
aqs := o.QueryTable(new(models.Admin))
// 检查User
count, _ := uqs.Filter("online__in", 1, 2).Filter("last_activity__lte", lastMessageUnixTimer).Update(orm.Params{
"online": 0,
"is_window": 0,
})
logs.Info("清理登录超时用户,有", count, "个用户被强制下线")
// 检查Admin
_, _ = aqs.Filter("online__in", 1, 2).Filter("last_activity__lte", adminOffLineUnixTimer).Update(orm.Params{
"online": 0,
"current_con_user": 0,
})
// Task scheduling (will be executed once every 5 minute)
checkOnLineTk := toolbox.NewTask("checkOnLine", "0 */5 * * * *", func() error {
// timers
userOffLineUnixTimer := time.Now().Unix() - (60 * 10) // User's last activity time T out online status rule
adminOffLineUnixTimer := time.Now().Unix() - (60 * 30) // Final reply time
lastMessageUnixTimer := time.Now().Unix() - (60 * 8) // Determine if the user will not use it for a certain period of time and force them to go offline
// user
userOfflineCount := services.GetUserRepositoryInstance().CheckUsersLoginTimeOutAndSetOffline(lastMessageUnixTimer)
logs.Info("清理登录超时user", userOfflineCount, "个被强制下线")
// admin
adminOfflineCount := services.GetAdminRepositoryInstance().CheckAdminsLoginTimeOutAndSetOffline(adminOffLineUnixTimer)
logs.Info("清理登录超时admin", adminOfflineCount, "个被强制下线")
// get offline all robots
robots, _ := services.GetRobotRepositoryInstance().GetRobotOnlineAll()
if robots != nil && len(robots) == 0 {
services.GetContactRepositoryInstance().SetTimeOutContactOffline(userOffLineUnixTimer)
}
// 2.判断用户是否超时无应答
cqs := o.QueryTable(new(models.Contact))
// 检查(有机器人在线)
if len(im.Robots) > 0 {
robot := im.Robots[0]
var contacts []models.Contact
_, _ = o.Raw("SELECT * FROM `contact` WHERE `create_at` <= ? AND `is_session_end` = 0 AND `last_message_type` != 'timeout'", lastMessageUnixTimer).QueryRows(&contacts)
logs.Info("清理会话超时用户,有", len(contacts), "个用户被结束对话")
contacts := services.GetContactRepositoryInstance().GetTimeOutList(lastMessageUnixTimer)
logs.Info("清理会话超时用户,有", len(contacts), "个被结束对话")
for _, contact := range contacts {
// 判断发送方是客服就不处理发送了
if err := o.Read(&models.Admin{ID: contact.FromAccount}); err == nil {
// Does not handle customer service
if admin := services.GetAdminRepositoryInstance().GetAdmin(contact.FromAccount); admin != nil {
continue
}
// 发送超时消息体
robot := robots[0]
// message body
message := models.Message{}
message.BizType = "timeout"
message.Read = 0
appAccount, _ := strconv.ParseInt(robot.AppAccount(), 10, 64)
message.FromAccount = appAccount
message.FromAccount = robot.ID
message.Timestamp = time.Now().Unix()
message.Payload = "由于您长时间未回复,本次会话超时了"
message.ToAccount = contact.FromAccount
......@@ -65,55 +60,33 @@ func appTask() {
var messageString string
messageJSON, _ = json.Marshal(message)
messageString = base64.StdEncoding.EncodeToString([]byte(messageJSON))
robot.SendMessage(strconv.FormatInt(contact.FromAccount, 10), []byte(messageString))
utils.PushMessage(contact.FromAccount, messageString)
// 该客户超时后给客服发送提醒消息
// Send a reminder message to customer service
message.FromAccount = contact.FromAccount
message.ToAccount = contact.ToAccount
messageJSON, _ = json.Marshal(message)
messageString = base64.StdEncoding.EncodeToString([]byte(messageJSON))
robot.SendMessage(strconv.FormatInt(contact.ToAccount, 10), []byte(messageString))
utils.PushMessage(contact.ToAccount, messageString)
utils.MessageInto(message, true)
// 超时后消息
// 数据库获取机器人配置信息
robotData := models.Robot{ID: appAccount}
cacheRobotKey := "robot_" + string(appAccount)
if robotDataTemp := im.BmCache.Get(cacheRobotKey); robotDataTemp == nil {
robotData = models.Robot{ID: appAccount}
_ = o.Read(&robotData)
robotDataJSON, _ := json.Marshal(robotData)
_ = im.BmCache.Put(cacheRobotKey, robotDataJSON, 60*time.Second)
} else {
_ = json.Unmarshal([]byte(string(robotDataTemp.([]byte))), &robotData)
}
if robotData.TimeoutText != "" {
message.FromAccount = appAccount
// Message after timeout
if robot.TimeoutText != "" {
message.FromAccount = robot.ID
message.ToAccount = contact.FromAccount
message.BizType = "text"
message.Key = time.Now().Unix()
message.Payload = robotData.TimeoutText
message.Payload = robot.TimeoutText
messageJSON, _ = json.Marshal(message)
messageString = base64.StdEncoding.EncodeToString([]byte(messageJSON))
robot.SendMessage(strconv.FormatInt(contact.FromAccount, 10), []byte(messageString))
utils.PushMessage(contact.FromAccount, messageString)
}
}
} else {
// 执行到这里说明,机器人死掉了
logs.Error("执行到这里说明,机器人死掉了")
im.RobotInit()
_, _ = cqs.Filter("create_at__lte", userOffLineUnixTimer).Update(orm.Params{
"last_message_type": "timeout",
"is_session_end": 1,
})
}
return nil
})
toolbox.AddTask("checkOnLine", checkOnLineTk)
toolbox.StartTask()
defer toolbox.StopTask()
}
......@@ -19,8 +19,8 @@ type TokenHandler struct {
AppAccount string `json:"appAccount"`
}
// GetMiMcToken ...
func GetMiMcToken(accountID string) (string, error) {
// CreateMiMcToken ...
func CreateMiMcToken(accountID string) (string, error) {
tokenHandler := new(TokenHandler)
tokenHandler.httpURL = beego.AppConfig.String("mimc_HttpUrl")
tokenHandler.AppID, _ = beego.AppConfig.Int64("mimc_appId")
......
package utils
import (
"fmt"
"strconv"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
)
// PushMessage send message
// toAccount receive message account
// bizType message type
// msg message base 64
func PushMessage(toAccount int64, msg string) bool {
appID, _ := beego.AppConfig.Int64("mimc_appId")
appKey := beego.AppConfig.String("mimc_appKey")
appSecret := beego.AppConfig.String("mimc_appSecret")
api := "https://mimc.chat.xiaomi.net/api/push/p2p/"
var request = map[string]string{}
request["appId"] = strconv.FormatInt(appID, 10)
request["appKey"] = appKey
request["appSecret"] = appSecret
request["fromAccount"] = "1"
request["toAccount"] = strconv.FormatInt(toAccount, 10)
request["msg"] = msg
response := HTTPRequest(api, "POST", request, "")
logs.Info(response)
if response.Code != 200 {
fmt.Println(response)
return false
}
return true
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment