From 5b12078be45d0f10751507791f38dae448638a84 Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Wed, 9 Apr 2025 19:48:03 +0800 Subject: [PATCH] Add RPC support and enhance MessageCenter with context --- config.go | 1 + const.go | 48 ++++---- go.mod | 13 +- msg.go | 38 +++--- option.go | 28 +++++ paramset.go | 34 +++++- protoc/msg/msg.proto | 274 +++++++++++++++++++++++++++++++++++++++++++ protoc/rpc.go | 21 ++++ 8 files changed, 415 insertions(+), 42 deletions(-) create mode 100644 option.go create mode 100644 protoc/msg/msg.proto create mode 100644 protoc/rpc.go diff --git a/config.go b/config.go index 64efc46..a451c4f 100644 --- a/config.go +++ b/config.go @@ -52,6 +52,7 @@ type ( TaxAmount string `json:"taxAmount"` //税率 CallbackUrl string `json:"callbackUrl"` //回调地址 Remark string `json:"remark"` + Purpose string `json:"purpose"` SystemName string `json:"systemName"` //业务系统名称 SubjectName string `json:"subjectName"` //户名 BankInfo BankInfo `json:"bankInfo"` diff --git a/const.go b/const.go index 17e7fe9..3134d08 100644 --- a/const.go +++ b/const.go @@ -1,5 +1,7 @@ package l_msg_api +import "gitea.cdlsxd.cn/self-tools/l_msg_api/protoc/msg" + const ( serverHost = "http://127.0.0.1:8001" timeOut = 60 @@ -13,17 +15,26 @@ const ( DDDateField = "DDDateField" ) -type RequestPath string +type requestPathIndex int32 const ( - accessToken RequestPath = "/oauth/v1/accesstoken" - oaCreat RequestPath = "/msg/v1/dingtalk/oa/create" - oaGet RequestPath = "/msg/v1/dingtalk/oa/get" - oaComment RequestPath = "/msg/v1/dingtalk/oa/comment" - sendSms RequestPath = "/msg/v1/sms/send" - sendSmsHs RequestPath = "/msg/v1/sms/send/hs" + accessToken requestPathIndex = iota + 1 + oaCreat + oaGet + oaComment + sendSms + sendSmsHs ) +var requestPath = map[requestPathIndex]map[RequestWay]string{ + accessToken: {Http: "/oauth/v1/accesstoken", Rpc: msg.Msg_Oauth_FullMethodName}, + oaCreat: {Http: "/msg/v1/dingtalk/oa/create", Rpc: msg.Msg_DingOACreate_FullMethodName}, + oaGet: {Http: "/msg/v1/dingtalk/oa/get", Rpc: msg.Msg_DingOAGet_FullMethodName}, + oaComment: {Http: "/msg/v1/dingtalk/oa/comment", Rpc: msg.Msg_DingOAComment_FullMethodName}, + sendSms: {Http: "/msg/v1/sms/send", Rpc: msg.Msg_SmsSend_FullMethodName}, + sendSmsHs: {Http: "/msg/v1/sms/send/hs", Rpc: msg.Msg_HsSmsSend_FullMethodName}, +} + type SmsBusiness string const ( @@ -31,21 +42,14 @@ const ( SmsBusinessDefault SmsBusiness = "aliyun" ) -var smsBusinessWithRequestPath = map[SmsBusiness]RequestPath{ - SmsBusinessHs: sendSmsHs, - SmsBusinessDefault: sendSms, +var smsBusinessWithRequestPath = map[SmsBusiness]requestPathIndex{ + SmsBusinessHs: sendSms, + SmsBusinessDefault: sendSmsHs, } -type ( - SmsOption func(*SmsOptionData) - SmsOptionData struct { - Business SmsBusiness - } +type RequestWay int8 + +const ( + Http RequestWay = iota + 1 + Rpc ) - -func WithBusiness(business SmsBusiness) SmsOption { - - return func(OptionData *SmsOptionData) { - OptionData.Business = business - } -} diff --git a/go.mod b/go.mod index fc9edce..017f9b8 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,18 @@ module gitea.cdlsxd.cn/self-tools/l_msg_api go 1.22.2 -require github.com/valyala/fasthttp v1.59.0 +require ( + github.com/valyala/fasthttp v1.59.0 + google.golang.org/grpc v1.71.1 +) + +require ( + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/protobuf v1.36.4 // indirect +) require ( github.com/andybalholm/brotli v1.1.1 // indirect diff --git a/msg.go b/msg.go index cf6fd52..beee123 100644 --- a/msg.go +++ b/msg.go @@ -1,6 +1,7 @@ package l_msg_api import ( + "context" "encoding/json" "errors" ) @@ -10,10 +11,11 @@ type MessageCenter struct { ClientKey string // 客户端id,获取授权token需要 ClientSecret string header map[string]string + option *mesOptionData base } -func NewMessageCenter(host, clientKey, clientSecret, serverIndex, tempIndex string) (*MessageCenter, error) { +func NewMessageCenter(host, clientKey, clientSecret, serverIndex, tempIndex string, args ...MesOption) (*MessageCenter, error) { msg := &MessageCenter{ Host: host, ClientKey: clientKey, @@ -22,21 +24,25 @@ func NewMessageCenter(host, clientKey, clientSecret, serverIndex, tempIndex stri ServerIndex: serverIndex, TempIndex: tempIndex, }, + option: new(mesOptionData), + } + err := msg.setHeader() + if err != nil { + return nil, err + } + for _, arg := range args { + arg(msg.option) } - msg.header = map[string]string{"content-type": "application/json; charset=utf-8"} - token, err := msg.getAccessToken() - msg.header = map[string]string{"Authorization": token, "content-type": "application/json; charset=utf-8"} - return msg, err } // OACreate 发起OA审批 -func (m *MessageCenter) OACreate(dTalkUserId, treadNo string, formModel *FormsData) (data OAResponse, err error) { +func (m *MessageCenter) OACreate(ctx context.Context, dTalkUserId, treadNo string, formModel *FormsData) (data OAResponse, err error) { formModel.formBase = formBase{ OutTradeNo: treadNo, OriginatorUserId: dTalkUserId, } - err = m.post(oaCreat, m.parseOACreateParam(formModel), &data) + err = m.send(ctx, oaCreat, m.parseOACreateParam(formModel), &data) if err != nil { return } @@ -44,12 +50,12 @@ func (m *MessageCenter) OACreate(dTalkUserId, treadNo string, formModel *FormsDa } // OAGetDetail OA详情 -func (m *MessageCenter) OAGetDetail(outTradeNo string) (data OAGetDetailData, err error) { +func (m *MessageCenter) OAGetDetail(ctx context.Context, outTradeNo string) (data OAGetDetailData, err error) { param, _ := json.Marshal(oAGetDetailRequest{ Base: m.base, OutTradeNo: outTradeNo, }) - err = m.post(oaGet, param, &data) + err = m.send(ctx, oaGet, param, &data) if err != nil { return } @@ -58,9 +64,9 @@ func (m *MessageCenter) OAGetDetail(outTradeNo string) (data OAGetDetailData, er // SendSms 短信 // business SmsBusiness -func (m *MessageCenter) SendSms(tels []string, jsonParam string, args ...SmsOption) (data SmsSend, err error) { +func (m *MessageCenter) SendSms(ctx context.Context, tels []string, jsonParam string, args ...SmsOption) (data SmsSend, err error) { var ( - e = new(SmsOptionData) + e = new(smsOptionData) ) if len(tels) == 0 { err = errors.New("手机号不能为空") @@ -80,7 +86,7 @@ func (m *MessageCenter) SendSms(tels []string, jsonParam string, args ...SmsOpti path := smsBusinessWithRequestPath[e.Business] param := m.parseSmsSendParam(tels, jsonParam) - err = m.post(path, param, &data) + err = m.send(ctx, path, param, &data) if err != nil { return } @@ -90,11 +96,11 @@ func (m *MessageCenter) SendSms(tels []string, jsonParam string, args ...SmsOpti // SendBlackBoard 钉钉公告 // deptidList //接收部门ID列表,最大的列表长度为20。 // UseridList //接收部用户ID列表,最大的列表长度为20。 -func (m *MessageCenter) SendBlackBoard(title, content string, deptidList []int, useridList []string) (data Default, err error) { +func (m *MessageCenter) SendBlackBoard(ctx context.Context, title, content string, deptidList []int, useridList []string) (data Default, err error) { receiver := blackboardReceiverView{ deptidList, useridList, } - err = m.post(sendSms, m.parseSendBlackBoardParam(title, content, receiver), &data) + err = m.send(ctx, sendSms, m.parseSendBlackBoardParam(title, content, receiver), &data) if err != nil { return } @@ -102,7 +108,7 @@ func (m *MessageCenter) SendBlackBoard(title, content string, deptidList []int, } // OAComment OA评论,CommentUserId为空则默认审核发起人评论 -func (m *MessageCenter) OAComment(outTradeNo, text, commentUserId string, file *DingOACommentReqFile) (data OAResponse, err error) { +func (m *MessageCenter) OAComment(ctx context.Context, outTradeNo, text, commentUserId string, file *DingOACommentReqFile) (data OAResponse, err error) { req := &oACommentRequest{ Base: m.base, OutTradeNo: outTradeNo, @@ -114,7 +120,7 @@ func (m *MessageCenter) OAComment(outTradeNo, text, commentUserId string, file * } param, _ := json.Marshal(req) - err = m.post(oaComment, param, &data) + err = m.send(ctx, oaComment, param, &data) if err != nil { return } diff --git a/option.go b/option.go new file mode 100644 index 0000000..57203f1 --- /dev/null +++ b/option.go @@ -0,0 +1,28 @@ +package l_msg_api + +type ( + SmsOption func(*smsOptionData) + smsOptionData struct { + Business SmsBusiness + } +) + +func WithBusiness(business SmsBusiness) SmsOption { + + return func(OptionData *smsOptionData) { + OptionData.Business = business + } +} + +type ( + MesOption func(*mesOptionData) + mesOptionData struct { + RequestWay RequestWay + } +) + +func WithRequestWay(RequestWay RequestWay) MesOption { + return func(OptionData *mesOptionData) { + OptionData.RequestWay = RequestWay + } +} diff --git a/paramset.go b/paramset.go index 5a86993..2861f41 100644 --- a/paramset.go +++ b/paramset.go @@ -1,6 +1,7 @@ package l_msg_api import ( + "context" "encoding/json" "fmt" "gitea.cdlsxd.cn/self-tools/l_msg_api/cache" @@ -38,7 +39,6 @@ func (m *MessageCenter) parseSendBlackBoardParam(title, content string, receiver } func (m *MessageCenter) getAccessToken() (string, error) { - if tokenInterface, exist := cache.InstanceCacheMap().Get(m.ClientKey); exist { return tokenInterface.(string), nil } @@ -53,7 +53,25 @@ func (m *MessageCenter) getAccessToken() (string, error) { return data.AccessToken, err } -func (m *MessageCenter) post(path RequestPath, data []byte, resReflect interface{}) (err error) { +func (m *MessageCenter) send(ctx context.Context, path requestPathIndex, data []byte, resReflect interface{}) (err error) { + switch m.option.RequestWay { + case Rpc: + //return m.rpc(ctx, requestAddr, data, resReflect) + default: + requestAddr := requestPath[path][m.option.RequestWay] + return m.post(ctx, requestAddr, data, resReflect) + } + return +} + +//func (m *MessageCenter) rpc(ctx context.Context, path string, data []byte, resReflect interface{}) (err error) { +// +// client := protoc.InstanceMsgClient(m.Host) +// client.FinanceNotify(ctx, a) +// return +//} + +func (m *MessageCenter) post(ctx context.Context, path string, data []byte, resReflect interface{}) (err error) { var body responseBody res, err := httpclient.FastHttpPost(fmt.Sprintf("%s%s", m.Host, path), m.header, data, timeOut) if err != nil { @@ -77,7 +95,7 @@ func (m *MessageCenter) post(path RequestPath, data []byte, resReflect interface return } -func (m *MessageCenter) accessPost(path RequestPath, data []byte, resReflect interface{}) (err error) { +func (m *MessageCenter) accessPost(path requestPathIndex, data []byte, resReflect interface{}) (err error) { var body responseBody res, err := httpclient.FastHttpPost(fmt.Sprintf("%s%s", m.Host, path), m.header, data, timeOut) if err != nil { @@ -100,3 +118,13 @@ func (m *MessageCenter) accessPost(path RequestPath, data []byte, resReflect int return } + +func (m *MessageCenter) setHeader() (err error) { + m.header = map[string]string{"content-type": "application/json; charset=utf-8"} + token, err := m.getAccessToken() + if err != nil { + return + } + m.header = map[string]string{"Authorization": token, "content-type": "application/json; charset=utf-8"} + return +} diff --git a/protoc/msg/msg.proto b/protoc/msg/msg.proto new file mode 100644 index 0000000..0bf2c31 --- /dev/null +++ b/protoc/msg/msg.proto @@ -0,0 +1,274 @@ +syntax = "proto3"; + +package msg; +option go_package = "/msg;msg"; + + +message Empty{} + +message BaseRes{ + bool isSuccess = 1; + string Msg = 2; +} + + +message MsgCenterResp{ + oneof Resp{ + SmsSendRes smsSendRes = 1; + HsSmsSendRes HsSmsSendRes = 2; + BaseRes baseRes = 3; + } +} + + +message OauthReq { + string clientKey = 1; + string clientSecret = 2; +} + +message OauthResp { + string accessToken = 2; + int64 AccessExpire = 3; +} + + +message CenterSendReq{ + string severType = 1; + string msgType = 2; + BaseReq base = 3; + string config = 4; +} + +message BaseReq{ + string serverIndex = 1; + string tempIndex = 2; +} + +message SmsSendReq{ + BaseReq base = 1; + string param = 2; + string tels = 3; +} + + +message SmsSendRes{ + repeated SmsSendResItem sendList = 1; + message SmsSendResItem{ + string tel = 1; + uint32 isSuccess = 2; + } +} + +message DingdingOASendReq { + BaseReq base = 1; + string originatorUserId = 2; //审批实例发起人的userId + FormComponentValuesView formComponentValues = 3; + message FormComponentValuesView { + string id = 1; + string bizAlias = 2; + string name = 3; + string value = 4; + } + +} + + + +message DingTalkBlackBoardSendReq { + BaseReq base = 1; + BlackboardReceiverView blackboardReceiver = 3; + message BlackboardReceiverView { + repeated int32 deptidList = 1; + repeated string useridList = 2; + } + string title = 4; + string content = 5; +} + + +message wxBizSendReq { + BaseReq base = 1; + string page = 2;//跳转页面 + string touser = 3; //用户open_id + string data = 4; //模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }的object + string miniprogram = 5; //跳转小程序时填写,格式如{ "appid": ,"pagepath": { "value": any } } +} + +message callInfoReq { + BaseReq base = 1; + string calledNumber = 3; //接收语音通知的被叫号码 + int32 playTimes = 4; //播放次数1->3 + int32 volume = 5; //音量1->100 + int32 speed = 6; //语速,-500->500 + string outId = 8; //预留给调用方的 ID,最终会通过回执消息将此 ID 带回给调用方。 +} + +message callCaptchaReq { + BaseReq base = 1; + string calledNumber = 3; //接收语音通知的被叫号码 + string ttsParam = 4; //{"AckNum":"123456"} + int32 playTimes = 5; //播放次数1->3 + int32 volume = 6; //音量1->100 + int32 speed = 7; //语速,-500->500 + string outId = 8; //预留给调用方的 ID,最终会通过回执消息将此 ID 带回给调用方。 +} + +// DingTalk OA 审批创建 +message DingOACreateReq { + BaseReq base = 1; + string outTradeNo = 2; //下游流水号 + string originatorUserId = 3; //审批实例发起人的userId + + repeated FormComponentValuesView formComponentValues = 5; //审批表单的参数 + message FormComponentValuesView { + string name = 1; //表单参数的名称 + string value = 2; //表单参数的值 + string ComponentType = 3; //表单参数的类型 + string extValue = 5; //表单扩展值 + string Id = 4; //控件的id + } + Finance finance = 6; //财务组件 + message Finance { + string checkUserId = 1; //审核人 + int64 sync =2;//是否是异步,如果为异步,付款申请和业务申请是同时进行 + string amount = 3; //金额 + string systemName = 13; //业务系统名称 + string goodsInfo = 4; //商品信息 + string paymentAccount=5; //付款方账户 + string taxAmount=6 ;//税率 + string callbackUrl=7; //回调地址 + string orderNo=8; //订单号 + string remark=9; //备注 + string purpose=12;//用途 + string subjectName=11; //户名 + BankInfo bankInfo=10; + message BankInfo { + string resellerName = 1; //供应商名称 + string bankName = 2; //银行名称 + string accountBankName = 3; //开户行名称 + string subjectName = 4; //户名 + string bankAccount = 5; //银行卡号 + } + } +} + +message DingOACreateResp { + bool isSuccess = 1; + string Msg = 2; + string instanceId = 3; // 审批实例的id +} + +// DingTalk OA 审批实例修改 +message DingOAUpdateReq { + BaseReq base = 1; + string outTradeNo = 2; //下游流水号 + repeated FormComponentValuesView formComponentValues = 5; //审批表单的参数 + message FormComponentValuesView { + string name = 1; //表单参数的名称 + string value = 2; //表单参数的值 + string ComponentType = 3; //表单参数的类型 + string extValue = 5; //表单扩展值 + string Id = 4; //控件的id + } +} + +message DingOAUpdateResp { + bool isSuccess = 1; + string Msg = 2; + string instanceId = 3; // 审批实例的id +} + +// DingTalk OA 审批实例修改 +message DingOACommentReq { + BaseReq base = 1; + string outTradeNo = 2; //下游流水号 + string text = 3; //评论的内容 + string commentUserId = 4; //评论的内容 + File file = 5; //文件 + message File{ //文件名 + repeated string photos = 1; //图片URL地址 + repeated Attachments attachments = 2; //图片URL地址 + message Attachments{ //文件名 + string spaceId =1; //钉盘空间ID。 + string fileSize =2; //文件大小。 + string fileId =3; //文件ID。 + string fileName =4; //文件名称。 + string fileType =5; //文件类型。 + } + + } + +} + +message DingOACommentResp { + bool isSuccess = 1; + string Msg = 2; + string instanceId = 3; // 审批实例的id +} + + + + +message DingOAGetReq { + BaseReq base = 1; + string processInstanceId = 2; //审批实例的id + string outTradeNo = 3; //下游流水号 +} + +message DingOAGetResp { + string OutTradeNo = 1; //下游流水号 + string processInstanceId = 2; //审批实例的id + string Title = 3; //审批实例的标题 + int64 Result = 4; //审批结果 0 进行中 1:同意 2:拒绝 + string Type = 5; //审批任务状态变更类型 + string Remarks = 6; //审批备注 + string CreateTime = 7; //审批任务创建时间 + string UpdateTime = 8; //审批任务更新时间 +} + +message PaymentCallbackData { + string sign=1; + string instanceId=2; + string corpId=3; + string failReason =4; + string paymentTime=5; //付款时间 + string userId=6; + string paymentStatus=7; + +} + +message PaymentCallbackRes { + bool isSuccess=1; +} + + +message HsSmsSendReq{ + BaseReq base = 1; + string param = 2; + string tels = 3; +} + + +message HsSmsSendRes{ + repeated SmsSendResItem sendList = 1; + message SmsSendResItem{ + string tel = 1; + uint32 isSuccess = 2; + } +} + +service Msg { + rpc oauth(OauthReq) returns(OauthResp); + rpc centerSend(CenterSendReq) returns(MsgCenterResp); + rpc smsSend(SmsSendReq) returns(SmsSendRes); + rpc dingTalkBlackBoardSend(DingTalkBlackBoardSendReq) returns(BaseRes); + rpc wxBizSend(wxBizSendReq) returns(BaseRes); + rpc callInfo(callInfoReq) returns(BaseRes); + rpc callCaptcha(callCaptchaReq) returns(BaseRes); + rpc dingOACreate(DingOACreateReq) returns(DingOACreateResp); + rpc dingOAUpdate(DingOAUpdateReq) returns(DingOAUpdateResp); + rpc dingOAComment(DingOACommentReq) returns(DingOACommentResp); + rpc dingOAGet(DingOAGetReq) returns(DingOAGetResp); + rpc FinanceNotify(PaymentCallbackData) returns(PaymentCallbackRes); + rpc hsSmsSend(HsSmsSendReq) returns(HsSmsSendRes); +} diff --git a/protoc/rpc.go b/protoc/rpc.go new file mode 100644 index 0000000..9e58d44 --- /dev/null +++ b/protoc/rpc.go @@ -0,0 +1,21 @@ +package protoc + +import ( + "gitea.cdlsxd.cn/self-tools/l_msg_api/protoc/msg" + "google.golang.org/grpc" + "log" +) + +var rpcMsgClient msg.MsgClient + +func InstanceMsgClient(host string) msg.MsgClient { + if rpcMsgClient != nil { + return rpcMsgClient + } + conn, err := grpc.NewClient(host) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + rpcMsgClient = msg.NewMsgClient(conn) + return rpcMsgClient +}