Compare commits
No commits in common. "d2ae9e258be093121d0a37f3c42f639e8a0c7b5a" and "af3a8435b9a8683ea3285db1425298e39f023290" have entirely different histories.
d2ae9e258b
...
af3a8435b9
|
|
@ -10,4 +10,3 @@ test
|
||||||
go.sum
|
go.sum
|
||||||
pkg
|
pkg
|
||||||
test.go
|
test.go
|
||||||
.claude
|
|
||||||
|
|
|
||||||
18
go.mod
18
go.mod
|
|
@ -7,9 +7,7 @@ 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
|
||||||
|
|
@ -17,7 +15,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.47.0
|
golang.org/x/crypto v0.42.0
|
||||||
gorm.io/gorm v1.31.0
|
gorm.io/gorm v1.31.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -49,8 +47,6 @@ 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
|
||||||
|
|
@ -62,12 +58,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.32.0 // indirect
|
golang.org/x/mod v0.27.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.44.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
golang.org/x/tools v0.41.0 // indirect
|
golang.org/x/tools v0.36.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,11 +316,6 @@ 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,12 +3,11 @@ 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 {
|
||||||
|
|
@ -91,8 +90,57 @@ type AppApprove struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ApproveDetail) GetValue(title string) string {
|
func (d *ApproveDetail) GetValue(title string) string {
|
||||||
data := d.GetData()
|
|
||||||
return data[title]
|
for _, content := range d.ApplyData.Contents {
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
@ -117,11 +165,9 @@ 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" {
|
||||||
fileIds := make([]string, 0)
|
if len(content.Value.Files) > 0 {
|
||||||
for _, v := range content.Value.Files {
|
value = content.Value.Files[0].FileId
|
||||||
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) +
|
||||||
|
|
@ -174,7 +220,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,12 +2,11 @@ package qyweixin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.u8t.cn/open/gosdk/util"
|
"git.u8t.cn/open/gosdk/util"
|
||||||
"git.u8t.cn/open/goutil"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"git.u8t.cn/open/goutil"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -15,7 +14,6 @@ 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 {
|
||||||
|
|
@ -24,11 +22,10 @@ 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 {
|
||||||
|
|
@ -45,38 +42,12 @@ 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{})
|
||||||
|
|
@ -137,7 +108,6 @@ 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)
|
||||||
|
|
@ -146,9 +116,7 @@ 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,14 +3,12 @@ package storage
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"github.com/minio/minio-go"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -63,13 +61,9 @@ 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: contentType})
|
minio.PutObjectOptions{ContentType: ext2ContentType(ext)})
|
||||||
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
|
||||||
|
|
@ -114,7 +108,7 @@ func (s *Minio) List(objectPrefix string) ([]string, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Minio) Url(objectName string, expire time.Duration, https ...bool) string {
|
func (s *Minio) Url(objectName string, expire time.Duration) string {
|
||||||
if err := s.Init(); err != nil {
|
if err := s.Init(); err != nil {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
@ -122,19 +116,11 @@ func (s *Minio) Url(objectName string, expire time.Duration, https ...bool) stri
|
||||||
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", baseUrl, s.config.Bucket, objectName)
|
return fmt.Sprintf("%s/%s/%s", s.config.BaseUrl, s.config.Bucket, objectName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.String()
|
return u.String()
|
||||||
|
|
@ -142,78 +128,9 @@ func (s *Minio) Url(objectName string, expire time.Duration, https ...bool) stri
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Minio) Stat(objectName string) (*ObjectInfo, error) {
|
func (s *Minio) Stat(objectName string) (*ObjectInfo, error) {
|
||||||
if err := s.Init(); err != nil {
|
return nil, 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isMinioNotFound 检查是否为 MinIO 文件不存在错误
|
func (s *Minio) Fetch(url, objectName string) error {
|
||||||
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,13 +4,10 @@ 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 {
|
||||||
|
|
@ -155,7 +152,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, https ...bool) string {
|
func (s *Qiniu) Url(objectName string, expire time.Duration) 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,
|
||||||
|
|
@ -169,11 +166,8 @@ func (s *Qiniu) Url(objectName string, expire time.Duration, https ...bool) stri
|
||||||
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 {
|
||||||
|
|
@ -191,10 +185,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,22 +196,7 @@ func (s *Qiniu) Stat(objectName string) (*ObjectInfo, error) {
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isQiniuNotFound 检查是否为七牛云文件不存在错误
|
func (s *Qiniu) Fetch(url, objectName string) error {
|
||||||
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,
|
||||||
|
|
@ -236,19 +211,3 @@ func (s *Qiniu) Fetch(url, objectName string, local ...bool) 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, https ...bool) string //https参数是否生成https参数
|
Url(objectName string, expire time.Duration) string
|
||||||
Stat(objectName string) (*ObjectInfo, error)
|
Stat(objectName string) (*ObjectInfo, error)
|
||||||
List(objectPrefix string) ([]string, error)
|
List(objectPrefix string) ([]string, error)
|
||||||
Fetch(url, objectName string, local ...bool) error //local参数决定是否先下载到本地在上传,七牛云下载企业微信的文件需要不能直接下载
|
Fetch(url, objectName string) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
storage/util.go
110
storage/util.go
|
|
@ -21,116 +21,16 @@ func contentType2Ext(contentType string) string {
|
||||||
|
|
||||||
func ext2ContentType(ext string) string {
|
func ext2ContentType(ext string) string {
|
||||||
ext = strings.ToLower(ext)
|
ext = strings.ToLower(ext)
|
||||||
switch ext {
|
if ext == "jpg" || ext == "jpeg" {
|
||||||
// 图片格式
|
|
||||||
case "jpg", "jpeg":
|
|
||||||
return "image/jpeg"
|
return "image/jpeg"
|
||||||
case "png":
|
} else if ext == "png" {
|
||||||
return "image/png"
|
return "image/png"
|
||||||
case "gif":
|
} else if ext == "mp3" {
|
||||||
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"
|
||||||
case "wav":
|
} else if ext == "mp4" {
|
||||||
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 {
|
||||||
|
|
|
||||||
77
util/http.go
77
util/http.go
|
|
@ -9,36 +9,26 @@ 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 := httpClient.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -51,17 +41,24 @@ 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 := httpClient.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -74,15 +71,22 @@ 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 := httpClient.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -95,15 +99,22 @@ 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 := httpClient.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue