diff --git a/.gitignore b/.gitignore index e2f1f53..a6b542a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ # vendor/ .idea .vscode +*_test.go diff --git a/go.mod b/go.mod index bd4f0b4..61d84f0 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.15 require ( github.com/buger/jsonparser v1.1.1 + github.com/ddliu/go-httpclient v0.6.9 github.com/gin-gonic/gin v1.7.2 github.com/go-redis/redis/v8 v8.3.4 github.com/go-redis/redis_rate/v9 v9.1.1 @@ -16,6 +17,8 @@ require ( github.com/stretchr/testify v1.7.0 github.com/tidwall/gjson v1.8.1 go.uber.org/zap v1.17.0 + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/yaml.v2 v2.3.0 gorm.io/driver/mysql v1.0.0 gorm.io/gorm v1.21.12 diff --git a/go.sum b/go.sum index 49b3a00..bdd7efa 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ddliu/go-httpclient v0.6.9 h1:/3hsBVpcgCJwqm1dkVlnAJ9NWuYInbRc+i9FyUXX/LE= +github.com/ddliu/go-httpclient v0.6.9/go.mod h1:zM9P0OxV4OGGz1pt/ibuj0ooX2SWH9a6MvXZLbT0JMc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -142,9 +144,13 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/mail/code_tpl.html b/mail/code_tpl.html new file mode 100644 index 0000000..91549d0 --- /dev/null +++ b/mail/code_tpl.html @@ -0,0 +1,126 @@ + + +
+
+
+
+
+
+
+ 尊敬的用户:您好!
+
+ 您正在进行{operate_description}操作,请在验证码输入框中输入:{operate_code},以完成操作。
+
+
+
+
+
+
+ 注意:此操作可能会修改您的密码、登录邮箱或绑定手机。如非本人操作,请及时登录并修改密码以保证帐户安全
+
+
+
+
+ 此为系统邮件,请勿回复 白茶清欢的个人网站 + |
+
m.SetHeader("From",mailConn["user"])
读者可以自行实验下效果
+ //m.SetHeader("From", mailConn["user"])
+ message.SetHeader("To", mailTo...) //发送给多个用户
+ message.SetHeader("Subject", subject) //设置邮件主题
+ message.SetBody("text/html", body) //设置邮件正文
+
+ d := gomail.NewDialer(m.conf["host"], m.port, m.conf["user"], m.conf["pass"])
+
+ return d.DialAndSend(message)
+}
+
+// TplSend 使用模版发送邮件
+//
+// Author : go_developer@163.com<白茶清欢>
+//
+// Date : 1:05 下午 2021/8/14
+func (m *mail) TplSend(mailTo []string, subject string, tpl string, bindData map[string]string) error {
+ var (
+ tplContent []byte
+ err error
+ )
+ if tplContent, err = util.ReadFileContent(tpl); nil != err {
+ return err
+ }
+ mailBody := string(tplContent)
+ for k, v := range bindData {
+ mailBody = strings.ReplaceAll(mailBody, "{"+k+"}", v)
+ }
+ return m.BaseSend(mailTo, subject, mailBody)
+}
diff --git a/request/curl.go b/request/curl.go
index 0fb0fc2..650ab23 100644
--- a/request/curl.go
+++ b/request/curl.go
@@ -7,6 +7,131 @@
// Date : 2021-08-01 8:47 下午
package request
-func Send(apiMethod *APIMethod) {
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "time"
+ "github.com/tidwall/gjson"
+
+ "github.com/ddliu/go-httpclient"
+)
+
+// Send 发送HTTP请求
+//
+// Author : go_developer@163.com<白茶清欢>
+//
+// Date : 9:38 下午 2021/8/11
+func Send(apiMethod *APIMethod) *APIResponse {
+ if apiMethod.Parameter == nil {
+ apiMethod.Parameter = make(map[string]interface{})
+ }
+ if apiMethod.Header == nil {
+ apiMethod.Header = make(map[string]string)
+ }
+ return nil
+}
+
+// request 发送请求
+//
+// Author : go_developer@163.com<白茶清欢>
+//
+// Date : 2:27 下午 2021/8/12
+func request(apiMethod *APIMethod, responseConfig *ResponseConfig) (*APIResponse, bool) {
+ apiResponse := &APIResponse{
+ Code: "",
+ Message: "",
+ Data: "",
+ Success: false,
+ TraceID: "",
+ Cost: 0,
+ RealCost: 0,
+ TotalCost: 0,
+ TotalRealCost: 0,
+ ErrorList: make([]error, 0),
+ StartRequestTime: time.Now().Unix(),
+ FinishRequestTime: 0,
+ }
+ defer func(apiResponse *APIResponse) {
+ apiResponse.FinishRequestTime = time.Now().Unix()
+ apiResponse.RealCost = apiResponse.FinishRequestTime - apiResponse.StartRequestTime
+ }(apiResponse)
+ var (
+ client *httpclient.HttpClient
+ response *httpclient.Response
+ err error
+ responseData []byte
+ )
+ client = httpclient.NewHttpClient()
+ inputByteData, _ := json.Marshal(apiMethod.Parameter)
+ requestData := bytes.NewReader(inputByteData)
+ if response, err = client.Do(apiMethod.Method, apiMethod.ServiceDomain+apiMethod.URI, apiMethod.Header, requestData); nil != err {
+ apiResponse.Success = false
+ apiResponse.ErrorList = append(apiResponse.ErrorList, err)
+ return apiResponse, false
+ }
+ if responseData, err = ioutil.ReadAll(response.Body); nil != err {
+ apiResponse.Success = false
+ apiResponse.ErrorList = append(apiResponse.ErrorList, err)
+ return apiResponse, false
+ }
+ orgData := string(responseData)
+ apiResponse.Data = gjson.Get(orgData, responseConfig.DataKey).String()
+ apiResponse.Code = fmt.Sprintf("%v", gjson.Get(orgData, responseConfig.CodeKey).Value())
+ apiResponse.Message = gjson.Get(orgData, responseConfig.MessageKey).String()
+ apiResponse.Cost = gjson.Get(orgData, responseConfig.CostKey).Int()
+ apiResponse.Success = isSuccessCode(apiResponse.Code, responseConfig.SuccessCode)
+
+ if apiResponse.Success {
+ return apiResponse, false
+ }
+
+ // 非成功, 判断是否需要重试
+ return apiResponse, isNeedRetry(apiMethod, ResponseErrorTypeBusinessCode)
+}
+
+// isNeedRetry 是否需要重试
+//
+// Author : go_developer@163.com<白茶清欢>
+//
+// Date : 3:01 下午 2021/8/12
+func isNeedRetry(apiMethod *APIMethod, errType string) bool {
+ return false
+}
+
+// isSuccessCode 是否为业务处理成功状态码
+//
+// Author : go_developer@163.com<白茶清欢>
+//
+// Date : 3:43 下午 2021/8/13
+func isSuccessCode(responseCode string, successResponseCodeList []string) bool {
+ isSuccess := false
+ for _, itemCode := range successResponseCodeList {
+ if responseCode == itemCode {
+ isSuccess = true
+ break
+ }
+ }
+ return isSuccess
+}
+
+// buildResponseData 构建响应结果
+//
+// Author : go_developer@163.com<白茶清欢>
+//
+// Date : 2:57 下午 2021/8/12
+func buildResponseData(response *httpclient.Response) *APIResponse {
+ return nil
+}
+
+// buildRequestHeader 构建请求header
+//
+// Author : go_developer@163.com<白茶清欢>
+//
+// Date : 9:41 下午 2021/8/11
+func buildRequestHeader(apiMethod *APIMethod) map[string]string {
+ header := make(map[string]string)
+ return header
}
diff --git a/request/define.go b/request/define.go
index 0493c8d..54dc013 100644
--- a/request/define.go
+++ b/request/define.go
@@ -13,12 +13,12 @@ package request
//
// Date : 8:29 下午 2021/8/1
type APIMethod struct {
- ServiceDomain string `json:"service_domain"` // 调用服务的域名
- URI string `json:"uri"` // 调用的URI
- Method string `json:"method"` // 请求方法
- ISRestfulURI bool `json:"is_restful_uri"` // 是否为restful uri
- Header map[string]string `json:"header"` // 请求header
-
+ ServiceDomain string `json:"service_domain"` // 调用服务的域名
+ URI string `json:"uri"` // 调用的URI
+ Method string `json:"method"` // 请求方法
+ ISRestfulURI bool `json:"is_restful_uri"` // 是否为restful uri
+ Header map[string]string `json:"header"` // 请求header
+ Parameter map[string]interface{} `json:"parameter"` // 请求参数
}
// ResponseConfig ...
@@ -27,12 +27,18 @@ type APIMethod struct {
//
// Date : 8:32 下午 2021/8/1
type ResponseConfig struct {
- ISJson bool `json:"is_json"` // 响应数据是否为json
- CodeKey string `json:"code_key"` // 代表业务Code的Key
- MessageKey string `json:"message_key"` // 描述业务code的key
- DataKey string `json:"data_key"` // 返回数据的key
- SuccessRule string `json:"success_rule"` // 请求成功的规则, http_code / business_code http状态码或者业务状态码, 如果是通过http code 判断是否为请求成功 code key / message key 配置无效
- SuccessCode []string `json:"success_code"` // 哪些状态码被认为是请求成功
+ RetryRule int `json:"retry_rule"` // 重试规则 0 - http error 1 - business error 2 - both 0 && 1
+ RetryCnt int `json:"retry_cnt"` // 重试次数(重试次数 1, 最多请求两次)
+ RetryInterval int `json:"retry_interval"` // 重试的时间间隔, 单位ms, 为 0 立即重试, > 0 休眠指定ms后重试
+ ISJson bool `json:"is_json"` // 响应数据是否为json
+ CodeKey string `json:"code_key"` // 代表业务Code的Key
+ MessageKey string `json:"message_key"` // 描述业务code的key
+ TraceKey string `json:"trace_key"` // 接口返回的trace追踪字段key
+ DataKey string `json:"data_key"` // 返回数据的key
+ CostKey string `json:"cost_key"` // 代表接口耗时的key
+ SuccessRule string `json:"success_rule"` // 请求成功的规则, http_code / business_code http状态码或者业务状态码, 如果是通过http code 判断是否为请求成功 code key / message key 配置无效
+ SuccessCode []string `json:"success_code"` // 哪些状态码被认为是请求成功
+ SuccessHttpCode []string `json:"success_http_code"` // 被认为是成功的http code
}
// APIResponse API响应数据
@@ -41,8 +47,34 @@ type ResponseConfig struct {
//
// Date : 8:54 下午 2021/8/1
type APIResponse struct {
- Data string `json:"data"` // 响应数据
- Cost int64 `json:"cost"` // 接口耗时
- StartRequestTime int64 `json:"start_request_time"` // 开始请求时间
- FinishRequestTime int64 `json:"finish_request_time"` // 完成请求时间
+ Code string `json:"code"` // 响应状态码
+ Message string `json:"message"` // 响应状态码的描述
+ Data string `json:"data"` // 响应数据
+ Success bool `json:"success"` // 请求是否成功
+ TraceID string `json:"trace_id"` // 日志追踪traceID
+ Cost int64 `json:"cost"` // 接口耗时(第三方接口返回)
+ RealCost int64 `json:"real_cost"` // 内部从发起请求,到拿到结果的耗时
+ TotalCost int64 `json:"total_cost"` // 累计请求加一起耗时
+ TotalRealCost int64 `json:"total_real_cost"` // 累计真实耗时
+ ErrorList []error `json:"error_list"` // 重试过程中累计出现过的异常
+ StartRequestTime int64 `json:"start_request_time"` // 开始请求时间
+ FinishRequestTime int64 `json:"finish_request_time"` // 完成请求时间
}
+
+const (
+ // ResponseErrorTypeInternal 内部异常,请求没发出去
+ ResponseErrorTypeInternal = "internal"
+ // ResponseErrorTypeHttpStatus http 状态码异常
+ ResponseErrorTypeHttpStatus = "http"
+ // ResponseErrorTypeBusinessCode 业务状态码异常
+ ResponseErrorTypeBusinessCode = "business"
+)
+
+const (
+ // RetryRuleOnlyHttpError 仅 http 异常重试
+ RetryRuleOnlyHttpError = 0
+ // RetryRuleOnlyBusinessError 仅业务状态码异常重试
+ RetryRuleOnlyBusinessError = 1
+ // RetryRuleMixed 任意一种异常出现,均重试
+ RetryRuleMixed = 2
+)