Commit 5e00b3ee authored by shajiaiming's avatar shajiaiming

Merge branch 'feature/bankcard' into 'develop'

Feature/bankcard See merge request !2
parents 922e46eb 37425a2d
...@@ -3,34 +3,30 @@ module slg ...@@ -3,34 +3,30 @@ module slg
go 1.16 go 1.16
require ( require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.7.7 github.com/gin-gonic/gin v1.7.7
github.com/go-ini/ini v1.66.4 github.com/go-ini/ini v1.66.4
github.com/go-playground/validator/v10 v10.10.1 // indirect github.com/go-playground/validator/v10 v10.10.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/golang/protobuf v1.5.2 // indirect github.com/go-stack/stack v1.8.0
github.com/gomodule/redigo v1.8.8 github.com/gomodule/redigo v1.8.8
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jinzhu/gorm v1.9.16 github.com/jinzhu/gorm v1.9.16
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/koding/logging v0.0.0-20160720134017-8b5a689ed69b github.com/koding/logging v0.0.0-20160720134017-8b5a689ed69b
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/pkg/errors v0.9.1
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/robfig/cron v1.2.0 github.com/robfig/cron v1.2.0
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/shopspring/decimal v1.3.1 github.com/shopspring/decimal v1.3.1
github.com/streadway/amqp v1.0.0 github.com/streadway/amqp v1.0.0
github.com/stretchr/testify v1.7.0 // indirect github.com/tal-tech/go-zero v1.1.10
github.com/ugorji/go v1.2.7 // indirect github.com/ugorji/go v1.2.7 // indirect
github.com/xinliangnote/go-util v0.0.0-20210703052933-7f9f6d961276 github.com/xinliangnote/go-util v0.0.0-20210703052933-7f9f6d961276
gitlab.33.cn/proof/backend-micro v1.0.11
gitlab.33.cn/utils/go-kit v1.0.8
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 gopkg.in/go-playground/validator.v9 v9.31.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"slg/models" "slg/models"
//"slg/pkg/cron"
"slg/pkg/gredis" "slg/pkg/gredis"
"slg/pkg/setting" "slg/pkg/setting"
"slg/pkg/util" "slg/pkg/util"
......
package auth
import (
"github.com/gin-gonic/gin"
"slg/pkg/e"
"slg/models"
"slg/pkg/handler"
"slg/pkg/errno"
)
func AUTH() gin.HandlerFunc {
return func(c *gin.Context) {
var code int
code = e.SUCCESS
token := c.Request.Header.Get("Token")
if token == "" {
code = e.INVALID_PARAMS
} else {
_, err := models.CheckToken(token)
if err != nil {
code = e.ERROR_AUTH_CHECK_TOKEN_FAIL
}
}
if code != e.SUCCESS {
handler.SendResponse(c, errno.PermissionDenied, nil)
c.Abort()
return
}
c.Next()
}
}
...@@ -43,6 +43,16 @@ func Exist(oid string) (*Bonus, error) { ...@@ -43,6 +43,16 @@ func Exist(oid string) (*Bonus, error) {
* @return * @return
*/ */
func AddBonus(data map[string]interface{}) error { func AddBonus(data map[string]interface{}) error {
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
bonus := Bonus{ bonus := Bonus{
Oid: data["oid"].(string), Oid: data["oid"].(string),
MerchantId: data["merchant_id"].(string), MerchantId: data["merchant_id"].(string),
...@@ -59,17 +69,6 @@ func AddBonus(data map[string]interface{}) error { ...@@ -59,17 +69,6 @@ func AddBonus(data map[string]interface{}) error {
UpdateTime: time.Now().UTC().Unix(), UpdateTime: time.Now().UTC().Unix(),
} }
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
if err := tx.Debug().Create(&bonus).Error; err != nil { if err := tx.Debug().Create(&bonus).Error; err != nil {
tx.Rollback() tx.Rollback()
return err return err
...@@ -77,11 +76,12 @@ func AddBonus(data map[string]interface{}) error { ...@@ -77,11 +76,12 @@ func AddBonus(data map[string]interface{}) error {
if data["invite_uid"] != "" { if data["invite_uid"] != "" {
bonusDetail := BonusDetail{ bonusDetail := BonusDetail{
Uid: data["invite_uid"].(string), Uid: data["invite_uid"].(string),
BonusAmount: data["invite_bonus_amount"].(string), BonusAmount: data["invite_bonus_amount"].(string),
BonusType: Invite, BonusProportion: data["invite_proportion"].(string),
CreateTime: time.Now().UTC().Unix(), BonusType: Invite,
UpdateTime: time.Now().UTC().Unix(), CreateTime: time.Now().UTC().Unix(),
UpdateTime: time.Now().UTC().Unix(),
} }
if err := tx.Debug().Table("bonus_detail").Create(&bonusDetail).Error; err != nil { if err := tx.Debug().Table("bonus_detail").Create(&bonusDetail).Error; err != nil {
tx.Rollback() tx.Rollback()
...@@ -119,11 +119,12 @@ func AddBonus(data map[string]interface{}) error { ...@@ -119,11 +119,12 @@ func AddBonus(data map[string]interface{}) error {
if data["share_uid"] != "" { if data["share_uid"] != "" {
bonusDetail := BonusDetail{ bonusDetail := BonusDetail{
Uid: data["share_uid"].(string), Uid: data["share_uid"].(string),
BonusAmount: data["share_bonus_amount"].(string), BonusAmount: data["share_bonus_amount"].(string),
BonusType: Share, BonusProportion: data["share_proportion"].(string),
CreateTime: time.Now().UTC().Unix(), BonusType: Share,
UpdateTime: time.Now().UTC().Unix(), CreateTime: time.Now().UTC().Unix(),
UpdateTime: time.Now().UTC().Unix(),
} }
if err := tx.Debug().Table("bonus_detail").Create(&bonusDetail).Error; err != nil { if err := tx.Debug().Table("bonus_detail").Create(&bonusDetail).Error; err != nil {
tx.Rollback() tx.Rollback()
......
...@@ -3,23 +3,27 @@ package models ...@@ -3,23 +3,27 @@ package models
import "github.com/jinzhu/gorm" import "github.com/jinzhu/gorm"
const Invite = 1 //邀请 const Invite = 1 //邀请
const Share = 2 //分享 const Share = 2 //分享
const Redraw = 3 //提取 const Redraw = 3 //提取
type BonusDetail struct { type BonusDetail struct {
Model Model
Uid string `json:"uid"` Uid string `json:"uid"`
BonusAmount string `json:"bonus_amount"` BonusAmount string `json:"bonus_amount"`
BonusType uint8 `json:"bonus_type"` BonusType uint8 `json:"bonus_type"`
CreateTime int64 `json:"create_time"` BonusProportion string `json:"bonus_proportion"`
UpdateTime int64 `json:"update_time"` CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
} }
type BonusDetailResp struct { type BonusDetailResp struct {
BonusAmount string `json:"bonus_amount"` Describe string `json:"describe"`
BonusType uint8 `json:"bonus_type"` BonusProportion string `json:"bonus_proportion"`
CreateTime string `json:"create_time"` BonusAmount string `json:"bonus_amount"`
BonusType uint8 `json:"bonus_type"`
Remart string `json:"remart"`
CreateTime string `json:"create_time"`
} }
func GetBonusDetailCount(conditions interface{}) (int, error) { func GetBonusDetailCount(conditions interface{}) (int, error) {
......
...@@ -40,3 +40,17 @@ func GetUserInfos(conditions interface{}) ([]*User, error) { ...@@ -40,3 +40,17 @@ func GetUserInfos(conditions interface{}) ([]*User, error) {
return users, nil return users, nil
} }
/**
* 通过uid获取用户信息
* @return
*/
func CheckToken(token string) (bool, error) {
var user User
err := db.Select("uid").Where("token = ?", token).First(&user).Error
if err != nil {
return false, err
}
return true, nil
}
package models
import "github.com/jinzhu/gorm"
type UserBankCard struct {
Model
Uid string `json:"uid"`
BankCard string `json:"bank_card"`
IdCard string `json:"id_card"`
Mobile string `json:"mobile"`
Name string `json:"name"`
IsFirst int `json:"is_first"`
}
/**
* 通过uid获取用户绑定银行卡信息
* @uid id
* @return
*/
func GetUserBankCardInfo(uid string) (*UserBankCard, error) {
var userBankCard UserBankCard
err := db.Debug().Where("uid = ?", uid).First(&userBankCard).Error
if err != nil {
return nil, err
}
return &userBankCard, nil
}
/**
* 绑定银行卡信息
* @return
*/
func AddUserBankCard(data map[string]interface{}) error {
userBankCard := UserBankCard{
Uid: data["uid"].(string),
BankCard: data["bank_card"].(string),
IdCard: data["id_card"].(string),
Mobile: data["mobile"].(string),
Name: data["name"].(string),
IsFirst: data["is_first"].(int),
}
if err := db.Create(&userBankCard).Error; err != nil {
return err
}
return nil
}
/**
* 更新银行卡信息
* @param
* @return
*/
func UpdateUserBankCard(uid string, data interface{}) error {
if err := db.Model(&UserBankCard{}).Where("uid = ?", uid).Updates(data).Error; err != nil {
return err
}
return nil
}
/**
* 判断用户是否已绑定银行卡F
* @param
* @return
*/
func ExistBankCard(uid string) (bool, error) {
var userBankCard UserBankCard
err := db.Select("id").Where("uid = ?", uid).First(&userBankCard).Error
if err != nil && err != gorm.ErrRecordNotFound {
return false, err
}
if userBankCard.ID > 0 {
return true, nil
}
return false, nil
}
package bankcard4c
import (
"net/http"
"net/url"
"github.com/pkg/errors"
"slg/pkg/sign"
"slg/pkg/xhttp"
)
// 阿里云银行卡四要素认证API:https://market.aliyun.com/products/57000002/cmapi033467.html
const (
// CodeSuccess 认证成功状态码
CodeSuccess = 200
// CodeFailure 认证失败状态码
CodeFailure = -1
// MsgSuccess 认证成功消息
MsgSuccess = "认证信息匹配"
// MsgFailure 认证失败消息
MsgFailure = "认证失败,请稍后再试"
)
// Config 银行卡四要素认证相关配置
type Config struct {
IsMock bool // 是否模拟通过
Url string // 接口地址
AppKey string // 应用key
AppKeySecret string // 应用密钥
}
// BankCard4C 银行卡四要素认证器结构详情
type BankCard4C struct {
c *Config
client *xhttp.Client
}
// NewBankCard4C 新建银行卡四要素认证器
func NewBankCard4C(c *Config) (*BankCard4C, error) {
if c == nil {
return nil, errors.New("bankcard4c: illegal bankcard4c configure")
}
if !c.IsMock {
if c.Url == "" || c.AppKey == "" || c.AppKeySecret == "" {
return nil, errors.New("bankcard4c: illegal bankcard4c configure")
}
}
return &BankCard4C{c: c, client: xhttp.NewDefaultClient()}, nil
}
// MustNewBankCard4C 新建银行卡四要素认证器
func MustNewBankCard4C(c *Config) *BankCard4C {
b, err := NewBankCard4C(c)
if err != nil {
panic(err)
}
return b
}
// AuthenticateReq 银行卡四要素认证请求
type AuthenticateReq struct {
BankCard string // 银行卡卡号
IdCard string // 身份证号
Mobile string // 手机号
Name string // 姓名
}
// AuthenticateResp 银行卡四要素认证响应
type AuthenticateResp struct {
Code int // 状态码
Msg string // 消息
}
// Authenticate 银行卡四要素认证
func (b *BankCard4C) Authenticate(req *AuthenticateReq) (*AuthenticateResp, error) {
if b.c.IsMock {
return &AuthenticateResp{Code: CodeSuccess, Msg: MsgSuccess}, nil
}
rawurl := b.c.Url
values := make(url.Values)
values.Set("bankcard", req.BankCard)
values.Set("idcard", req.IdCard)
values.Set("mobile", req.Mobile)
values.Set("name", req.Name)
rawurl += "?" + values.Encode()
request, err := http.NewRequest(http.MethodGet, rawurl, nil)
if err != nil {
return nil, errors.WithMessage(err, "new http request err")
}
err = sign.Sign(request, b.c.AppKey, b.c.AppKeySecret)
if err != nil {
return nil, errors.WithMessage(err, "sign request err")
}
var resp apiResp
response, err := b.client.CallWithRequest(request, &resp)
if err != nil {
return nil, errors.WithMessage(err, "client call with request err")
}
if resp.Code == codeSuccess && resp.Data.Result != nil && *resp.Data.Result == resultConsistent {
return &AuthenticateResp{Code: CodeSuccess, Msg: MsgSuccess}, nil
}
message := MsgFailure
messages := []string{resp.Data.Desc, resp.Msg, response.Header.Get("X-Ca-Error-Message")}
for _, msg := range messages {
if msg != "" {
message = msg
break
}
}
return &AuthenticateResp{Code: CodeFailure, Msg: message}, nil
}
const (
// codeSuccess 接口请求成功状态码
codeSuccess = 200
// resultConsistent 认证结果:一致
resultConsistent = 0
// resultInconsistent 认证结果:不一致
resultInconsistent = 1
// resultNotAuth 认证结果:未认证
resultNotAuth = 2
// resultCancelled 认证结果:已注销
resultCancelled = 3
)
// apiResp 认证接口响应
type apiResp struct {
Msg string `json:"msg"`
Success bool `json:"success"`
Code int `json:"code"`
Data data `json:"data"`
}
// data 认证接口响应Data
type data struct {
OrderNo string `json:"order_no"`
Result *int `json:"result"`
Msg string `json:"msg"`
Desc string `json:"desc"`
}
...@@ -72,4 +72,12 @@ var ( ...@@ -72,4 +72,12 @@ var (
ErrUpdateBonus = &Errno{Code: 20103, Message: "佣金更新失败."} ErrUpdateBonus = &Errno{Code: 20103, Message: "佣金更新失败."}
ErrDeleteBonus = &Errno{Code: 20104, Message: "佣金删除失败."} ErrDeleteBonus = &Errno{Code: 20104, Message: "佣金删除失败."}
ErrCountBonus = &Errno{Code: 20105, Message: "佣金统计失败."} ErrCountBonus = &Errno{Code: 20105, Message: "佣金统计失败."}
//BankCard errors
ErrBankCardNotFound = &Errno{Code: 30101, Message: "银行卡信息未找到."}
ErrAddBankCard = &Errno{Code: 30102, Message: "银行卡绑定失败."}
ErrUpdateBankCard = &Errno{Code: 30103, Message: "银行卡更新失败."}
ErrAgainBankCard = &Errno{Code: 30104, Message: "已认证用户仅有一次机会换绑银行卡."}
ErrBankCardInfo = &Errno{Code: 30105, Message: "换绑的银行卡必须为用户名下的银行卡."}
ErrBankCardIdentification = &Errno{Code: 30106, Message: "认证失败,请稍后再试."}
) )
MIT License
Copyright (c) 2020 SliverYou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Aliyun api gateway request signature algorithm implemented by go
*[English](README.md) ∙ [简体中文](README_zh-CN.md)*
[![Github License](https://img.shields.io/github/license/sliveryou/aliyun-api-gateway-sign.svg?style=flat)](https://github.com/sliveryou/aliyun-api-gateway-sign/blob/master/LICENSE)
[![Go Doc](https://godoc.org/github.com/sliveryou/aliyun-api-gateway-sign?status.svg)](https://pkg.go.dev/github.com/sliveryou/aliyun-api-gateway-sign)
[![Go Report](https://goreportcard.com/badge/github.com/sliveryou/aliyun-api-gateway-sign)](https://goreportcard.com/report/github.com/sliveryou/aliyun-api-gateway-sign)
[![Github Latest Release](https://img.shields.io/github/release/sliveryou/aliyun-api-gateway-sign.svg?style=flat)](https://github.com/sliveryou/aliyun-api-gateway-sign/releases/latest)
[![Github Latest Tag](https://img.shields.io/github/tag/sliveryou/aliyun-api-gateway-sign.svg?style=flat)](https://github.com/sliveryou/aliyun-api-gateway-sign/tags)
[![Github Stars](https://img.shields.io/github/stars/sliveryou/aliyun-api-gateway-sign.svg?style=flat)](https://github.com/sliveryou/aliyun-api-gateway-sign/stargazers)
For signature methods, see: [client signature documentation](https://help.aliyun.com/document_detail/29475.html)
## Installation
Download package by using:
```sh
$ go get github.com/sliveryou/aliyun-api-gateway-sign
```
## Usage Example
```golang
package main
import (
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"strings"
sign "github.com/sliveryou/aliyun-api-gateway-sign"
)
func main() {
var url string = "https://bankcard4c.shumaidata.com/bankcard4c"
var body string
var appKey, appKeySecret string
// Prepare a HTTP request.
req, err := http.NewRequest(sign.HTTPMethodPost, url, strings.NewReader(body))
if err != nil {
// Handle err.
panic(err)
}
// Set the request with headers.
req.Header.Set(sign.HTTPHeaderAccept, sign.HTTPContentTypeJson)
req.Header.Set(sign.HTTPHeaderContentType, sign.HTTPContentTypeJson)
// Sign the request.
if err := sign.Sign(req, appKey, appKeySecret); err != nil {
panic(err)
}
// Show the dump request.
dumpReq, err := httputil.DumpRequestOut(req, true)
if err != nil {
panic(err)
}
log.Println("\n" + string(dumpReq))
// Do the request.
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Handle response.
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
log.Println("\n"+string(content), resp.StatusCode, resp.Header.Get("X-Ca-Error-Message"))
}
```
# 阿里云 API 网关请求签名算法的 Go 实现
*[English](README.md) ∙ [简体中文](README_zh-CN.md)*
[![Github License](https://img.shields.io/github/license/sliveryou/aliyun-api-gateway-sign.svg?style=flat)](https://github.com/sliveryou/aliyun-api-gateway-sign/blob/master/LICENSE)
[![Go Doc](https://godoc.org/github.com/sliveryou/aliyun-api-gateway-sign?status.svg)](https://pkg.go.dev/github.com/sliveryou/aliyun-api-gateway-sign)
[![Go Report](https://goreportcard.com/badge/github.com/sliveryou/aliyun-api-gateway-sign)](https://goreportcard.com/report/github.com/sliveryou/aliyun-api-gateway-sign)
[![Github Latest Release](https://img.shields.io/github/release/sliveryou/aliyun-api-gateway-sign.svg?style=flat)](https://github.com/sliveryou/aliyun-api-gateway-sign/releases/latest)
[![Github Latest Tag](https://img.shields.io/github/tag/sliveryou/aliyun-api-gateway-sign.svg?style=flat)](https://github.com/sliveryou/aliyun-api-gateway-sign/tags)
[![Github Stars](https://img.shields.io/github/stars/sliveryou/aliyun-api-gateway-sign.svg?style=flat)](https://github.com/sliveryou/aliyun-api-gateway-sign/stargazers)
详细的签名算法,请查看:[客户端签名说明文档](https://help.aliyun.com/document_detail/29475.html)
## 安装
使用如下命令下载并安装包:
```sh
$ go get github.com/sliveryou/aliyun-api-gateway-sign
```
## 使用示例
```golang
package main
import (
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"strings"
sign "github.com/sliveryou/aliyun-api-gateway-sign"
)
func main() {
var url string = "https://bankcard4c.shumaidata.com/bankcard4c"
var body string
var appKey, appKeySecret string
// 先创建一个 HTTP 请求对象
req, err := http.NewRequest(sign.HTTPMethodPost, url, strings.NewReader(body))
if err != nil {
// 错误逻辑处理
panic(err)
}
// 设置请求的请求头
req.Header.Set(sign.HTTPHeaderAccept, sign.HTTPContentTypeJson)
req.Header.Set(sign.HTTPHeaderContentType, sign.HTTPContentTypeJson)
// 对请求使用签名函数进行签名
if err := sign.Sign(req, appKey, appKeySecret); err != nil {
panic(err)
}
// 打印签名后请求的具体内容
dumpReq, err := httputil.DumpRequestOut(req, true)
if err != nil {
panic(err)
}
log.Println("\n" + string(dumpReq))
// 发起请求
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// 处理返回的响应内容
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
log.Println("\n"+string(content), resp.StatusCode, resp.Header.Get("X-Ca-Error-Message"))
}
```
package sign
// HTTP header keys.
const (
HTTPHeaderAccept = "Accept"
HTTPHeaderContentMD5 = "Content-MD5"
HTTPHeaderContentType = "Content-Type"
HTTPHeaderUserAgent = "User-Agent"
HTTPHeaderDate = "Date"
)
// HTTP header keys used for Aliyun API gateway signature.
const (
HTTPHeaderCAPrefix = "X-Ca-"
HTTPHeaderCASignature = "X-Ca-Signature"
HTTPHeaderCATimestamp = "X-Ca-Timestamp"
HTTPHeaderCANonce = "X-Ca-Nonce"
HTTPHeaderCAKey = "X-Ca-Key"
HTTPHeaderCASignatureHeaders = "X-Ca-Signature-Headers"
)
// HTTP header content-type values.
const (
HTTPContentTypeForm = "application/x-www-form-urlencoded"
HTTPContentTypeStream = "application/octet-stream"
HTTPContentTypeJson = "application/json"
HTTPContentTypeXml = "application/xml"
HTTPContentTypeText = "application/text"
)
// HTTP method values.
const (
HTTPMethodGet = "GET"
HTTPMethodPost = "POST"
HTTPMethodPut = "PUT"
HTTPMethodDelete = "DELETE"
HTTPMethodPatch = "PATCH"
HTTPMethodHead = "HEAD"
HTTPMethodOptions = "OPTIONS"
)
// default values.
const (
defaultUserAgent = "Go-Aliyun-Sign-Client"
defaultAccept = "*/*"
defaultLF = "\n"
)
package sign
import (
"bytes"
"context"
"io/ioutil"
"net/http"
"strings"
)
// Sign will sign the request with appKey and appKeySecret.
func Sign(req *http.Request, appKey, appKeySecret string) error {
req.Header.Set(HTTPHeaderCATimestamp, CurrentTimeMillis())
req.Header.Set(HTTPHeaderCANonce, UUID4())
req.Header.Set(HTTPHeaderCAKey, appKey)
if req.Header.Get(HTTPHeaderAccept) == "" {
req.Header.Set(HTTPHeaderAccept, defaultAccept)
}
if req.Header.Get(HTTPHeaderDate) == "" {
req.Header.Set(HTTPHeaderDate, CurrentGMTDate())
}
if req.Header.Get(HTTPHeaderUserAgent) == "" {
req.Header.Set(HTTPHeaderUserAgent, defaultUserAgent)
}
if req.Body != nil && req.Header.Get(HTTPHeaderContentType) == HTTPContentTypeStream {
b, err := ioutil.ReadAll(req.Body)
if err != nil {
return err
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
req.Header.Set(HTTPHeaderContentMD5, MD5(b))
}
stringToSign, err := buildStringToSign(req)
if err != nil {
return err
}
// log.Println("\n" + stringToSign + "\n")
req.Header.Set(HTTPHeaderCASignature, HmacSHA256([]byte(stringToSign), []byte(appKeySecret)))
return nil
}
func buildStringToSign(req *http.Request) (string, error) {
s := ""
s += strings.ToUpper(req.Method) + defaultLF
s += req.Header.Get(HTTPHeaderAccept) + defaultLF
s += req.Header.Get(HTTPHeaderContentMD5) + defaultLF
s += req.Header.Get(HTTPHeaderContentType) + defaultLF
s += req.Header.Get(HTTPHeaderDate) + defaultLF
s += buildHeaderStringToSign(req)
paramStr, err := buildParamStringToSign(req)
if err != nil {
return "", err
}
s += paramStr
return s, nil
}
func buildHeaderStringToSign(req *http.Request) string {
headerKeys := getSortKeys(req.Header)
headerCAs := make([]string, 0)
headerCAKeys := make([]string, 0)
for _, key := range headerKeys {
if strings.HasPrefix(http.CanonicalHeaderKey(key), HTTPHeaderCAPrefix) {
headerCAKeys = append(headerCAKeys, key)
headerCAs = append(headerCAs, key+":"+req.Header.Get(key)+defaultLF)
}
}
req.Header.Set(HTTPHeaderCASignatureHeaders, strings.Join(headerCAKeys, ","))
return strings.Join(headerCAs, "")
}
func buildParamStringToSign(req *http.Request) (string, error) {
var err error
reqClone := req.Clone(context.Background())
if req.Body != nil {
reqClone.Body, err = req.GetBody()
if err != nil {
return "", err
}
}
err = reqClone.ParseForm()
if err != nil {
return "", err
}
paramKeys := getSortKeys(reqClone.Form)
paramList := make([]string, 0)
for _, key := range paramKeys {
value := reqClone.Form.Get(key)
if value == "" {
paramList = append(paramList, key)
} else {
paramList = append(paramList, key+"="+value)
}
}
params := strings.Join(paramList, "&")
if params != "" {
params = "?" + params
}
return req.URL.Path + params, nil
}
package sign
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"net/http"
"sort"
"strconv"
"time"
"gitlab.33.cn/utils/go-kit/id-generator/uuid"
)
func getSortKeys(m map[string][]string) []string {
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
// CurrentTimeMillis returns the millisecond representation of the current time.
func CurrentTimeMillis() string {
t := time.Now().UnixNano() / 1000000
return strconv.FormatInt(t, 10)
}
// CurrentGMTDate returns the GMT date representation of the current time.
func CurrentGMTDate() string {
return time.Now().UTC().Format(http.TimeFormat)
}
// UUID4 returns random generated UUID string.
func UUID4() string {
return uuid.NextV4()
}
// HmacSHA256 returns the string encrypted with HmacSHA256 method.
func HmacSHA256(b, key []byte) string {
h := hmac.New(sha256.New, key)
h.Write(b)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
// MD5 returns the string hashed with MD5 method.
func MD5(b []byte) string {
m := md5.New()
m.Write(b)
return base64.StdEncoding.EncodeToString(m.Sum(nil))
}
package xhttp
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net"
"net/http"
"time"
"github.com/pkg/errors"
)
// Config HTTP客户端相关配置
type Config struct {
HTTPTimeout time.Duration // HTTP请求超时时间
DialTimeout time.Duration // 拨号超时时间
DialKeepAlive time.Duration // 拨号保持连接时间
MaxIdleConns int // 最大空闲连接数
MaxIdleConnsPerHost int // 每个主机最大空闲连接数
MaxConnsPerHost int // 每个主机最大连接数
IdleConnTimeout time.Duration // 空闲连接超时时间
ResponseHeaderTimeout time.Duration // 读取响应头超时时间
ExpectContinueTimeout time.Duration // 期望继续超时时间
TLSHandshakeTimeout time.Duration // TLS握手超时时间
ForceAttemptHTTP2 bool // 允许尝试启用HTTP/2
}
// GetDefaultConfig 获取默认HTTP客户端相关配置
func GetDefaultConfig() *Config {
return &Config{
HTTPTimeout: 20 * time.Second,
DialTimeout: 15 * time.Second,
DialKeepAlive: 30 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
MaxConnsPerHost: 100,
IdleConnTimeout: 60 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 5 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ForceAttemptHTTP2: true,
}
}
// NewHTTPClient 新建HTTP客户端
func NewHTTPClient(c *Config) *http.Client {
if c == nil {
c = GetDefaultConfig()
}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: c.DialTimeout,
KeepAlive: c.DialKeepAlive,
}).DialContext,
MaxIdleConns: c.MaxIdleConns,
MaxIdleConnsPerHost: c.MaxIdleConnsPerHost,
MaxConnsPerHost: c.MaxConnsPerHost,
IdleConnTimeout: c.IdleConnTimeout,
ResponseHeaderTimeout: c.ResponseHeaderTimeout,
ExpectContinueTimeout: c.ExpectContinueTimeout,
TLSHandshakeTimeout: c.TLSHandshakeTimeout,
ForceAttemptHTTP2: c.ForceAttemptHTTP2,
}
client := &http.Client{
Timeout: c.HTTPTimeout,
Transport: tr,
}
return client
}
// NewDefaultHTTPClient 新建默认HTTP客户端
func NewDefaultHTTPClient() *http.Client {
return NewHTTPClient(nil)
}
// Client HTTP拓展客户端结构详情
type Client struct {
*http.Client
}
// NewClient 新建HTTP拓展客户端
func NewClient(c *Config) *Client {
return &Client{Client: NewHTTPClient(c)}
}
// NewDefaultClient 新建默认HTTP拓展客户端
func NewDefaultClient() *Client {
return &Client{Client: NewDefaultHTTPClient()}
}
// NewClientWithHTTPClient 使用HTTP客户端新建HTTP拓展客户端
func NewClientWithHTTPClient(client *http.Client) *Client {
return &Client{Client: client}
}
// GetRequest 获取HTTP请求
func (c *Client) GetRequest(method, rawurl string, header map[string]string, data io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, rawurl, data)
if err != nil {
return nil, errors.WithMessagef(err, "new http request err, method = %v, rawurl = %v, header = %v",
method, rawurl, header)
}
for k, v := range header {
req.Header.Add(k, v)
}
return req, nil
}
// GetResponse 获取HTTP响应及其响应体内容
func (c *Client) GetResponse(req *http.Request) (*http.Response, []byte, error) {
response, err := c.Do(req)
if err != nil {
return nil, nil, errors.WithMessage(err, "http client do request err")
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, nil, errors.WithMessage(err, "read all response body err")
}
return response, body, nil
}
// CallWithRequest 利用HTTP请求进行HTTP调用
func (c *Client) CallWithRequest(req *http.Request, resp interface{}) (*http.Response, error) {
response, body, err := c.GetResponse(req)
if err != nil {
return nil, errors.WithMessage(err, "get response err")
}
if len(body) > 0 {
err = json.Unmarshal(body, resp)
if err != nil {
return nil, errors.WithMessage(err, "json unmarshal response body err")
}
}
return response, nil
}
// Call HTTP调用
func (c *Client) Call(method, rawurl string, header map[string]string, data io.Reader, resp interface{}) error {
req, err := c.GetRequest(method, rawurl, header, data)
if err != nil {
return errors.WithMessage(err, "get request err")
}
_, err = c.CallWithRequest(req, resp)
if err != nil {
return errors.WithMessage(err, "call with request err")
}
return nil
}
// ChainReq 区块链HTTP调用请求
type ChainReq struct {
Id int `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params"`
}
// ChainResp 区块链HTTP调用响应
type ChainResp struct {
Id int `json:"id"`
Result json.RawMessage `json:"result"`
Error string `json:"error"`
}
// CallChain 区块链HTTP调用
func (c *Client) CallChain(method, url string, header map[string]string, params, resp interface{}) error {
cReq := &ChainReq{Method: method, Params: params}
data, err := json.Marshal(cReq)
if err != nil {
return errors.WithMessage(err, "json marshal chain request err")
}
req, err := c.GetRequest(http.MethodPost, url, header, bytes.NewBuffer(data))
if err != nil {
return errors.WithMessage(err, "get request err")
}
cResp := ChainResp{}
_, err = c.CallWithRequest(req, &cResp)
if err != nil {
return errors.WithMessage(err, "call with request err")
}
if cResp.Error != "" {
return errors.Errorf("chain return err: %v", cResp.Error)
}
err = json.Unmarshal(cResp.Result, resp)
if err != nil {
return errors.WithMessage(err, "json unmarshal chain result err")
}
return nil
}
package xhttp
import (
"net/http"
)
const (
// MethodGet 请求方法:GET
MethodGet = http.MethodGet
// MethodHead 请求方法:HEAD
MethodHead = http.MethodHead
// MethodPost 请求方法:POST
MethodPost = http.MethodPost
// MethodPut 请求方法:PUT
MethodPut = http.MethodPut
// MethodPatch 请求方法:PATCH
MethodPatch = http.MethodPatch
// MethodDelete 请求方法:DELETE
MethodDelete = http.MethodDelete
// MethodConnect 请求方法:CONNECT
MethodConnect = http.MethodConnect
// MethodOptions 请求方法:OPTIONS
MethodOptions = http.MethodOptions
// MethodTrace 请求方法:TRACE
MethodTrace = http.MethodTrace
// HeaderAccept 请求头:Accept
HeaderAccept = "Accept"
// HeaderContentType 请求头:Content-Type
HeaderContentType = "Content-Type"
// HeaderDate 请求头:Date
HeaderDate = "Date"
// HeaderUserAgent 请求头:User-Agent
HeaderUserAgent = "User-Agent"
// HeaderAuthorization 请求头:Authorization
HeaderAuthorization = "Authorization"
// HeaderLocation 请求头:Location
HeaderLocation = "Location"
// HeaderContentDisposition 请求头:Content-Disposition
HeaderContentDisposition = "Content-Disposition"
// HeaderGWErrorCode 自定义网关请求头:X-GW-Error-Code
HeaderGWErrorCode = "X-GW-Error-Code"
// HeaderGWErrorMessage 自定义网关请求头:X-GW-Error-Message
HeaderGWErrorMessage = "X-GW-Error-Message"
// ApplicationForm 应用类型:x-www-form-urlencoded
ApplicationForm = "application/x-www-form-urlencoded"
// ApplicationStream 应用类型:octet-stream
ApplicationStream = "application/octet-stream"
// ApplicationJSON 应用类型:json
ApplicationJSON = "application/json"
// ApplicationXML 应用类型:xml
ApplicationXML = "application/xml"
// ApplicationText 应用类型:text
ApplicationText = "application/text"
// ApplicationZip 应用类型:zip
ApplicationZip = "application/zip"
)
package xhttp
import (
"bytes"
"encoding/json"
"net/http"
"reflect"
"github.com/pkg/errors"
)
const (
// jsonrpcVersion 默认 JSON-RPC 默认版本
jsonrpcVersion = "2.0"
)
// RPCClient 通用 JSON-RPC 客户端接口
type RPCClient interface {
// Call 进行 JSON-RPC 调用
Call(method string, params ...interface{}) (*RPCResponse, error)
// CallRaw 基于所给请求体进行 JSON-RPC 调用
CallRaw(request *RPCRequest) (*RPCResponse, error)
// CallFor 进行 JSON-RPC 调用并将响应结果反序列化到所给类型对象中
CallFor(out interface{}, method string, params ...interface{}) error
}
// RPCOption JSON-RPC 客户端可选配置
type RPCOption func(server *rpcClient)
// WithHTTPClient 使用配置的 HTTP 客户端
func WithHTTPClient(hc *http.Client) RPCOption {
return func(c *rpcClient) {
c.httpClient = hc
}
}
// WithCustomHeaders 使用配置的 HTTP 请求头
func WithCustomHeaders(m map[string]string) RPCOption {
return func(c *rpcClient) {
c.customHeaders = make(map[string]string)
for k, v := range m {
c.customHeaders[k] = v
}
}
}
// NewRPCClient 新建通用 JSON-RPC 客户端
func NewRPCClient(endpoint string, opts ...RPCOption) RPCClient {
c := &rpcClient{endpoint: endpoint}
for _, opt := range opts {
opt(c)
}
if c.httpClient == nil {
c.httpClient = NewDefaultHTTPClient()
}
return c
}
// rpcClient 默认 JSON-RPC 客户端
type rpcClient struct {
endpoint string
httpClient *http.Client
customHeaders map[string]string
}
// newRequest 新建 HTTP 请求体
func (c *rpcClient) newRequest(req interface{}) (*http.Request, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, errors.WithMessagef(err, "json marshal %v err", req)
}
// fmt.Println(string(body))
request, err := http.NewRequest(http.MethodPost, c.endpoint, bytes.NewReader(body))
if err != nil {
return nil, errors.WithMessage(err, "new http request err")
}
request.Header.Set(HeaderAccept, ApplicationJSON)
request.Header.Set(HeaderContentType, ApplicationJSON)
for k, v := range c.customHeaders {
request.Header.Set(k, v)
}
return request, nil
}
// doCall 执行 JSON-RPC 调用
func (c *rpcClient) doCall(req *RPCRequest) (*RPCResponse, error) {
httpReq, err := c.newRequest(req)
if err != nil {
return nil, errors.WithMessagef(err, "call %s method on %s err",
req.Method, c.endpoint)
}
httpResp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, errors.WithMessagef(err, "call %s method on %s err",
req.Method, httpReq.URL.String())
}
defer httpResp.Body.Close()
d := json.NewDecoder(httpResp.Body)
d.DisallowUnknownFields()
d.UseNumber()
var rpcResp *RPCResponse
err = d.Decode(&rpcResp)
if err != nil {
return nil, errors.WithMessagef(err, "call %s method on %s status code: %d, decode body err",
req.Method, httpReq.URL.String(), httpResp.StatusCode)
}
if rpcResp == nil {
return nil, errors.WithMessagef(err, "call %s method on %s status code: %d, rpc response missing err.",
req.Method, httpReq.URL.String(), httpResp.StatusCode)
}
return rpcResp, nil
}
// Call 进行 JSON-RPC 调用
func (c *rpcClient) Call(method string, params ...interface{}) (*RPCResponse, error) {
req := &RPCRequest{
Method: method,
Params: Params(params...),
JSONRPC: jsonrpcVersion,
}
return c.doCall(req)
}
// CallRaw 基于所给请求体进行 JSON-RPC 调用
func (c *rpcClient) CallRaw(request *RPCRequest) (*RPCResponse, error) {
return c.doCall(request)
}
// CallFor 进行 JSON-RPC 调用并将响应结果反序列化到所给类型对象中
func (c *rpcClient) CallFor(out interface{}, method string, params ...interface{}) error {
rpcResp, err := c.Call(method, params...)
if err != nil {
return err
}
return rpcResp.ReadToObject(out)
}
// RPCRequest 通用 JSON-RPC 请求体
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params,omitempty"`
}
// NewRPCRequest 新建通用 JSON-RPC 请求体
func NewRPCRequest(method string, params ...interface{}) *RPCRequest {
req := &RPCRequest{
Method: method,
Params: Params(params...),
JSONRPC: jsonrpcVersion,
}
return req
}
// Params 构建请求参数
func Params(params ...interface{}) interface{} {
var ps interface{}
if params != nil {
switch len(params) {
case 0:
case 1:
if param := params[0]; param != nil {
typeOf := reflect.TypeOf(param)
for typeOf != nil && typeOf.Kind() == reflect.Ptr {
typeOf = typeOf.Elem()
}
// array、slice、interface 和 map 不改变其参数方式,其余类型都包装在数组中
if typeOf != nil {
switch typeOf.Kind() {
case reflect.Array:
ps = param
case reflect.Slice:
ps = param
case reflect.Interface:
ps = param
case reflect.Map:
ps = param
default:
ps = params
}
}
} else {
ps = params
}
default:
ps = params
}
}
return ps
}
// RPCResponse 通用 JSON-RPC 响应体
type RPCResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result interface{} `json:"result,omitempty"`
Error interface{} `json:"error,omitempty"`
}
// GetInt64 获取响应结果的 int64 类型值
func (resp *RPCResponse) GetInt64() (int64, error) {
if resp.Error != nil {
return 0, errors.Errorf("%v", resp.Error)
}
val, ok := resp.Result.(json.Number)
if !ok {
return 0, errors.Errorf("parse int64 from %v err", resp.Result)
}
i, err := val.Int64()
if err != nil {
return 0, err
}
return i, nil
}
// GetFloat64 获取响应结果的 float64 类型值
func (resp *RPCResponse) GetFloat64() (float64, error) {
if resp.Error != nil {
return 0, errors.Errorf("%v", resp.Error)
}
val, ok := resp.Result.(json.Number)
if !ok {
return 0, errors.Errorf("parse float64 from %v err", resp.Result)
}
f, err := val.Float64()
if err != nil {
return 0, err
}
return f, nil
}
// GetBool 获取响应结果的 bool 类型值
func (resp *RPCResponse) GetBool() (bool, error) {
if resp.Error != nil {
return false, errors.Errorf("%v", resp.Error)
}
val, ok := resp.Result.(bool)
if !ok {
return false, errors.Errorf("parse bool from %v err", resp.Result)
}
return val, nil
}
// GetString 获取响应结果的 string 类型值
func (resp *RPCResponse) GetString() (string, error) {
if resp.Error != nil {
return "", errors.Errorf("%v", resp.Error)
}
val, ok := resp.Result.(string)
if !ok {
return "", errors.Errorf("parse string from %v err", resp.Result)
}
return val, nil
}
// ReadToObject 将响应结果反序列化到所给类型对象中
func (resp *RPCResponse) ReadToObject(to interface{}) error {
if resp.Error != nil {
return errors.Errorf("%v", resp.Error)
}
from, err := json.Marshal(resp.Result)
if err != nil {
return errors.WithMessagef(err, "json marshal %v err", resp.Result)
}
err = json.Unmarshal(from, to)
if err != nil {
return errors.WithMessagef(err, "json unmarshal %s err", from)
}
return nil
}
package xhttp
import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httputil"
"strings"
"github.com/go-stack/stack"
"github.com/tal-tech/go-zero/core/iox"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/rest/httpx"
"gitlab.33.cn/proof/backend-micro/pkg/xgrpc"
)
// -------------------- CorsMiddleware -------------------- //
// CorsMiddleware 跨域请求处理中间件
type CorsMiddleware struct{}
// NewCorsMiddleware 新建跨域请求处理中间件
func NewCorsMiddleware() *CorsMiddleware {
return &CorsMiddleware{}
}
// Handle 跨域请求处理
func (m *CorsMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
setHeader(w)
// 放行所有 OPTIONS 方法
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
// 处理请求
next(w, r)
}
}
// Handler 跨域请求处理器
func (m *CorsMiddleware) Handler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
setHeader(w)
// 放行所有 OPTIONS 方法
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
} else {
w.WriteHeader(http.StatusNotFound)
}
})
}
// setHeader 设置响应头
func setHeader(w http.ResponseWriter) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, Authorization, AccessToken, Token, X-Health-Secret")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
w.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type, Access-Control-Allow-Origin, Access-Control-Allow-Headers, X-GW-Error-Code, X-GW-Error-Message")
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
// -------------------- RecoverMiddleware -------------------- //
// RecoverMiddleware 恐慌捕获恢复处理中间件
type RecoverMiddleware struct{}
// NewRecoverMiddleware 新建恐慌捕获恢复处理中间件
func NewRecoverMiddleware() *RecoverMiddleware {
return &RecoverMiddleware{}
}
// Handle 恐慌捕获恢复处理
func (m *RecoverMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if cause := recover(); cause != nil {
logx.WithContext(r.Context()).Errorf("%s%v [running]:\n%s", dumpRequest(r), cause, getStacks())
w.WriteHeader(http.StatusInternalServerError)
}
}()
next(w, r)
}
}
// getStacks 获取调用堆栈信息
func getStacks() string {
cs := stack.Trace().TrimBelow(stack.Caller(2)).TrimRuntime()
var b strings.Builder
for _, c := range cs {
s := fmt.Sprintf("%+n\n\t%+v", c, c)
if !strings.Contains(s, "github.com/tal-tech/go-zero") &&
!strings.Contains(s, "net/http") {
b.WriteString(s)
b.WriteString("\n")
}
}
return strings.TrimSpace(b.String())
}
// -------------------- RLogMiddleware -------------------- //
// RLogMiddleware 请求响应日志打印处理中间件
type RLogMiddleware struct{}
// NewRLogMiddleware 新建请求响应日志打印处理中间件
func NewRLogMiddleware() *RLogMiddleware {
return &RLogMiddleware{}
}
// Handle 请求响应日志打印处理
func (m *RLogMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var dup io.ReadCloser
writer := NewDetailLoggedResponseWriter(w, r)
r.Body, dup = iox.DupReadCloser(r.Body)
next(writer, r)
r.Body = dup
logDetails(writer, r)
}
}
// logDetails 请求响应日志详情打印
func logDetails(writer *DetailLoggedResponseWriter, r *http.Request) {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("%d - %s\n=> %s",
writer.Writer.Code, httpx.GetRemoteAddr(r), dumpRequest(r)))
respBuf := writer.Buf.Bytes()
if len(respBuf) > 0 {
buf.WriteString(fmt.Sprintf("<= %s", respBuf))
}
logx.WithContext(r.Context()).Info(buf.String())
}
// dumpRequest 格式化请求样式
func dumpRequest(req *http.Request) string {
var dup io.ReadCloser
req.Body, dup = iox.DupReadCloser(req.Body)
var b bytes.Buffer
var err error
reqURI := req.RequestURI
if reqURI == "" {
reqURI = req.URL.RequestURI()
}
fmt.Fprintf(&b, "%s %s HTTP/%d.%d\n", req.Method,
reqURI, req.ProtoMajor, req.ProtoMinor)
chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
if req.Body != nil {
var n int64
var dest io.Writer = &b
if chunked {
dest = httputil.NewChunkedWriter(dest)
}
n, err = io.Copy(dest, req.Body)
if chunked {
dest.(io.Closer).Close()
}
if n > 0 {
io.WriteString(&b, "\n")
}
}
req.Body = dup
if err != nil {
return err.Error()
}
return b.String()
}
// -------------------- IgnoreRLogMiddleware -------------------- //
// IgnoreRLogMiddleware 忽略 gRPC 请求响应日志打印处理中间件
type IgnoreRLogMiddleware struct{}
// NewIgnoreRLogMiddleware 新建忽略 gRPC 请求响应日志打印处理中间件
func NewIgnoreRLogMiddleware() *IgnoreRLogMiddleware {
return &IgnoreRLogMiddleware{}
}
// Handle 忽略 gRPC 请求响应日志打印处理
func (m *IgnoreRLogMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := xgrpc.IgnoreRLog(r.Context())
next(w, r.WithContext(ctx))
}
}
package xhttp
import (
"bufio"
"bytes"
"errors"
"net"
"net/http"
)
// LoggedResponseWriter 日志记录响应写入器
type LoggedResponseWriter struct {
W http.ResponseWriter
R *http.Request
Code int
}
// NewLoggedResponseWriter 新建日志记录响应写入器
func NewLoggedResponseWriter(w http.ResponseWriter, r *http.Request) *LoggedResponseWriter {
return &LoggedResponseWriter{
W: w,
R: r,
Code: http.StatusOK,
}
}
// Flush 实现Flush方法
func (w *LoggedResponseWriter) Flush() {
if flusher, ok := w.W.(http.Flusher); ok {
flusher.Flush()
}
}
// Header 实现Header方法
func (w *LoggedResponseWriter) Header() http.Header {
return w.W.Header()
}
// Hijack 实现Hijack方法
func (w *LoggedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacked, ok := w.W.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
// Write 实现Write方法
func (w *LoggedResponseWriter) Write(bytes []byte) (int, error) {
return w.W.Write(bytes)
}
// WriteHeader 实现WriteHeader方法
func (w *LoggedResponseWriter) WriteHeader(code int) {
w.W.WriteHeader(code)
w.Code = code
}
// DetailLoggedResponseWriter 详细日志记录响应写入器
type DetailLoggedResponseWriter struct {
Writer *LoggedResponseWriter
Buf *bytes.Buffer
}
// NewDetailLoggedResponseWriter 新建详细日志记录响应写入器
func NewDetailLoggedResponseWriter(w http.ResponseWriter, r *http.Request) *DetailLoggedResponseWriter {
return &DetailLoggedResponseWriter{
Writer: NewLoggedResponseWriter(w, r),
Buf: &bytes.Buffer{},
}
}
// Flush 实现Flush方法
func (w *DetailLoggedResponseWriter) Flush() {
w.Writer.Flush()
}
// Header 实现Header方法
func (w *DetailLoggedResponseWriter) Header() http.Header {
return w.Writer.Header()
}
// Hijack 实现Hijack方法
func (w *DetailLoggedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.Writer.Hijack()
}
// Write 实现Write方法
func (w *DetailLoggedResponseWriter) Write(bs []byte) (int, error) {
w.Buf.Write(bs)
return w.Writer.Write(bs)
}
// WriteHeader 实现WriteHeader方法
func (w *DetailLoggedResponseWriter) WriteHeader(code int) {
w.Writer.WriteHeader(code)
}
package xhttp
import (
"bytes"
"context"
"io/ioutil"
"mime/multipart"
"net"
"net/http"
"net/url"
"strings"
"github.com/pkg/errors"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/trace/tracespec"
"github.com/tal-tech/go-zero/rest/httpx"
"gitlab.33.cn/utils/go-kit/convert"
"gitlab.33.cn/utils/go-kit/validator"
"gitlab.33.cn/proof/backend-micro/pkg/errcode"
)
const (
halfShowLen = 100
defaultMultipartMemory = 32 << 20 // 32 MB
)
// Response 业务通用响应体
type Response struct {
TraceId string `json:"trace_id" example:"a1b2c3d4e5f6g7h8" extensions:"x-order=000"` // 链路追踪id
Code uint32 `json:"code" example:"200" extensions:"x-order=001"` // 状态码
Msg string `json:"msg" example:"OK" extensions:"x-order=002"` // 消息
Data interface{} `json:"data" extensions:"x-order=003"` // 数据
}
// GetTraceId 获取链路追踪id
func GetTraceId(ctx context.Context) string {
t, ok := ctx.Value(tracespec.TracingKey).(tracespec.Trace)
if !ok {
return ""
}
return t.TraceId()
}
// WriteHeader 写入自定义响应header
func WriteHeader(w http.ResponseWriter, err ...error) {
var ee error
if len(err) > 0 {
ee = err[0]
}
e := errcode.ParseErr(ee)
w.Header().Set(HeaderGWErrorCode, convert.ToString(e.Code()))
w.Header().Set(HeaderGWErrorMessage, url.QueryEscape(e.Error()))
}
// OkJson 成功json响应返回
func OkJson(w http.ResponseWriter, r *http.Request, v interface{}) {
WriteHeader(w)
httpx.WriteJson(w, http.StatusOK, &Response{
TraceId: GetTraceId(r.Context()),
Code: errcode.CodeOK,
Msg: errcode.MsgOK,
Data: v,
})
}
// Error 错误响应返回
func Error(w http.ResponseWriter, r *http.Request, err error) {
ctx := r.Context()
logx.WithContext(ctx).Errorf("request handle err, err: %+v", err)
e := errcode.ParseErr(err)
WriteHeader(w, e)
httpx.WriteJson(w, e.HTTPCode(), &Response{
TraceId: GetTraceId(ctx),
Code: e.Code(),
Msg: e.Error(),
Data: nil,
})
}
// Parse 请求体解析
func Parse(r *http.Request, v interface{}) error {
if err := httpx.Parse(r, v); err != nil {
logx.WithContext(r.Context()).Errorf("request parse err, err: %s",
formatStr(err.Error(), halfShowLen))
return errcode.ErrInvalidParams
}
if err := validator.Verify(v); err != nil {
return errcode.NewCustomErr(err.Error())
}
return nil
}
// ParseForm 请求表单解析
func ParseForm(r *http.Request, v interface{}) error {
if err := httpx.ParseForm(r, v); err != nil {
logx.WithContext(r.Context()).Errorf("request parse form err, err: %s",
formatStr(err.Error(), halfShowLen))
return errcode.ErrInvalidParams
}
if err := validator.Verify(v); err != nil {
return errcode.NewCustomErr(err.Error())
}
return nil
}
// FromFile 请求表单文件获取
func FromFile(r *http.Request, name string) (*multipart.FileHeader, error) {
if r.MultipartForm == nil {
if err := r.ParseMultipartForm(defaultMultipartMemory); err != nil {
return nil, err
}
}
f, fh, err := r.FormFile(name)
if err != nil {
if err == http.ErrMissingFile {
return nil, errcode.ErrInvalidParams
}
return nil, err
}
f.Close()
return fh, nil
}
// Query 返回给定请求查询参数键的字符串值
func Query(r *http.Request, key string) string {
value, _ := GetQuery(r, key)
return value
}
// GetQuery 返回给定请求查询参数键的字符串值并判断其是否存在
func GetQuery(r *http.Request, key string) (string, bool) {
if values, ok := GetQueryArray(r, key); ok {
return values[0], ok
}
return "", false
}
// QueryArray 返回给定请求查询参数键的字符串切片值
func QueryArray(r *http.Request, key string) []string {
values, _ := GetQueryArray(r, key)
return values
}
// GetQueryArray 返回给定请求查询参数键的字符串切片值并判断其是否存在
func GetQueryArray(r *http.Request, key string) ([]string, bool) {
query := r.URL.Query()
if values, ok := query[key]; ok && len(values) > 0 {
return values, true
}
return []string{}, false
}
// GetClientIP 获取客户端的IP
func GetClientIP(r *http.Request) string {
ip := strings.TrimSpace(strings.Split(r.Header.Get("X-Forwarded-For"), ",")[0])
if ip != "" {
return ip
}
ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
if ip != "" {
return ip
}
if addr := r.Header.Get("X-Appengine-Remote-Addr"); addr != "" {
return addr
}
if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
return ip
}
return ""
}
// GetExternalIP 通过API获取服务端的外部IP
func GetExternalIP() (string, error) {
api := "http://pv.sohu.com/cityjson?ie=utf-8"
resp, err := http.Get(api)
if err != nil {
return "", errors.WithMessagef(err, "http get api = %v err", api)
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", errors.WithMessage(err, "read all response body err")
}
s := string(b)
i := strings.Index(s, `"cip": "`)
s = s[i+len(`"cip": "`):]
i = strings.Index(s, `"`)
s = s[:i]
return s, nil
}
// GetInternalIP 获取服务端的内部IP
func GetInternalIP() string {
infs, err := net.Interfaces()
if err != nil {
return ""
}
for _, inf := range infs {
if isEthDown(inf.Flags) || isLoopback(inf.Flags) {
continue
}
addrs, err := inf.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
}
return ""
}
func isEthDown(f net.Flags) bool {
return f&net.FlagUp != net.FlagUp
}
func isLoopback(f net.Flags) bool {
return f&net.FlagLoopback == net.FlagLoopback
}
func formatStr(s string, halfShowLen int) string {
if length := len(s); length > halfShowLen*2 {
return s[:halfShowLen] + " ...... " + s[length-halfShowLen-1:]
}
return s
}
// CopyHttpRequest 复制请求体
func CopyHttpRequest(r *http.Request) (*http.Request, error) {
rClone := r.Clone(context.Background())
// 克隆请求体
if r.Body != nil {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
r.Body = ioutil.NopCloser(bytes.NewReader(body))
rClone.Body = ioutil.NopCloser(bytes.NewReader(body))
}
return rClone, nil
}
...@@ -26,10 +26,10 @@ func GetBonusStatistics(c *gin.Context) { ...@@ -26,10 +26,10 @@ func GetBonusStatistics(c *gin.Context) {
func GetBonusDetailList(c *gin.Context) { func GetBonusDetailList(c *gin.Context) {
var uid = "c420fb06d76b4fd09050579ab175cff0" var uid = "c420fb06d76b4fd09050579ab175cff0"
var bonus_type = 1 bonus_type := c.DefaultQuery("bonus_type", "0")
bonusDetailService := bonus_detail_service.BonusDetailService{ bonusDetailService := bonus_detail_service.BonusDetailService{
Uid: uid, Uid: uid,
BonusType: uint8(bonus_type), BonusType: uint8(util.ToUint(bonus_type)),
PageNum: util.GetPage(c), PageNum: util.GetPage(c),
PageSize: util.GetLimit(c), PageSize: util.GetLimit(c),
......
package h5
import (
"github.com/gin-gonic/gin"
"slg/pkg/handler"
"slg/pkg/bankcard4c"
"slg/validate_service"
"slg/pkg/errno"
"strings"
"slg/service/user_bank_card_service"
)
func GetBankCard(c *gin.Context) {
var userBankCardService user_bank_card_service.UserBankCardService
userBankCardService.Uid = "16a6220e5ed643afa552b8364fd90fdd"
//exists, _ := userBankCardService.ExistBankCard()
//if !exists {
// handler.SendResponse(c, errno.ErrBankCardNotFound, nil)
// return
//}
user_bank_card, err := userBankCardService.GetUserBankCardInfo()
if err != nil {
handler.SendResponse(c, errno.ErrAddBankCard, nil)
return
}
handler.SendResponse(c, nil, user_bank_card)
return
}
func BindBankCard(c *gin.Context) {
var bank_card validate_service.BindBankCardReq
_ = c.ShouldBindJSON(&bank_card)
if ok, errors := validate_service.ValidateInputs(bank_card); !ok {
for _, err := range errors {
handler.SendResponse(c, errno.ErrBind, strings.Join(err, " "))
return
}
}
config := bankcard4c.Config{
IsMock: false,
Url: "https://bankcard4c.shumaidata.com/bankcard4c",
AppKey: "203824165",
AppKeySecret: "ZWktsksDEzEz7c0c6TJXeQFEeqnHbXtl",
}
bank := bankcard4c.MustNewBankCard4C(&config)
req := bankcard4c.AuthenticateReq{
BankCard: bank_card.BankCard,
IdCard: bank_card.IdCard,
Mobile: bank_card.Mobile,
Name: bank_card.Name,
}
resp, err := bank.Authenticate(&req)
if err != nil || resp.Code == -1 {
// 银行卡四要素认证发生错误,设置默认认证失败响应
//resp = &bankcard4c.AuthenticateResp{Code: bankcard4c.CodeFailure, Msg: bankcard4c.MsgFailure}
handler.SendResponse(c, errno.ErrBankCardIdentification, nil)
return
}
var userBankCardService user_bank_card_service.UserBankCardService
userBankCardService.Uid = "16a6220e5ed643afa552b8364fd90fdd"
exists, _ := userBankCardService.ExistBankCard()
if !exists {
userBankCardService = user_bank_card_service.UserBankCardService{
Uid: "16a6220e5ed643afa552b8364fd90fdd",
BankCard: bank_card.BankCard,
IdCard: bank_card.IdCard,
Mobile: bank_card.Mobile,
Name: bank_card.Name,
}
if err := userBankCardService.Add(); err != nil {
handler.SendResponse(c, errno.ErrAddBankCard, nil)
return
}
}
if exists {
user_bank_card, err := userBankCardService.GetUserBankCardInfo()
if err != nil {
handler.SendResponse(c, errno.ErrAddBankCard, nil)
return
}
if user_bank_card.IsFirst == 0 {
if user_bank_card.IdCard != bank_card.IdCard {
handler.SendResponse(c, errno.ErrBankCardInfo, nil)
return
}
userBankCardService = user_bank_card_service.UserBankCardService{
Uid: "16a6220e5ed643afa552b8364fd90fdd",
BankCard: bank_card.BankCard,
IdCard: bank_card.IdCard,
Mobile: bank_card.Mobile,
Name: bank_card.Name,
}
if err := userBankCardService.Update(); err != nil {
handler.SendResponse(c, errno.ErrUpdateBankCard, nil)
return
}
}
if user_bank_card.IsFirst > 0 {
handler.SendResponse(c, errno.ErrAgainBankCard, nil)
return
}
}
handler.SendResponse(c, nil, nil)
return
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"slg/routers/api/backend" "slg/routers/api/backend"
"slg/routers/h5" "slg/routers/h5"
"slg/middleware/auth"
) )
func InitRouter() *gin.Engine { func InitRouter() *gin.Engine {
...@@ -12,9 +13,13 @@ func InitRouter() *gin.Engine { ...@@ -12,9 +13,13 @@ func InitRouter() *gin.Engine {
r.Use(gin.Recovery()) r.Use(gin.Recovery())
client := r.Group("/h5") client := r.Group("/h5")
client.Use(auth.AUTH())
client.GET("/bonus-statistics", h5.GetBonusStatistics) client.GET("/bonus-statistics", h5.GetBonusStatistics)
client.GET("/bonus-detail-list", h5.GetBonusDetailList) client.GET("/bonus-detail-list", h5.GetBonusDetailList)
client.GET("/bank-card", h5.GetBankCard)
client.POST("/bind-bank-card", h5.BindBankCard)
api := r.Group("/api") api := r.Group("/api")
api.GET("/bonus-list", backend.GetBonus) api.GET("/bonus-list", backend.GetBonus)
return r return r
......
...@@ -6,11 +6,12 @@ import ( ...@@ -6,11 +6,12 @@ import (
) )
type BonusDetailService struct { type BonusDetailService struct {
Uid string `json:"uid"` Uid string `json:"uid"`
BonusAmount string `json:"bonus_amount"` BonusAmount string `json:"bonus_amount"`
BonusType uint8 `json:"bonus_type"` BonusType uint8 `json:"bonus_type"`
CreateTime int64 `json:"create_time"` BonusProportion string `json:"bonus_proportion"`
UpdateTime int64 `json:"update_time"` CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
PageNum int PageNum int
PageSize int PageSize int
...@@ -42,6 +43,19 @@ func (b *BonusDetailService) GetBonusDetailList() ([]*models.BonusDetailResp, er ...@@ -42,6 +43,19 @@ func (b *BonusDetailService) GetBonusDetailList() ([]*models.BonusDetailResp, er
for _, value := range bonusDetailList { for _, value := range bonusDetailList {
record := &models.BonusDetailResp{} record := &models.BonusDetailResp{}
if value.BonusType == models.Invite {
record.Describe = "邀请注册佣金收益"
record.Remart = "存入我的奖励账户"
}
if value.BonusType == models.Share {
record.Describe = "推广分销佣金收益"
record.Remart = "存入我的奖励账户"
}
if value.BonusType == models.Redraw {
record.Describe = "零钱提现"
record.Remart = "提现到银行卡"
}
record.BonusProportion = value.BonusProportion
record.BonusAmount = value.BonusAmount record.BonusAmount = value.BonusAmount
record.BonusType = value.BonusType record.BonusType = value.BonusType
record.CreateTime = util.FormatUnix(value.CreateTime) record.CreateTime = util.FormatUnix(value.CreateTime)
...@@ -59,5 +73,9 @@ func (b *BonusDetailService) getBonusDetailCondition() map[string]interface{} { ...@@ -59,5 +73,9 @@ func (b *BonusDetailService) getBonusDetailCondition() map[string]interface{} {
conditions["uid"] = b.Uid conditions["uid"] = b.Uid
} }
if b.BonusType != 0 {
conditions["bonus_type"] = b.BonusType
}
return conditions return conditions
} }
...@@ -212,14 +212,19 @@ func (b *BonusService) SettlementBonus() error { ...@@ -212,14 +212,19 @@ func (b *BonusService) SettlementBonus() error {
"update_time": time.Now().UTC().Unix(), "update_time": time.Now().UTC().Unix(),
"merchant_id": merchant_uid, "merchant_id": merchant_uid,
"merchant_bonus_amount": merchant_bonus_amount, "merchant_bonus_amount": merchant_bonus_amount,
"merchant_proportion": util.ToString(merchant),
"author_uid": author_uid, "author_uid": author_uid,
"author_bonus_amount": author_bonus_amount, "author_bonus_amount": author_bonus_amount,
"author_proportion": util.ToString(author),
"invite_uid": invite_uid, "invite_uid": invite_uid,
"invite_bonus_amount": invite_bonus_amount, "invite_bonus_amount": invite_bonus_amount,
"invite_proportion": util.ToString(invite),
"share_uid": share_uid, "share_uid": share_uid,
"share_bonus_amount": share_bonus_amount, "share_bonus_amount": share_bonus_amount,
"share_proportion": util.ToString(share),
"platform_uid": platform_uid, "platform_uid": platform_uid,
"platform_bonus_amount": platform_bonus_amount, "platform_bonus_amount": platform_bonus_amount,
"platform_proportion": util.ToString(platform),
} }
tmp, _ := json.Marshal(bonus) tmp, _ := json.Marshal(bonus)
......
package user_bank_card_service
import (
"slg/models"
"github.com/jinzhu/gorm"
)
type UserBankCardService struct {
Uid string `json:"uid"`
BankCard string `json:"bank_card"`
IdCard string `json:"id_card"`
Mobile string `json:"mobile"`
Name string `json:"name"`
IsFirst int `json:"is_first"`
}
func (u *UserBankCardService) GetUserBankCardInfo() (*models.UserBankCard, error) {
user_card_bank, err := models.GetUserBankCardInfo(u.Uid)
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
return user_card_bank, nil
}
func (u *UserBankCardService) Add() error {
userBankInfo := map[string]interface{}{
"uid": u.Uid,
"bank_card": u.BankCard,
"id_card": u.IdCard,
"mobile": u.Mobile,
"name": u.Name,
"is_first": 0,
}
if err := models.AddUserBankCard(userBankInfo); err != nil {
return err
}
return nil
}
func (u *UserBankCardService) Update() error {
return models.UpdateUserBankCard(u.Uid, map[string]interface{}{
"bank_card": u.BankCard,
"id_card": u.IdCard,
"mobile": u.Mobile,
"name": u.Name,
"is_first": 1,
})
}
func (u *UserBankCardService) ExistBankCard() (bool, error) {
return models.ExistBankCard(u.Uid)
}
package validate_service
type Bank struct {
BankName string `json:"name" validate:"required"`
}
package validate_service
type BindBankCardReq struct {
BankCard string `json:"bank_card" validate:"required"`
IdCard string `json:"id_card" validate:"required"`
Mobile string `json:"mobile" validate:"required"`
Name string `json:"name" validate:"required"`
}
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 to comment