Compare commits

..

24 Commits

Author SHA1 Message Date
wangfuduo d2ae9e258b refactor: reuse httpClient 2026-04-16 15:45:21 +08:00
jiangyong f4c1cdb283 Merge branch 'master' of git.u8t.cn:open/gosdk 2026-03-27 23:45:37 +08:00
jiangyong d350e22c50 https 2026-03-27 23:45:32 +08:00
jiangyong 1e1d966023 value 2026-03-21 00:03:49 +08:00
jiangyong e45b0d8b95 log 2026-03-20 23:48:50 +08:00
jiangyong 271a3579a1 stat 2026-03-20 17:10:59 +08:00
jiangyong 0820c34450 controactinfo 2026-03-19 16:27:42 +08:00
jiangyong ccc7894e50 https 2026-03-17 17:20:43 +08:00
jiangyong 4489f801ca local 2026-03-17 17:13:13 +08:00
jiangyong 2f86b5dd30 fetch 2026-03-17 17:11:50 +08:00
jiangyong 74725d735d https 2026-03-17 12:38:44 +08:00
jiangyong 593342b848 https 2026-03-17 12:16:58 +08:00
jiangyong 43d91bd3d8 http.DetectContentType 2026-03-16 12:40:26 +08:00
jiangyong 6770bae66a content-type 2026-03-16 12:27:51 +08:00
jiangyong 863d64b53a storage 2026-03-13 12:02:01 +08:00
jiangyong 006c8de201 stat 2026-03-13 10:44:51 +08:00
jiangyong 3458c6fc4e miniofetchurl 2026-03-13 10:33:23 +08:00
jiangyong 086162dc67 Merge branch 'master' of git.u8t.cn:open/gosdk 2026-03-12 20:43:29 +08:00
jiangyong bfdd9339e6 media 2026-03-12 20:43:21 +08:00
jiangyong d74c2284cf contract 2026-03-10 21:55:32 +08:00
jiangyong b456c3da06 department flat 2026-03-10 16:26:33 +08:00
jiangyong 010cacb4b1 Merge branch 'master' of git.u8t.cn:open/gosdk 2026-03-10 16:19:10 +08:00
jiangyong 0d82e7521f hr 2026-03-10 16:19:05 +08:00
jiangyong 9c46469b67 Merge pull request 'feat: refund to balance' (#4) from wfd into master
Reviewed-on: #4
2026-03-10 11:27:07 +08:00
10 changed files with 341 additions and 132 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ test
go.sum go.sum
pkg pkg
test.go test.go
.claude

18
go.mod
View File

@ -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
) )

View File

@ -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 {

View File

@ -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

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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
}

View File

@ -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参数决定是否先下载到本地在上传七牛云下载企业微信的文件需要不能直接下载
} }

View File

@ -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 {

View File

@ -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
} }