Merge branch 'master' of git.u8t.cn:open/gosdk
This commit is contained in:
commit
e02ab6405d
|
|
@ -10,3 +10,4 @@ test
|
||||||
go.sum
|
go.sum
|
||||||
pkg
|
pkg
|
||||||
test.go
|
test.go
|
||||||
|
.claude
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import "github.com/go-redis/redis"
|
||||||
|
|
||||||
|
var (
|
||||||
|
redisClient *redis.Client = nil
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRedis() {
|
||||||
|
addr := "127.0.0.1:6379"
|
||||||
|
redis.NewClient(&redis.Options{})
|
||||||
|
|
||||||
|
redisClient = redis.NewClient(&redis.Options{
|
||||||
|
Addr: addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRedis() *redis.Client {
|
||||||
|
if redisClient == nil {
|
||||||
|
NewRedis()
|
||||||
|
}
|
||||||
|
return redisClient
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ type Form struct {
|
||||||
Name string `json:"name"` //表单名称
|
Name string `json:"name"` //表单名称
|
||||||
Key string `json:"key"` //表单KEY
|
Key string `json:"key"` //表单KEY
|
||||||
Value interface{} `json:"value"` //表单值
|
Value interface{} `json:"value"` //表单值
|
||||||
|
Default interface{} `json:"default"` //默认值
|
||||||
Disable bool `json:"disable,omitempty"` //是否禁用
|
Disable bool `json:"disable,omitempty"` //是否禁用
|
||||||
Tips string `json:"tips"` //表单提示
|
Tips string `json:"tips"` //表单提示
|
||||||
Option []*FormOption `json:"option"` //表单选项,radio和checkbox需要
|
Option []*FormOption `json:"option"` //表单选项,radio和checkbox需要
|
||||||
|
|
@ -50,6 +51,8 @@ func NewFroms(tplConfig, saveConfig string) ([]*Form, error) {
|
||||||
for _, form := range forms {
|
for _, form := range forms {
|
||||||
if _, ok := cfg[form.Key]; ok {
|
if _, ok := cfg[form.Key]; ok {
|
||||||
form.Value = cfg[form.Key]
|
form.Value = cfg[form.Key]
|
||||||
|
} else {
|
||||||
|
form.Value = form.Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return forms, err
|
return forms, err
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package enterprise
|
||||||
|
|
||||||
|
type Enterprise struct {
|
||||||
|
token string
|
||||||
|
baseUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Enterprise) GetHeader() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"x-token": e.token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Enterprise) GetBaseUrl() string {
|
||||||
|
if e.baseUrl != "" {
|
||||||
|
return e.baseUrl
|
||||||
|
}
|
||||||
|
return "http://e.batiao8.com"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package enterprise
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.u8t.cn/open/goutil"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
corpId = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifyCode struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
}
|
||||||
|
type StaffInfo struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Realname string `json:"realname"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
}
|
||||||
|
type StaffUser struct {
|
||||||
|
Enterprise
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStaffUser(baseUrl, token string) *StaffUser {
|
||||||
|
return &StaffUser{
|
||||||
|
Enterprise: Enterprise{
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StaffUser) SendVerifyCode(username, scene string) (*VerifyCode, error) {
|
||||||
|
var reqBody string
|
||||||
|
reqBody = fmt.Sprintf(`{"username":"%s","scene":"%s"}`, username, scene)
|
||||||
|
reqUrl := e.GetBaseUrl() + "/api/staff/verify/code"
|
||||||
|
|
||||||
|
body, err := goutil.HttpPost(reqUrl, e.GetHeader(), []byte(reqBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
g := gjson.ParseBytes(body)
|
||||||
|
if g.Get("code").Int() != 0 {
|
||||||
|
return nil, errors.New(string(body))
|
||||||
|
}
|
||||||
|
r := new(VerifyCode)
|
||||||
|
r.Timestamp = g.Get("data.timestamp").String()
|
||||||
|
r.Username = g.Get("data.username").String()
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StaffUser) Login(username, timestamp, code string) (*StaffInfo, error) {
|
||||||
|
reqBody := fmt.Sprintf(`{"username":"%s","timestamp":"%s","code":"%s"}`, username, timestamp, code)
|
||||||
|
reqUrl := e.GetBaseUrl() + "/api/staff/login"
|
||||||
|
body, err := goutil.HttpPost(reqUrl, e.GetHeader(), []byte(reqBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
g := gjson.ParseBytes(body)
|
||||||
|
if g.Get("code").Int() != 0 {
|
||||||
|
return nil, errors.New(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
r := new(StaffInfo)
|
||||||
|
r.Realname = g.Get("data.realname").String()
|
||||||
|
r.Username = g.Get("data.username").String()
|
||||||
|
r.Phone = g.Get("data.phone").String()
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
18
go.mod
18
go.mod
|
|
@ -7,7 +7,9 @@ require (
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf
|
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf
|
||||||
github.com/eclipse/paho.mqtt.golang v1.5.1
|
github.com/eclipse/paho.mqtt.golang v1.5.1
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
github.com/gomodule/redigo v1.9.2
|
github.com/gomodule/redigo v1.9.2
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
|
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
|
||||||
github.com/minio/minio-go v6.0.14+incompatible
|
github.com/minio/minio-go v6.0.14+incompatible
|
||||||
github.com/qiniu/go-sdk/v7 v7.25.4
|
github.com/qiniu/go-sdk/v7 v7.25.4
|
||||||
|
|
@ -15,7 +17,7 @@ require (
|
||||||
github.com/spf13/cast v1.10.0
|
github.com/spf13/cast v1.10.0
|
||||||
github.com/tidwall/gjson v1.18.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
|
||||||
golang.org/x/crypto v0.42.0
|
golang.org/x/crypto v0.47.0
|
||||||
gorm.io/gorm v1.31.0
|
gorm.io/gorm v1.31.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -47,6 +49,8 @@ require (
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
|
github.com/onsi/gomega v1.39.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
|
|
@ -58,12 +62,12 @@ require (
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/arch v0.20.0 // indirect
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
golang.org/x/image v0.31.0 // indirect
|
golang.org/x/image v0.31.0 // indirect
|
||||||
golang.org/x/mod v0.27.0 // indirect
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
golang.org/x/net v0.44.0 // indirect
|
golang.org/x/net v0.49.0 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
golang.org/x/text v0.29.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
golang.org/x/tools v0.36.0 // indirect
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.9 // indirect
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
modernc.org/fileutil v1.0.0 // indirect
|
modernc.org/fileutil v1.0.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,11 @@ func (a *App) Upload(path, kind string) (string, error) {
|
||||||
return cast.ToString(result["media_id"]), nil
|
return cast.ToString(result["media_id"]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) MediaUrl(mediaId string) string {
|
||||||
|
mUrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s", a.GetToken(), mediaId)
|
||||||
|
return mUrl
|
||||||
|
}
|
||||||
|
|
||||||
func (q *App) GetJsapiConfig(url string) (*JsapiConfig, error) {
|
func (q *App) GetJsapiConfig(url string) (*JsapiConfig, error) {
|
||||||
ticket, err := q.getJsapiTicket()
|
ticket, err := q.getJsapiTicket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@ package qyweixin
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.u8t.cn/open/gosdk/util"
|
"git.u8t.cn/open/gosdk/util"
|
||||||
"git.u8t.cn/open/goutil"
|
"git.u8t.cn/open/goutil"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Applyer struct {
|
type Applyer struct {
|
||||||
|
|
@ -90,57 +91,8 @@ type AppApprove struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ApproveDetail) GetValue(title string) string {
|
func (d *ApproveDetail) GetValue(title string) string {
|
||||||
|
data := d.GetData()
|
||||||
for _, content := range d.ApplyData.Contents {
|
return data[title]
|
||||||
|
|
||||||
isEqual := false
|
|
||||||
for _, ti := range content.Title {
|
|
||||||
if ti.Text == title {
|
|
||||||
isEqual = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isEqual {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var value string
|
|
||||||
if content.Control == "Selector" {
|
|
||||||
for _, v := range content.Value.Selector.Options[0].Value {
|
|
||||||
if v.Text != "" {
|
|
||||||
value = v.Text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if content.Control == "Text" || content.Control == "Textarea" {
|
|
||||||
value = content.Value.Text
|
|
||||||
} else if content.Control == "Date" {
|
|
||||||
value = content.Value.Date.Timestamp
|
|
||||||
} else if content.Control == "Money" {
|
|
||||||
value = content.Value.NewMoney
|
|
||||||
} else if content.Control == "File" {
|
|
||||||
value = content.Value.Files[0].FileId
|
|
||||||
} else if content.Control == "Vacation" { //请假 : 请假类型,开始时间,结束时间,请假时长
|
|
||||||
tp := content.Value.Vacation.Selector.Options[0].Value[0].Text
|
|
||||||
value = tp + "," + cast.ToString(content.Value.Vacation.Attendance.DateRange.NewBegin) +
|
|
||||||
"," + cast.ToString(content.Value.Vacation.Attendance.DateRange.NewEnd) +
|
|
||||||
"," + cast.ToString(content.Value.Vacation.Attendance.DateRange.NewDuration)
|
|
||||||
} else if content.Control == "PunchCorrection" { //补卡:日期,时间,状态
|
|
||||||
mp := cast.ToStringMap(content.Value.PunchCorrection)
|
|
||||||
ddate := cast.ToString(mp["daymonthyear"])
|
|
||||||
dtime := cast.ToString(mp["time"])
|
|
||||||
if ddate == "" {
|
|
||||||
ddate = dtime
|
|
||||||
}
|
|
||||||
value = ddate + "," + dtime + "," + cast.ToString(mp["state"])
|
|
||||||
} else if content.Control == "BankAccount" {
|
|
||||||
mp := cast.ToStringMap(content.Value.BankAccount)
|
|
||||||
value = cast.ToString(mp["account_type"]) + "," + cast.ToString(mp["account_name"]) + "," + cast.ToString(mp["account_number"])
|
|
||||||
} else if content.Control == "Number" {
|
|
||||||
value = content.Value.NewNumber
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ApproveDetail) GetData() map[string]string {
|
func (d *ApproveDetail) GetData() map[string]string {
|
||||||
|
|
@ -165,9 +117,11 @@ func (d *ApproveDetail) GetData() map[string]string {
|
||||||
} else if content.Control == "Money" {
|
} else if content.Control == "Money" {
|
||||||
value = content.Value.NewMoney
|
value = content.Value.NewMoney
|
||||||
} else if content.Control == "File" {
|
} else if content.Control == "File" {
|
||||||
if len(content.Value.Files) > 0 {
|
fileIds := make([]string, 0)
|
||||||
value = content.Value.Files[0].FileId
|
for _, v := range content.Value.Files {
|
||||||
|
fileIds = append(fileIds, v.FileId)
|
||||||
}
|
}
|
||||||
|
value = strings.Join(fileIds, ",")
|
||||||
} else if content.Control == "Vacation" { //请假 : 请假类型,开始时间,结束时间,请假时长
|
} else if content.Control == "Vacation" { //请假 : 请假类型,开始时间,结束时间,请假时长
|
||||||
tp := content.Value.Vacation.Selector.Options[0].Value[0].Text
|
tp := content.Value.Vacation.Selector.Options[0].Value[0].Text
|
||||||
value = tp + "," + cast.ToString(content.Value.Vacation.Attendance.DateRange.NewBegin) +
|
value = tp + "," + cast.ToString(content.Value.Vacation.Attendance.DateRange.NewBegin) +
|
||||||
|
|
@ -220,7 +174,7 @@ func (q *AppApprove) GetDetail(spNo string) (*ApproveDetail, error) {
|
||||||
}
|
}
|
||||||
var rsp ApproveDetailRsp
|
var rsp ApproveDetailRsp
|
||||||
|
|
||||||
fmt.Println("spno: %s, detail: %s", spNo, string(rspBody))
|
//fmt.Println("spno: %s, detail: %s", spNo, string(rspBody))
|
||||||
if err := json.Unmarshal(rspBody, &rsp); err != nil {
|
if err := json.Unmarshal(rspBody, &rsp); err != nil {
|
||||||
log.Errorf("get body[%s] json error :%s", string(rspBody), err.Error())
|
log.Errorf("get body[%s] json error :%s", string(rspBody), err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package qyweixin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.u8t.cn/open/gosdk/util"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"git.u8t.cn/open/goutil"
|
|
||||||
"github.com/spf13/cast"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.u8t.cn/open/gosdk/util"
|
||||||
|
"git.u8t.cn/open/goutil"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -14,6 +15,7 @@ var (
|
||||||
urlQyWeixinHrGetStaffInfo = "https://qyapi.weixin.qq.com/cgi-bin/hr/get_staff_info"
|
urlQyWeixinHrGetStaffInfo = "https://qyapi.weixin.qq.com/cgi-bin/hr/get_staff_info"
|
||||||
urlQyWeixinHrGetDepartment = "https://qyapi.weixin.qq.com/cgi-bin/department/list"
|
urlQyWeixinHrGetDepartment = "https://qyapi.weixin.qq.com/cgi-bin/department/list"
|
||||||
urlQyWeixinHrGetDepartmentUser = "https://qyapi.weixin.qq.com/cgi-bin/user/list"
|
urlQyWeixinHrGetDepartmentUser = "https://qyapi.weixin.qq.com/cgi-bin/user/list"
|
||||||
|
urlQyWeixinHrGetContactInfo = "https://qyapi.weixin.qq.com/cgi-bin/user/get"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppHr struct {
|
type AppHr struct {
|
||||||
|
|
@ -22,10 +24,11 @@ type AppHr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Department struct {
|
type Department struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Pid int64 `json:"pid"`
|
Pid int64 `json:"pid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Leader []string `json:"leader"`
|
Leader []string `json:"leader"`
|
||||||
|
Childchren []*Department `json:"childchren"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StaffInfo struct {
|
type StaffInfo struct {
|
||||||
|
|
@ -42,12 +45,38 @@ type StaffInfo struct {
|
||||||
BankCard string
|
BankCard string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContactInfo struct {
|
||||||
|
Realname string
|
||||||
|
DirectLeader []string
|
||||||
|
DepartmentId []int64
|
||||||
|
IsDepartmentLeader []int
|
||||||
|
}
|
||||||
|
|
||||||
func NewAppHr(cfg *AppConfig) *AppHr {
|
func NewAppHr(cfg *AppConfig) *AppHr {
|
||||||
return &AppHr{
|
return &AppHr{
|
||||||
App: *NewApp(cfg),
|
App: *NewApp(cfg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *AppHr) GetContactInfo(userId string) (*ContactInfo, error) {
|
||||||
|
reqUrl := fmt.Sprintf("%s?access_token=%s&userid=%s", urlQyWeixinHrGetContactInfo, h.GetToken(), userId)
|
||||||
|
|
||||||
|
rspBody, err := util.HttpGet(reqUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contract := new(ContactInfo)
|
||||||
|
result, err := h.GetResult(rspBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contract.Realname = cast.ToString(result["name"])
|
||||||
|
contract.DirectLeader = cast.ToStringSlice(result["direct_leader"])
|
||||||
|
contract.DepartmentId = cast.ToInt64Slice(result["department"])
|
||||||
|
contract.IsDepartmentLeader = cast.ToIntSlice(result["is_leader_in_dept"])
|
||||||
|
return contract, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *AppHr) GetStaffInfo(userId string) (*StaffInfo, error) {
|
func (h *AppHr) GetStaffInfo(userId string) (*StaffInfo, error) {
|
||||||
reqUrl := fmt.Sprintf("%s?access_token=%s", urlQyWeixinHrGetStaffInfo, h.GetToken())
|
reqUrl := fmt.Sprintf("%s?access_token=%s", urlQyWeixinHrGetStaffInfo, h.GetToken())
|
||||||
reqBody := make(map[string]interface{})
|
reqBody := make(map[string]interface{})
|
||||||
|
|
@ -108,6 +137,7 @@ func (h *AppHr) GetDepartment(id int64) ([]*Department, error) {
|
||||||
result := make([]*Department, 0)
|
result := make([]*Department, 0)
|
||||||
|
|
||||||
departments := cast.ToSlice(resp["department"])
|
departments := cast.ToSlice(resp["department"])
|
||||||
|
|
||||||
for _, dd := range departments {
|
for _, dd := range departments {
|
||||||
d := cast.ToStringMap(dd)
|
d := cast.ToStringMap(dd)
|
||||||
r := new(Department)
|
r := new(Department)
|
||||||
|
|
@ -116,7 +146,9 @@ func (h *AppHr) GetDepartment(id int64) ([]*Department, error) {
|
||||||
r.Id = cast.ToInt64(d["id"])
|
r.Id = cast.ToInt64(d["id"])
|
||||||
r.Pid = cast.ToInt64(d["parentid"])
|
r.Pid = cast.ToInt64(d["parentid"])
|
||||||
result = append(result, r)
|
result = append(result, r)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ package storage
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/minio/minio-go"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -61,9 +63,13 @@ func (s *Minio) Put(fileName, objectName string, onProcess func(fsize, uploaded
|
||||||
|
|
||||||
objectName = strings.TrimLeft(objectName, "/ ")
|
objectName = strings.TrimLeft(objectName, "/ ")
|
||||||
ext := strings.TrimLeft(path.Ext(fileName), ".")
|
ext := strings.TrimLeft(path.Ext(fileName), ".")
|
||||||
|
contentType := ext2ContentType(ext)
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = detectContentType(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := s.client.FPutObjectWithContext(ctx, s.config.Bucket, objectName, fileName,
|
_, err := s.client.FPutObjectWithContext(ctx, s.config.Bucket, objectName, fileName,
|
||||||
minio.PutObjectOptions{ContentType: ext2ContentType(ext)})
|
minio.PutObjectOptions{ContentType: contentType})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("upload file to minio error:%s", err.Error())
|
log.Errorf("upload file to minio error:%s", err.Error())
|
||||||
return err
|
return err
|
||||||
|
|
@ -108,7 +114,7 @@ func (s *Minio) List(objectPrefix string) ([]string, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Minio) Url(objectName string, expire time.Duration) string {
|
func (s *Minio) Url(objectName string, expire time.Duration, https ...bool) string {
|
||||||
if err := s.Init(); err != nil {
|
if err := s.Init(); err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
@ -116,11 +122,19 @@ func (s *Minio) Url(objectName string, expire time.Duration) string {
|
||||||
if expire > time.Hour*24*7 || expire == -1 {
|
if expire > time.Hour*24*7 || expire == -1 {
|
||||||
expire = time.Hour * 24 * 7
|
expire = time.Hour * 24 * 7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseUrl := s.config.BaseUrl
|
||||||
|
if len(https) > 0 && https[0] {
|
||||||
|
if strings.HasPrefix(baseUrl, "http://") {
|
||||||
|
baseUrl = "https://" + strings.TrimPrefix(baseUrl, "http://")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var params url.Values
|
var params url.Values
|
||||||
u, err := s.client.PresignedGetObject(s.config.Bucket, objectName, expire, params)
|
u, err := s.client.PresignedGetObject(s.config.Bucket, objectName, expire, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error:%s", err.Error())
|
log.Errorf("error:%s", err.Error())
|
||||||
return fmt.Sprintf("%s/%s/%s", s.config.BaseUrl, s.config.Bucket, objectName)
|
return fmt.Sprintf("%s/%s/%s", baseUrl, s.config.Bucket, objectName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.String()
|
return u.String()
|
||||||
|
|
@ -128,9 +142,78 @@ func (s *Minio) Url(objectName string, expire time.Duration) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Minio) Stat(objectName string) (*ObjectInfo, error) {
|
func (s *Minio) Stat(objectName string) (*ObjectInfo, error) {
|
||||||
return nil, nil
|
if err := s.Init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
objectName = strings.TrimLeft(objectName, "/ ")
|
||||||
|
stat, err := s.client.StatObject(s.config.Bucket, objectName, minio.StatObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
// 文件不存在不算错误,返回 nil, nil
|
||||||
|
if isMinioNotFound(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
log.Errorf("stat object from minio error:%s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := &ObjectInfo{
|
||||||
|
Size: stat.Size,
|
||||||
|
Hash: stat.ETag,
|
||||||
|
MimeType: stat.ContentType,
|
||||||
|
PutTime: stat.LastModified.Unix(),
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Minio) Fetch(url, objectName string) error {
|
// isMinioNotFound 检查是否为 MinIO 文件不存在错误
|
||||||
|
func isMinioNotFound(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// MinIO 返回 NoSuchKey 表示文件不存在
|
||||||
|
if respErr, ok := err.(minio.ErrorResponse); ok {
|
||||||
|
return respErr.Code == "NoSuchKey" || respErr.Code == "NotFound"
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Minio) Fetch(urlStr, objectName string, local ...bool) error {
|
||||||
|
if err := s.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 URL 下载文件
|
||||||
|
resp, err := http.Get(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("fetch file from url error:%s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("fetch file from url failed, status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件名和 Content-Type
|
||||||
|
objectName = strings.TrimLeft(objectName, "/ ")
|
||||||
|
// 优先根据文件扩展名推断 Content-Type,确保图片能正确预览
|
||||||
|
ext := strings.TrimLeft(path.Ext(objectName), ".")
|
||||||
|
contentType := ext2ContentType(ext)
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = resp.Header.Get("Content-Type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传到 MinIO
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), s.config.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err = s.client.PutObjectWithContext(ctx, s.config.Bucket, objectName, resp.Body, resp.ContentLength,
|
||||||
|
minio.PutObjectOptions{ContentType: contentType})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("upload fetched file to minio error:%s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/qiniu/go-sdk/v7/auth/qbox"
|
"github.com/qiniu/go-sdk/v7/auth/qbox"
|
||||||
"github.com/qiniu/go-sdk/v7/storage"
|
"github.com/qiniu/go-sdk/v7/storage"
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type QiniuConfig struct {
|
type QiniuConfig struct {
|
||||||
|
|
@ -152,7 +155,7 @@ func (s *Qiniu) Get(objectName, fileName string) error {
|
||||||
return Download(url, fileName)
|
return Download(url, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Qiniu) Url(objectName string, expire time.Duration) string {
|
func (s *Qiniu) Url(objectName string, expire time.Duration, https ...bool) string {
|
||||||
mac := qbox.NewMac(s.config.AK, s.config.SK)
|
mac := qbox.NewMac(s.config.AK, s.config.SK)
|
||||||
cfg := storage.Config{
|
cfg := storage.Config{
|
||||||
UseHTTPS: false,
|
UseHTTPS: false,
|
||||||
|
|
@ -166,8 +169,11 @@ func (s *Qiniu) Url(objectName string, expire time.Duration) string {
|
||||||
if len(domains) <= 0 {
|
if len(domains) <= 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
domain := "http://" + domains[0].Domain
|
||||||
|
if len(https) > 0 && https[0] {
|
||||||
|
domain = "https://" + domains[0].Domain
|
||||||
|
}
|
||||||
|
|
||||||
domain := "https://" + domains[0].Domain
|
|
||||||
if expire == 0 {
|
if expire == 0 {
|
||||||
return storage.MakePublicURLv2(domain, objectName)
|
return storage.MakePublicURLv2(domain, objectName)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -185,6 +191,10 @@ func (s *Qiniu) Stat(objectName string) (*ObjectInfo, error) {
|
||||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||||
fileInfo, err := bucketManager.Stat(s.config.Bucket, objectName)
|
fileInfo, err := bucketManager.Stat(s.config.Bucket, objectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// 文件不存在不算错误,返回 nil, nil
|
||||||
|
if isQiniuNotFound(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,7 +206,22 @@ func (s *Qiniu) Stat(objectName string) (*ObjectInfo, error) {
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Qiniu) Fetch(url, objectName string) error {
|
// isQiniuNotFound 检查是否为七牛云文件不存在错误
|
||||||
|
func isQiniuNotFound(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 七牛云错误码 612 表示文件不存在
|
||||||
|
if respErr, ok := err.(*storage.ErrorInfo); ok {
|
||||||
|
return respErr.Code == 612
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Qiniu) Fetch(url, objectName string, local ...bool) error {
|
||||||
|
if len(local) > 0 && local[0] == true {
|
||||||
|
return s.fetchLocal(url, objectName)
|
||||||
|
}
|
||||||
mac := qbox.NewMac(s.config.AK, s.config.SK)
|
mac := qbox.NewMac(s.config.AK, s.config.SK)
|
||||||
cfg := storage.Config{
|
cfg := storage.Config{
|
||||||
UseHTTPS: false,
|
UseHTTPS: false,
|
||||||
|
|
@ -211,3 +236,19 @@ func (s *Qiniu) Fetch(url, objectName string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Qiniu) fetchLocal(url, objectName string) error {
|
||||||
|
objectName = strings.TrimLeft(objectName, "/ ")
|
||||||
|
ext := strings.TrimLeft(path.Ext(objectName), ".")
|
||||||
|
|
||||||
|
tmpFile := fmt.Sprintf("%d.%s", time.Now().UnixMilli(), ext)
|
||||||
|
if err := Download(url, tmpFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpFile)
|
||||||
|
|
||||||
|
if err := s.Put(tmpFile, objectName, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ type Storage interface {
|
||||||
Put(fileName, objectName string, onProcess func(fsize, uploaded int64)) error
|
Put(fileName, objectName string, onProcess func(fsize, uploaded int64)) error
|
||||||
Get(objectName, fileName string) error
|
Get(objectName, fileName string) error
|
||||||
Del(objectName string) error
|
Del(objectName string) error
|
||||||
Url(objectName string, expire time.Duration) string
|
Url(objectName string, expire time.Duration, https ...bool) string //https参数是否生成https参数
|
||||||
Stat(objectName string) (*ObjectInfo, error)
|
Stat(objectName string) (*ObjectInfo, error)
|
||||||
List(objectPrefix string) ([]string, error)
|
List(objectPrefix string) ([]string, error)
|
||||||
Fetch(url, objectName string) error
|
Fetch(url, objectName string, local ...bool) error //local参数决定是否先下载到本地在上传,七牛云下载企业微信的文件需要不能直接下载
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
storage/util.go
110
storage/util.go
|
|
@ -21,16 +21,116 @@ func contentType2Ext(contentType string) string {
|
||||||
|
|
||||||
func ext2ContentType(ext string) string {
|
func ext2ContentType(ext string) string {
|
||||||
ext = strings.ToLower(ext)
|
ext = strings.ToLower(ext)
|
||||||
if ext == "jpg" || ext == "jpeg" {
|
switch ext {
|
||||||
|
// 图片格式
|
||||||
|
case "jpg", "jpeg":
|
||||||
return "image/jpeg"
|
return "image/jpeg"
|
||||||
} else if ext == "png" {
|
case "png":
|
||||||
return "image/png"
|
return "image/png"
|
||||||
} else if ext == "mp3" {
|
case "gif":
|
||||||
|
return "image/gif"
|
||||||
|
case "webp":
|
||||||
|
return "image/webp"
|
||||||
|
case "bmp":
|
||||||
|
return "image/bmp"
|
||||||
|
case "svg", "svgz":
|
||||||
|
return "image/svg+xml"
|
||||||
|
case "ico":
|
||||||
|
return "image/x-icon"
|
||||||
|
case "tiff", "tif":
|
||||||
|
return "image/tiff"
|
||||||
|
case "avif":
|
||||||
|
return "image/avif"
|
||||||
|
case "apng":
|
||||||
|
return "image/apng"
|
||||||
|
// 音频格式
|
||||||
|
case "mp3":
|
||||||
return "audio/mpeg"
|
return "audio/mpeg"
|
||||||
} else if ext == "mp4" {
|
case "wav":
|
||||||
|
return "audio/wav"
|
||||||
|
case "ogg":
|
||||||
|
return "audio/ogg"
|
||||||
|
case "flac":
|
||||||
|
return "audio/flac"
|
||||||
|
case "aac":
|
||||||
|
return "audio/aac"
|
||||||
|
case "m4a":
|
||||||
|
return "audio/mp4"
|
||||||
|
// 视频格式
|
||||||
|
case "mp4":
|
||||||
return "video/mp4"
|
return "video/mp4"
|
||||||
|
case "avi":
|
||||||
|
return "video/x-msvideo"
|
||||||
|
case "mov", "qt":
|
||||||
|
return "video/quicktime"
|
||||||
|
case "webm":
|
||||||
|
return "video/webm"
|
||||||
|
case "flv":
|
||||||
|
return "video/x-flv"
|
||||||
|
case "mkv":
|
||||||
|
return "video/x-matroska"
|
||||||
|
case "wmv":
|
||||||
|
return "video/x-ms-wmv"
|
||||||
|
case "m3u8":
|
||||||
|
return "application/vnd.apple.mpegurl"
|
||||||
|
case "ts":
|
||||||
|
return "video/mp2t"
|
||||||
|
// 文档格式
|
||||||
|
case "pdf":
|
||||||
|
return "application/pdf"
|
||||||
|
case "doc":
|
||||||
|
return "application/msword"
|
||||||
|
case "docx":
|
||||||
|
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
|
case "xls":
|
||||||
|
return "application/vnd.ms-excel"
|
||||||
|
case "xlsx":
|
||||||
|
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
case "ppt":
|
||||||
|
return "application/vnd.ms-powerpoint"
|
||||||
|
case "pptx":
|
||||||
|
return "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||||
|
case "txt":
|
||||||
|
return "text/plain"
|
||||||
|
case "html", "htm":
|
||||||
|
return "text/html"
|
||||||
|
case "css":
|
||||||
|
return "text/css"
|
||||||
|
case "js":
|
||||||
|
return "application/javascript"
|
||||||
|
case "json":
|
||||||
|
return "application/json"
|
||||||
|
case "xml":
|
||||||
|
return "application/xml"
|
||||||
|
case "zip":
|
||||||
|
return "application/zip"
|
||||||
|
case "rar":
|
||||||
|
return "application/x-rar-compressed"
|
||||||
|
case "7z":
|
||||||
|
return "application/x-7z-compressed"
|
||||||
|
case "gz":
|
||||||
|
return "application/gzip"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return ""
|
}
|
||||||
|
|
||||||
|
// detectContentType 从文件内容检测 MIME 类型
|
||||||
|
func detectContentType(filePath string) string {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// 读取前 512 字节用于检测
|
||||||
|
buf := make([]byte, 512)
|
||||||
|
n, err := f.Read(buf)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.DetectContentType(buf[:n])
|
||||||
}
|
}
|
||||||
|
|
||||||
func Download(url, path string) error {
|
func Download(url, path string) error {
|
||||||
|
|
|
||||||
30
unify/pay.go
30
unify/pay.go
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.u8t.cn/open/gosdk/util"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"git.u8t.cn/open/gosdk/util"
|
||||||
|
"git.u8t.cn/open/goutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -177,7 +179,7 @@ func (p *Pay) RefundOrder(req *RefundOrderReq) error {
|
||||||
errors.New("outTradeNo is nil")
|
errors.New("outTradeNo is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
reqUrl := fmt.Sprintf("%s/api/pay/order?outTradeNo=%s&reason=%s&refundFee=%d", p.address, req.OutTradeNo, url.QueryEscape(req.Reason), req.RefundFee)
|
reqUrl := fmt.Sprintf("%s/api/pay/order?outTradeNo=%s&reason=%s&refundFee=%d&refundTarget=%s", p.address, req.OutTradeNo, url.QueryEscape(req.Reason), req.RefundFee, req.RefundTarget)
|
||||||
result, err := util.HttpDelete(reqUrl, map[string]string{
|
result, err := util.HttpDelete(reqUrl, map[string]string{
|
||||||
"x-token": p.token,
|
"x-token": p.token,
|
||||||
})
|
})
|
||||||
|
|
@ -219,3 +221,27 @@ func (p *Pay) RefundPartnerOrder(req *RefundOrderReq) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pay) Transfer(req *TransferReq) error {
|
||||||
|
if err := req.Check(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqUrl := fmt.Sprintf("%s/api/pay/transfer?", p.address)
|
||||||
|
result, err := util.HttpPostJson(reqUrl, map[string]string{
|
||||||
|
"x-token": p.token,
|
||||||
|
}, []byte(goutil.EncodeJSON(req)))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var rsp CommonResponse
|
||||||
|
if err := json.Unmarshal([]byte(result), &rsp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rsp.Code != 0 {
|
||||||
|
return fmt.Errorf("%d:%s", rsp.Code, rsp.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package unify
|
package unify
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
type OrderUser struct {
|
type OrderUser struct {
|
||||||
UserId string `json:"userId"`
|
UserId string `json:"userId"`
|
||||||
UserName string `json:"userName"`
|
UserName string `json:"userName"`
|
||||||
|
|
@ -46,9 +48,19 @@ type CreatePartnerOrderReq struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RefundOrderReq struct {
|
type RefundOrderReq struct {
|
||||||
|
OutTradeNo string `json:"outTradeNo"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
RefundFee int64 `json:"refundFee,omitempty"`
|
||||||
|
RefundTarget string `json:"refundTarget,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferReq struct {
|
||||||
|
PayAmount int64 `json:"payAmount"`
|
||||||
|
PayType string `json:"payType"`
|
||||||
|
PayTitle string `json:"payTitle"`
|
||||||
|
PayChannel string `json:"payChannel"`
|
||||||
|
UserId string `json:"userId"`
|
||||||
OutTradeNo string `json:"outTradeNo"`
|
OutTradeNo string `json:"outTradeNo"`
|
||||||
Reason string `json:"reason,omitempty"`
|
|
||||||
RefundFee int64 `json:"refundFee,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommonResponse struct {
|
type CommonResponse struct {
|
||||||
|
|
@ -56,3 +68,19 @@ type CommonResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Data map[string]interface{} `json:"data"`
|
Data map[string]interface{} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *TransferReq) Check() error {
|
||||||
|
if r.PayTitle == "" {
|
||||||
|
return errors.New("PayTitle is nil")
|
||||||
|
}
|
||||||
|
if r.PayType == "" {
|
||||||
|
return errors.New("PayType is nil")
|
||||||
|
}
|
||||||
|
if r.UserId == "" {
|
||||||
|
return errors.New("UserId is nil")
|
||||||
|
}
|
||||||
|
if r.PayAmount < 0 {
|
||||||
|
return errors.New("PayAmount is nil")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
77
util/http.go
77
util/http.go
|
|
@ -9,26 +9,36 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpClient *http.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
httpClient = &http.Client{
|
||||||
|
Timeout: 20 * time.Second,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
MaxIdleConns: 200,
|
||||||
|
MaxIdleConnsPerHost: 50,
|
||||||
|
MaxConnsPerHost: 500,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PostJson 请求
|
// PostJson 请求
|
||||||
func HttpPostJson(link string, header map[string]string, json []byte) ([]byte, error) {
|
func HttpPostJson(link string, header map[string]string, json []byte) ([]byte, error) {
|
||||||
client := &http.Client{Timeout: 20 * time.Second}
|
|
||||||
//忽略https的证书
|
|
||||||
client.Transport = &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", link, bytes.NewBuffer(json))
|
req, err := http.NewRequest("POST", link, bytes.NewBuffer(json))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if header != nil {
|
|
||||||
for k, v := range header {
|
for k, v := range header {
|
||||||
req.Header.Add(k, v)
|
req.Header.Add(k, v)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -41,24 +51,17 @@ func HttpPostJson(link string, header map[string]string, json []byte) ([]byte, e
|
||||||
|
|
||||||
// PostJson 请求
|
// PostJson 请求
|
||||||
func HttpPutJson(link string, header map[string]string, json []byte) ([]byte, error) {
|
func HttpPutJson(link string, header map[string]string, json []byte) ([]byte, error) {
|
||||||
client := &http.Client{Timeout: 20 * time.Second}
|
|
||||||
//忽略https的证书
|
|
||||||
client.Transport = &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("PUT", link, bytes.NewBuffer(json))
|
req, err := http.NewRequest("PUT", link, bytes.NewBuffer(json))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if header != nil {
|
|
||||||
for k, v := range header {
|
for k, v := range header {
|
||||||
req.Header.Add(k, v)
|
req.Header.Add(k, v)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -71,22 +74,15 @@ func HttpPutJson(link string, header map[string]string, json []byte) ([]byte, er
|
||||||
|
|
||||||
// Get 请求 link:请求url
|
// Get 请求 link:请求url
|
||||||
func HttpGet(link string, header map[string]string) ([]byte, error) {
|
func HttpGet(link string, header map[string]string) ([]byte, error) {
|
||||||
client := &http.Client{Timeout: 20 * time.Second}
|
|
||||||
//忽略https的证书
|
|
||||||
client.Transport = &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", link, nil)
|
req, err := http.NewRequest("GET", link, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if header != nil {
|
|
||||||
for k, v := range header {
|
for k, v := range header {
|
||||||
req.Header.Add(k, v)
|
req.Header.Add(k, v)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
resp, err := client.Do(req)
|
resp, err := httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -99,22 +95,15 @@ func HttpGet(link string, header map[string]string) ([]byte, error) {
|
||||||
|
|
||||||
// Get 请求 link:请求url
|
// Get 请求 link:请求url
|
||||||
func HttpDelete(link string, header map[string]string) ([]byte, error) {
|
func HttpDelete(link string, header map[string]string) ([]byte, error) {
|
||||||
client := &http.Client{Timeout: 20 * time.Second}
|
|
||||||
//忽略https的证书
|
|
||||||
client.Transport = &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("DELETE", link, nil)
|
req, err := http.NewRequest("DELETE", link, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if header != nil {
|
|
||||||
for k, v := range header {
|
for k, v := range header {
|
||||||
req.Header.Add(k, v)
|
req.Header.Add(k, v)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
resp, err := client.Do(req)
|
resp, err := httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,15 @@ package weixin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
cache2 "git.u8t.cn/open/gosdk/cache"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
uuid2 "github.com/google/uuid"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -87,6 +91,65 @@ func (o *BaseSdk) getAccessToken() (string, error) {
|
||||||
return o.accessToken, nil
|
return o.accessToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// redis share version
|
||||||
|
func (o *BaseSdk) getAccessToken2() (string, error) {
|
||||||
|
redix := o.getRedis()
|
||||||
|
tokenKey := fmt.Sprintf("weixin_oa_access_token_v2_%s", o.appid)
|
||||||
|
lockKey := fmt.Sprintf("get_access_token_unique_%s", o.appid)
|
||||||
|
uuid := uuid2.New().String()
|
||||||
|
|
||||||
|
// get token
|
||||||
|
token := redix.Get(tokenKey).Val()
|
||||||
|
if token != "" {
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setEX and expire
|
||||||
|
// if not set key. wait 200ms and get again and retry 5 times
|
||||||
|
if ok := redix.SetNX(lockKey, uuid, 10*time.Second).Val(); !ok {
|
||||||
|
for i := range 5 {
|
||||||
|
time.Sleep(time.Duration(200*(i+2)) * time.Millisecond)
|
||||||
|
if token = redix.Get(tokenKey).Val(); token != "" {
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("<get access_token v2> wait 5 times,but still not get token")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if redix.Get(lockKey).Val() == uuid {
|
||||||
|
redix.Del(lockKey)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// get new token
|
||||||
|
url := fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", accessTokenUrl, o.appid, o.secret)
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
g := gjson.ParseBytes(body)
|
||||||
|
|
||||||
|
token = g.Get("access_token").String()
|
||||||
|
if token == "" {
|
||||||
|
return "", fmt.Errorf("%d:%s", g.Get("errcode").Int(), g.Get("errmsg").String())
|
||||||
|
}
|
||||||
|
|
||||||
|
expire := g.Get("expires_in").Int() - 10
|
||||||
|
redix.Set(tokenKey, token, time.Second*time.Duration(expire))
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *BaseSdk) getRedis() *redis.Client {
|
||||||
|
return cache2.GetRedis()
|
||||||
|
}
|
||||||
|
|
||||||
func (o *BaseSdk) getUserInfoByCode(code string, auth bool) (*UserInfo, error) {
|
func (o *BaseSdk) getUserInfoByCode(code string, auth bool) (*UserInfo, error) {
|
||||||
url := fmt.Sprintf("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
|
url := fmt.Sprintf("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
|
||||||
code2AccessTokenUrl, o.appid, o.secret, code)
|
code2AccessTokenUrl, o.appid, o.secret, code)
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OaSdk struct {
|
type OaSdk struct {
|
||||||
|
|
@ -35,7 +36,7 @@ func (o *OaSdk) GetQrCode(sceneStr string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
marshal, _ := json.Marshal(params)
|
marshal, _ := json.Marshal(params)
|
||||||
accessToken, err := o.getAccessToken()
|
accessToken, err := o.getAccessToken2()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +69,7 @@ func (o *OaSdk) GetUserInfoByCodeNoAuth(code string) (*UserInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OaSdk) GetUserInfoByOpenid(openid string) (*UserInfo, error) {
|
func (o *OaSdk) GetUserInfoByOpenid(openid string) (*UserInfo, error) {
|
||||||
accessToken, err := o.getAccessToken()
|
accessToken, err := o.getAccessToken2()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +116,7 @@ func (o *OaSdk) UploadFileByByte(file []byte, ext string) (string, string, error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
token, err := o.getAccessToken()
|
token, err := o.getAccessToken2()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +156,7 @@ func (o *OaSdk) UploadFileByByte(file []byte, ext string) (string, string, error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OaSdk) CreateMenu(buttons []*OaMenuButton) error {
|
func (o *OaSdk) CreateMenu(buttons []*OaMenuButton) error {
|
||||||
accessToken, err := o.getAccessToken()
|
accessToken, err := o.getAccessToken2()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +183,7 @@ func (o *OaSdk) CreateMenu(buttons []*OaMenuButton) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OaSdk) DeleteMenu() error {
|
func (o *OaSdk) DeleteMenu() error {
|
||||||
accessToken, err := o.getAccessToken()
|
accessToken, err := o.getAccessToken2()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue