From 6bdfa4b438d969c0c4bea1d7083d5a20a8fd7811 Mon Sep 17 00:00:00 2001 From: wangfuduo Date: Thu, 13 Nov 2025 18:09:37 +0800 Subject: [PATCH 01/31] feat: add redis cache --- cache/redis.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 cache/redis.go diff --git a/cache/redis.go b/cache/redis.go new file mode 100644 index 0000000..8eb4424 --- /dev/null +++ b/cache/redis.go @@ -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 +} From 7f0d4976ebd07503b4be9217d8d350bb09c6d980 Mon Sep 17 00:00:00 2001 From: wangfuduo Date: Thu, 13 Nov 2025 18:10:12 +0800 Subject: [PATCH 02/31] feat: add access token save to redis --- weixin/base.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/weixin/base.go b/weixin/base.go index 2b561cc..17f4092 100644 --- a/weixin/base.go +++ b/weixin/base.go @@ -2,11 +2,15 @@ package weixin import ( "fmt" - "github.com/tidwall/gjson" "io" "net/http" "sync" "time" + + cache2 "git.u8t.cn/open/gosdk/cache" + "github.com/go-redis/redis" + uuid2 "github.com/google/uuid" + "github.com/tidwall/gjson" ) const ( @@ -87,6 +91,65 @@ func (o *BaseSdk) getAccessToken() (string, error) { 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(" 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) { url := fmt.Sprintf("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code", code2AccessTokenUrl, o.appid, o.secret, code) From b5e41d6fefa142eb8c66eef7843d11f888c35e36 Mon Sep 17 00:00:00 2001 From: wangfuduo Date: Thu, 13 Nov 2025 18:10:42 +0800 Subject: [PATCH 03/31] fix: use new get access_token func --- weixin/oa_sdk.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/weixin/oa_sdk.go b/weixin/oa_sdk.go index 0adb6fe..7698358 100644 --- a/weixin/oa_sdk.go +++ b/weixin/oa_sdk.go @@ -4,11 +4,12 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/tidwall/gjson" "io" "mime/multipart" "net/http" "time" + + "github.com/tidwall/gjson" ) type OaSdk struct { @@ -35,7 +36,7 @@ func (o *OaSdk) GetQrCode(sceneStr string) (string, error) { } marshal, _ := json.Marshal(params) - accessToken, err := o.getAccessToken() + accessToken, err := o.getAccessToken2() if err != nil { return "", err } @@ -68,7 +69,7 @@ func (o *OaSdk) GetUserInfoByCodeNoAuth(code string) (*UserInfo, error) { } func (o *OaSdk) GetUserInfoByOpenid(openid string) (*UserInfo, error) { - accessToken, err := o.getAccessToken() + accessToken, err := o.getAccessToken2() if err != nil { return nil, err } @@ -115,7 +116,7 @@ func (o *OaSdk) UploadFileByByte(file []byte, ext string) (string, string, error if err != nil { return "", "", err } - token, err := o.getAccessToken() + token, err := o.getAccessToken2() if err != nil { return "", "", err } @@ -155,7 +156,7 @@ func (o *OaSdk) UploadFileByByte(file []byte, ext string) (string, string, error } func (o *OaSdk) CreateMenu(buttons []*OaMenuButton) error { - accessToken, err := o.getAccessToken() + accessToken, err := o.getAccessToken2() if err != nil { return err } @@ -182,7 +183,7 @@ func (o *OaSdk) CreateMenu(buttons []*OaMenuButton) error { } func (o *OaSdk) DeleteMenu() error { - accessToken, err := o.getAccessToken() + accessToken, err := o.getAccessToken2() if err != nil { return err } From 4642e7e686d2ce1de13a36c70a04b8744829400e Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 30 Dec 2025 17:05:19 +0800 Subject: [PATCH 04/31] tansfer --- unify/pay.go | 28 +++++++++++++++++++++++++++- unify/pay_type.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/unify/pay.go b/unify/pay.go index b854753..55ed809 100644 --- a/unify/pay.go +++ b/unify/pay.go @@ -4,8 +4,10 @@ import ( "encoding/json" "errors" "fmt" - "git.u8t.cn/open/gosdk/util" "net/url" + + "git.u8t.cn/open/gosdk/util" + "git.u8t.cn/open/goutil" ) var ( @@ -219,3 +221,27 @@ func (p *Pay) RefundPartnerOrder(req *RefundOrderReq) error { 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 +} diff --git a/unify/pay_type.go b/unify/pay_type.go index dbf43b2..5f6ead2 100644 --- a/unify/pay_type.go +++ b/unify/pay_type.go @@ -1,5 +1,7 @@ package unify +import "errors" + type OrderUser struct { UserId string `json:"userId"` UserName string `json:"userName"` @@ -51,8 +53,35 @@ type RefundOrderReq struct { RefundFee int64 `json:"refundFee,omitempty"` } +type TransferReq struct { + PayAmount int64 `json:"payAmount"` + PayType string `json:"payType"` + PayTitle string `json:"payTitle"` + PayChannel string `json:"payChannel"` + UserId string `json:"userId"` +} + type CommonResponse struct { Code int `json:"code"` Message string `json:"message"` Data map[string]interface{} `json:"data"` } + +func (r *TransferReq) Check() error { + if r.PayTitle == "" { + return errors.New("PayTitle is nil") + } + if r.PayChannel == "" { + return errors.New("PayChannel 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 +} From 7ca2710e165bc421730c926cdeab4b2c921f64b7 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 30 Dec 2025 17:07:32 +0800 Subject: [PATCH 05/31] channelg --- unify/pay_type.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/unify/pay_type.go b/unify/pay_type.go index 5f6ead2..a99e77f 100644 --- a/unify/pay_type.go +++ b/unify/pay_type.go @@ -71,9 +71,6 @@ func (r *TransferReq) Check() error { if r.PayTitle == "" { return errors.New("PayTitle is nil") } - if r.PayChannel == "" { - return errors.New("PayChannel is nil") - } if r.PayType == "" { return errors.New("PayType is nil") } From 587e29ccd9b89f03ceb72b4df5936e820490a622 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Thu, 1 Jan 2026 01:08:18 +0800 Subject: [PATCH 06/31] outTradeNo --- unify/pay_type.go | 1 + 1 file changed, 1 insertion(+) diff --git a/unify/pay_type.go b/unify/pay_type.go index a99e77f..ef6d009 100644 --- a/unify/pay_type.go +++ b/unify/pay_type.go @@ -59,6 +59,7 @@ type TransferReq struct { PayTitle string `json:"payTitle"` PayChannel string `json:"payChannel"` UserId string `json:"userId"` + OutTradeNo string `json:"outTradeNo"` } type CommonResponse struct { From 79dd16de5884da6840bc24c32b923532f5cbb099 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Mon, 9 Mar 2026 12:38:49 +0800 Subject: [PATCH 07/31] default --- config/form.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/form.go b/config/form.go index c545dc1..a314edc 100644 --- a/config/form.go +++ b/config/form.go @@ -24,6 +24,7 @@ type Form struct { Name string `json:"name"` //表单名称 Key string `json:"key"` //表单KEY Value interface{} `json:"value"` //表单值 + Default interface{} `json:"default"` //默认值 Disable bool `json:"disable,omitempty"` //是否禁用 Tips string `json:"tips"` //表单提示 Option []*FormOption `json:"option"` //表单选项,radio和checkbox需要 @@ -50,6 +51,8 @@ func NewFroms(tplConfig, saveConfig string) ([]*Form, error) { for _, form := range forms { if _, ok := cfg[form.Key]; ok { form.Value = cfg[form.Key] + } else { + form.Value = form.Default } } return forms, err From af3a8435b9a8683ea3285db1425298e39f023290 Mon Sep 17 00:00:00 2001 From: wangfuduo Date: Tue, 10 Mar 2026 11:20:30 +0800 Subject: [PATCH 08/31] feat: refund to balance --- unify/pay.go | 2 +- unify/pay_type.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/unify/pay.go b/unify/pay.go index 55ed809..25abe72 100644 --- a/unify/pay.go +++ b/unify/pay.go @@ -179,7 +179,7 @@ func (p *Pay) RefundOrder(req *RefundOrderReq) error { 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{ "x-token": p.token, }) diff --git a/unify/pay_type.go b/unify/pay_type.go index ef6d009..89eb630 100644 --- a/unify/pay_type.go +++ b/unify/pay_type.go @@ -48,9 +48,10 @@ type CreatePartnerOrderReq struct { } type RefundOrderReq struct { - OutTradeNo string `json:"outTradeNo"` - Reason string `json:"reason,omitempty"` - RefundFee int64 `json:"refundFee,omitempty"` + OutTradeNo string `json:"outTradeNo"` + Reason string `json:"reason,omitempty"` + RefundFee int64 `json:"refundFee,omitempty"` + RefundTarget string `json:"refundTarget,omitempty"` } type TransferReq struct { From 0d82e7521fd10f3e106b2443ebeade2d740c0e15 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 10 Mar 2026 16:19:05 +0800 Subject: [PATCH 09/31] hr --- qyweixin/app_hr.go | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/qyweixin/app_hr.go b/qyweixin/app_hr.go index 1adef49..89d2514 100644 --- a/qyweixin/app_hr.go +++ b/qyweixin/app_hr.go @@ -2,11 +2,12 @@ package qyweixin import ( "fmt" - "git.u8t.cn/open/gosdk/util" - log "github.com/sirupsen/logrus" - "git.u8t.cn/open/goutil" - "github.com/spf13/cast" "time" + + "git.u8t.cn/open/gosdk/util" + "git.u8t.cn/open/goutil" + log "github.com/sirupsen/logrus" + "github.com/spf13/cast" ) var ( @@ -22,10 +23,11 @@ type AppHr struct { } type Department struct { - Id int64 `json:"id"` - Pid int64 `json:"pid"` - Name string `json:"name"` - Leader []string `json:"leader"` + Id int64 `json:"id"` + Pid int64 `json:"pid"` + Name string `json:"name"` + Leader []string `json:"leader"` + Childchren []*Department `json:"childchren"` } type StaffInfo struct { @@ -108,6 +110,8 @@ func (h *AppHr) GetDepartment(id int64) ([]*Department, error) { result := make([]*Department, 0) departments := cast.ToSlice(resp["department"]) + + mapDepartment := make(map[int64]*Department) for _, dd := range departments { d := cast.ToStringMap(dd) r := new(Department) @@ -115,7 +119,23 @@ func (h *AppHr) GetDepartment(id int64) ([]*Department, error) { r.Leader = cast.ToStringSlice(d["department_leader"]) r.Id = cast.ToInt64(d["id"]) r.Pid = cast.ToInt64(d["parentid"]) - result = append(result, r) + mapDepartment[r.Id] = r + } + for _, v := range mapDepartment { + parent := mapDepartment[v.Pid] + if parent == nil { + continue + } + if parent.Childchren == nil { + parent.Childchren = make([]*Department, 0) + } + parent.Childchren = append(parent.Childchren, v) + } + for _, v := range mapDepartment { + if v.Pid != id { + continue + } + result = append(result, v) } return result, nil } From b456c3da06b0a778c8412430a8c4e2778b6965d8 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 10 Mar 2026 16:26:33 +0800 Subject: [PATCH 10/31] department flat --- qyweixin/app_hr.go | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/qyweixin/app_hr.go b/qyweixin/app_hr.go index 89d2514..55d2929 100644 --- a/qyweixin/app_hr.go +++ b/qyweixin/app_hr.go @@ -111,7 +111,6 @@ func (h *AppHr) GetDepartment(id int64) ([]*Department, error) { departments := cast.ToSlice(resp["department"]) - mapDepartment := make(map[int64]*Department) for _, dd := range departments { d := cast.ToStringMap(dd) r := new(Department) @@ -119,24 +118,10 @@ func (h *AppHr) GetDepartment(id int64) ([]*Department, error) { r.Leader = cast.ToStringSlice(d["department_leader"]) r.Id = cast.ToInt64(d["id"]) r.Pid = cast.ToInt64(d["parentid"]) - mapDepartment[r.Id] = r - } - for _, v := range mapDepartment { - parent := mapDepartment[v.Pid] - if parent == nil { - continue - } - if parent.Childchren == nil { - parent.Childchren = make([]*Department, 0) - } - parent.Childchren = append(parent.Childchren, v) - } - for _, v := range mapDepartment { - if v.Pid != id { - continue - } - result = append(result, v) + result = append(result, r) + } + return result, nil } From d74c2284cf4459f2c54ed024b7edba1f4e478385 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 10 Mar 2026 21:55:32 +0800 Subject: [PATCH 11/31] contract --- qyweixin/app_hr.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/qyweixin/app_hr.go b/qyweixin/app_hr.go index 55d2929..8e3ae30 100644 --- a/qyweixin/app_hr.go +++ b/qyweixin/app_hr.go @@ -15,6 +15,7 @@ var ( urlQyWeixinHrGetStaffInfo = "https://qyapi.weixin.qq.com/cgi-bin/hr/get_staff_info" urlQyWeixinHrGetDepartment = "https://qyapi.weixin.qq.com/cgi-bin/department/list" urlQyWeixinHrGetDepartmentUser = "https://qyapi.weixin.qq.com/cgi-bin/user/list" + urlQyWeixinHrGetContactInfo = "https://qyapi.weixin.qq.com/cgi-bin/user/get" ) type AppHr struct { @@ -44,12 +45,39 @@ type StaffInfo struct { BankCard string } +type ContactInfo struct { + Realname string + DirectLeader []string + DepartmentId []int64 + IsDepartmentLeader []int +} + func NewAppHr(cfg *AppConfig) *AppHr { return &AppHr{ App: *NewApp(cfg), } } +func (h *AppHr) GetContactInfo(userId string) (*ContactInfo, error) { + reqUrl := fmt.Sprintf("%s?access_token=%s", urlQyWeixinHrGetContactInfo, h.GetToken()) + reqBody := make(map[string]interface{}) + reqBody["userid"] = userId + rspBody, err := util.HttpPostJson(reqUrl, nil, []byte(goutil.EncodeJSON(reqBody))) + 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) { reqUrl := fmt.Sprintf("%s?access_token=%s", urlQyWeixinHrGetStaffInfo, h.GetToken()) reqBody := make(map[string]interface{}) From bfdd9339e6d45fd41d2e22670a1014df8ab98285 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Thu, 12 Mar 2026 20:43:21 +0800 Subject: [PATCH 12/31] media --- qyweixin/app.go | 5 +++++ qyweixin/app_approve.go | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/qyweixin/app.go b/qyweixin/app.go index ce9a3b3..8b6fff7 100644 --- a/qyweixin/app.go +++ b/qyweixin/app.go @@ -316,6 +316,11 @@ func (a *App) Upload(path, kind string) (string, error) { 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) { ticket, err := q.getJsapiTicket() if err != nil { diff --git a/qyweixin/app_approve.go b/qyweixin/app_approve.go index f343a23..3b9f251 100644 --- a/qyweixin/app_approve.go +++ b/qyweixin/app_approve.go @@ -3,11 +3,12 @@ package qyweixin import ( "encoding/json" "fmt" + "strings" + "git.u8t.cn/open/gosdk/util" "git.u8t.cn/open/goutil" log "github.com/sirupsen/logrus" "github.com/spf13/cast" - "strings" ) type Applyer struct { @@ -165,9 +166,11 @@ func (d *ApproveDetail) GetData() map[string]string { } else if content.Control == "Money" { value = content.Value.NewMoney } else if content.Control == "File" { - if len(content.Value.Files) > 0 { - value = content.Value.Files[0].FileId + fileIds := make([]string, 0) + for _, v := range content.Value.Files { + fileIds = append(fileIds, v.FileId) } + value = strings.Join(fileIds, ",") } 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) + From 3458c6fc4ee0dcdb0f589d201ce77d3aef198825 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Fri, 13 Mar 2026 10:33:23 +0800 Subject: [PATCH 13/31] miniofetchurl --- go.mod | 18 +++++++++++------- storage/minio.go | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index d9ae7e6..5edad07 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,9 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf github.com/eclipse/paho.mqtt.golang v1.5.1 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/google/uuid v1.6.0 github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c github.com/minio/minio-go v6.0.14+incompatible github.com/qiniu/go-sdk/v7 v7.25.4 @@ -15,7 +17,7 @@ require ( github.com/spf13/cast v1.10.0 github.com/tidwall/gjson v1.18.0 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 ) @@ -47,6 +49,8 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // 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/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect @@ -58,12 +62,12 @@ require ( go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/image v0.31.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.44.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/tools v0.41.0 // indirect google.golang.org/protobuf v1.36.9 // indirect modernc.org/fileutil v1.0.0 // indirect ) diff --git a/storage/minio.go b/storage/minio.go index df822b8..60bf833 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/minio/minio-go" + "net/http" "net/url" "path" "strings" @@ -131,6 +132,41 @@ func (s *Minio) Stat(objectName string) (*ObjectInfo, error) { return nil, nil } -func (s *Minio) Fetch(url, objectName string) error { +func (s *Minio) Fetch(urlStr, objectName string) 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, "/ ") + contentType := resp.Header.Get("Content-Type") + if contentType == "" { + ext := strings.TrimLeft(path.Ext(objectName), ".") + contentType = ext2ContentType(ext) + } + + // 上传到 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 } From 006c8de201af77270f55dc644d60e2f0cad2c273 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Fri, 13 Mar 2026 10:44:51 +0800 Subject: [PATCH 14/31] stat --- storage/minio.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/storage/minio.go b/storage/minio.go index 60bf833..3ce9d83 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -3,13 +3,14 @@ package storage import ( "context" "fmt" - "github.com/minio/minio-go" "net/http" "net/url" "path" "strings" "time" + "github.com/minio/minio-go" + log "github.com/sirupsen/logrus" ) @@ -129,7 +130,24 @@ func (s *Minio) Url(objectName string, expire time.Duration) string { } 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 { + 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(urlStr, objectName string) error { From 863d64b53ab589524b5817df3819497ab85e15cf Mon Sep 17 00:00:00 2001 From: jiangyong Date: Fri, 13 Mar 2026 12:02:01 +0800 Subject: [PATCH 15/31] storage --- storage/minio.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/storage/minio.go b/storage/minio.go index 3ce9d83..21adf50 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -169,10 +169,11 @@ func (s *Minio) Fetch(urlStr, objectName string) error { // 获取文件名和 Content-Type objectName = strings.TrimLeft(objectName, "/ ") - contentType := resp.Header.Get("Content-Type") + // 优先根据文件扩展名推断 Content-Type,确保图片能正确预览 + ext := strings.TrimLeft(path.Ext(objectName), ".") + contentType := ext2ContentType(ext) if contentType == "" { - ext := strings.TrimLeft(path.Ext(objectName), ".") - contentType = ext2ContentType(ext) + contentType = resp.Header.Get("Content-Type") } // 上传到 MinIO From 6770bae66a1822f9603948d4e4ca33bb809dd339 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Mon, 16 Mar 2026 12:27:51 +0800 Subject: [PATCH 16/31] content-type --- storage/util.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/storage/util.go b/storage/util.go index de9ad2c..c749723 100644 --- a/storage/util.go +++ b/storage/util.go @@ -21,16 +21,98 @@ func contentType2Ext(contentType string) string { func ext2ContentType(ext string) string { ext = strings.ToLower(ext) - if ext == "jpg" || ext == "jpeg" { + switch ext { + // 图片格式 + case "jpg", "jpeg": return "image/jpeg" - } else if ext == "png" { + case "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" - } 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" + 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 "" } func Download(url, path string) error { From 43d91bd3d880f5a48ff7cf394e7e68419b0e0ce2 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Mon, 16 Mar 2026 12:40:26 +0800 Subject: [PATCH 17/31] http.DetectContentType --- storage/minio.go | 6 +++++- storage/util.go | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/storage/minio.go b/storage/minio.go index 21adf50..4eb3a83 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -63,9 +63,13 @@ func (s *Minio) Put(fileName, objectName string, onProcess func(fsize, uploaded objectName = strings.TrimLeft(objectName, "/ ") ext := strings.TrimLeft(path.Ext(fileName), ".") + contentType := ext2ContentType(ext) + if contentType == "" { + contentType = detectContentType(fileName) + } _, err := s.client.FPutObjectWithContext(ctx, s.config.Bucket, objectName, fileName, - minio.PutObjectOptions{ContentType: ext2ContentType(ext)}) + minio.PutObjectOptions{ContentType: contentType}) if err != nil { log.Errorf("upload file to minio error:%s", err.Error()) return err diff --git a/storage/util.go b/storage/util.go index c749723..a99f44f 100644 --- a/storage/util.go +++ b/storage/util.go @@ -115,6 +115,24 @@ func ext2ContentType(ext string) string { } } +// 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 { f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) stat, err := f.Stat() From 593342b848d9ef74e0a2c92582fd60ebdf30e111 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 17 Mar 2026 12:16:58 +0800 Subject: [PATCH 18/31] https --- storage/qiniu.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/storage/qiniu.go b/storage/qiniu.go index dfb8ae7..563b176 100644 --- a/storage/qiniu.go +++ b/storage/qiniu.go @@ -4,10 +4,11 @@ import ( "context" "errors" "fmt" - "github.com/qiniu/go-sdk/v7/auth/qbox" - "github.com/qiniu/go-sdk/v7/storage" "os" "time" + + "github.com/qiniu/go-sdk/v7/auth/qbox" + "github.com/qiniu/go-sdk/v7/storage" ) type QiniuConfig struct { @@ -15,6 +16,7 @@ type QiniuConfig struct { AK string SK string Timeout time.Duration + Https bool } type Qiniu struct { @@ -167,7 +169,11 @@ func (s *Qiniu) Url(objectName string, expire time.Duration) string { return "" } - domain := "https://" + domains[0].Domain + domain := "http://" + domains[0].Domain + if s.config.Https { + domain = "https://" + domains[0].Domain + } + if expire == 0 { return storage.MakePublicURLv2(domain, objectName) } else { From 74725d735d3a78dcf4ec94bdcd0ca467ed412d12 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 17 Mar 2026 12:38:44 +0800 Subject: [PATCH 19/31] https --- storage/qiniu.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/storage/qiniu.go b/storage/qiniu.go index 563b176..a428850 100644 --- a/storage/qiniu.go +++ b/storage/qiniu.go @@ -16,7 +16,6 @@ type QiniuConfig struct { AK string SK string Timeout time.Duration - Https bool } type Qiniu struct { @@ -169,11 +168,8 @@ func (s *Qiniu) Url(objectName string, expire time.Duration) string { return "" } - domain := "http://" + domains[0].Domain - if s.config.Https { - domain = "https://" + domains[0].Domain - } - + domain := "https://" + domains[0].Domain + if expire == 0 { return storage.MakePublicURLv2(domain, objectName) } else { From 2f86b5dd303783c95a055e62ed53e4b34d519edd Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 17 Mar 2026 17:11:50 +0800 Subject: [PATCH 20/31] fetch --- storage/minio.go | 2 +- storage/qiniu.go | 25 +++++++++++++++++++++++-- storage/storage.go | 2 +- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/storage/minio.go b/storage/minio.go index 4eb3a83..d0f0922 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -154,7 +154,7 @@ func (s *Minio) Stat(objectName string) (*ObjectInfo, error) { return info, nil } -func (s *Minio) Fetch(urlStr, objectName string) error { +func (s *Minio) Fetch(urlStr, objectName string, local ...bool) error { if err := s.Init(); err != nil { return err } diff --git a/storage/qiniu.go b/storage/qiniu.go index a428850..97ab69c 100644 --- a/storage/qiniu.go +++ b/storage/qiniu.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "os" + "path" + "strings" "time" "github.com/qiniu/go-sdk/v7/auth/qbox" @@ -169,7 +171,7 @@ func (s *Qiniu) Url(objectName string, expire time.Duration) string { } domain := "https://" + domains[0].Domain - + if expire == 0 { return storage.MakePublicURLv2(domain, objectName) } else { @@ -198,7 +200,10 @@ func (s *Qiniu) Stat(objectName string) (*ObjectInfo, error) { return info, nil } -func (s *Qiniu) Fetch(url, objectName string) error { +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) cfg := storage.Config{ UseHTTPS: false, @@ -213,3 +218,19 @@ func (s *Qiniu) Fetch(url, objectName string) error { 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 +} diff --git a/storage/storage.go b/storage/storage.go index cf5e329..cd229ec 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -16,5 +16,5 @@ type Storage interface { Url(objectName string, expire time.Duration) string Stat(objectName string) (*ObjectInfo, error) List(objectPrefix string) ([]string, error) - Fetch(url, objectName string) error + Fetch(url, objectName string, local ...bool) error } From 4489f801ca9922d1590285fa6c9ee13b9b4af302 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 17 Mar 2026 17:13:13 +0800 Subject: [PATCH 21/31] local --- storage/storage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/storage.go b/storage/storage.go index cd229ec..41f1483 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -16,5 +16,5 @@ type Storage interface { Url(objectName string, expire time.Duration) string Stat(objectName string) (*ObjectInfo, error) List(objectPrefix string) ([]string, error) - Fetch(url, objectName string, local ...bool) error + Fetch(url, objectName string, local ...bool) error //local参数决定是否先下载到本地在上传,七牛云下载企业微信的文件需要不能直接下载 } From ccc7894e50a2bbcbc4461b7099ce3eaaeb898511 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Tue, 17 Mar 2026 17:20:43 +0800 Subject: [PATCH 22/31] https --- storage/minio.go | 12 ++++++++++-- storage/qiniu.go | 8 +++++--- storage/storage.go | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/storage/minio.go b/storage/minio.go index d0f0922..367a58b 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -114,7 +114,7 @@ func (s *Minio) List(objectPrefix string) ([]string, error) { 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 { return err.Error() } @@ -122,11 +122,19 @@ func (s *Minio) Url(objectName string, expire time.Duration) string { if expire > time.Hour*24*7 || expire == -1 { expire = time.Hour * 24 * 7 } + + baseUrl := s.config.BaseUrl + if len(https) > 1 && https[0] { + if strings.HasPrefix(baseUrl, "http://") { + baseUrl = "https://" + strings.TrimPrefix(baseUrl, "http://") + } + } + var params url.Values u, err := s.client.PresignedGetObject(s.config.Bucket, objectName, expire, params) if err != nil { 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() diff --git a/storage/qiniu.go b/storage/qiniu.go index 97ab69c..2791b36 100644 --- a/storage/qiniu.go +++ b/storage/qiniu.go @@ -155,7 +155,7 @@ func (s *Qiniu) Get(objectName, fileName string) error { 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) cfg := storage.Config{ UseHTTPS: false, @@ -169,8 +169,10 @@ func (s *Qiniu) Url(objectName string, expire time.Duration) string { if len(domains) <= 0 { return "" } - - domain := "https://" + domains[0].Domain + domain := "http://" + domains[0].Domain + if len(https) > 0 && https[0] { + domain = "https://" + domains[0].Domain + } if expire == 0 { return storage.MakePublicURLv2(domain, objectName) diff --git a/storage/storage.go b/storage/storage.go index 41f1483..22436bb 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -13,7 +13,7 @@ type Storage interface { Put(fileName, objectName string, onProcess func(fsize, uploaded int64)) error Get(objectName, fileName 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) List(objectPrefix string) ([]string, error) Fetch(url, objectName string, local ...bool) error //local参数决定是否先下载到本地在上传,七牛云下载企业微信的文件需要不能直接下载 From 0820c34450fa8fd75fb41e7cbb340539ac142fca Mon Sep 17 00:00:00 2001 From: jiangyong Date: Thu, 19 Mar 2026 16:27:42 +0800 Subject: [PATCH 23/31] controactinfo --- qyweixin/app_hr.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qyweixin/app_hr.go b/qyweixin/app_hr.go index 8e3ae30..6889c05 100644 --- a/qyweixin/app_hr.go +++ b/qyweixin/app_hr.go @@ -59,10 +59,9 @@ func NewAppHr(cfg *AppConfig) *AppHr { } func (h *AppHr) GetContactInfo(userId string) (*ContactInfo, error) { - reqUrl := fmt.Sprintf("%s?access_token=%s", urlQyWeixinHrGetContactInfo, h.GetToken()) - reqBody := make(map[string]interface{}) - reqBody["userid"] = userId - rspBody, err := util.HttpPostJson(reqUrl, nil, []byte(goutil.EncodeJSON(reqBody))) + 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 } From 271a3579a15ffd9e2f306808f413f7c882bb9209 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Fri, 20 Mar 2026 17:10:59 +0800 Subject: [PATCH 24/31] stat --- .gitignore | 1 + storage/minio.go | 16 ++++++++++++++++ storage/qiniu.go | 16 ++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/.gitignore b/.gitignore index 0ed3f20..79e270e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ test go.sum pkg test.go +.claude diff --git a/storage/minio.go b/storage/minio.go index 367a58b..40a30f3 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -149,6 +149,10 @@ func (s *Minio) Stat(objectName string) (*ObjectInfo, error) { 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 } @@ -162,6 +166,18 @@ func (s *Minio) Stat(objectName string) (*ObjectInfo, error) { return info, nil } +// 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 diff --git a/storage/qiniu.go b/storage/qiniu.go index 2791b36..997976a 100644 --- a/storage/qiniu.go +++ b/storage/qiniu.go @@ -191,6 +191,10 @@ func (s *Qiniu) Stat(objectName string) (*ObjectInfo, error) { bucketManager := storage.NewBucketManager(mac, &cfg) fileInfo, err := bucketManager.Stat(s.config.Bucket, objectName) if err != nil { + // 文件不存在不算错误,返回 nil, nil + if isQiniuNotFound(err) { + return nil, nil + } return nil, err } @@ -202,6 +206,18 @@ func (s *Qiniu) Stat(objectName string) (*ObjectInfo, error) { return info, nil } +// 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) From e45b0d8b95fd9025bcea4efed2ba1f56f91bee0e Mon Sep 17 00:00:00 2001 From: jiangyong Date: Fri, 20 Mar 2026 23:48:50 +0800 Subject: [PATCH 25/31] log --- qyweixin/app_approve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qyweixin/app_approve.go b/qyweixin/app_approve.go index 3b9f251..a7a2f45 100644 --- a/qyweixin/app_approve.go +++ b/qyweixin/app_approve.go @@ -223,7 +223,7 @@ func (q *AppApprove) GetDetail(spNo string) (*ApproveDetail, error) { } 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 { log.Errorf("get body[%s] json error :%s", string(rspBody), err.Error()) return nil, err From 1e1d9660235bc51530d676fb18d55f45624b2d5a Mon Sep 17 00:00:00 2001 From: jiangyong Date: Sat, 21 Mar 2026 00:03:49 +0800 Subject: [PATCH 26/31] value --- qyweixin/app_approve.go | 53 ++--------------------------------------- 1 file changed, 2 insertions(+), 51 deletions(-) diff --git a/qyweixin/app_approve.go b/qyweixin/app_approve.go index a7a2f45..739ffc0 100644 --- a/qyweixin/app_approve.go +++ b/qyweixin/app_approve.go @@ -91,57 +91,8 @@ type AppApprove struct { } func (d *ApproveDetail) GetValue(title string) string { - - 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 "" + data := d.GetData() + return data[title] } func (d *ApproveDetail) GetData() map[string]string { From d350e22c5054cb8de9019ee14f81e78f5345c36e Mon Sep 17 00:00:00 2001 From: jiangyong Date: Fri, 27 Mar 2026 23:45:32 +0800 Subject: [PATCH 27/31] https --- storage/minio.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/minio.go b/storage/minio.go index 40a30f3..659df71 100644 --- a/storage/minio.go +++ b/storage/minio.go @@ -124,7 +124,7 @@ func (s *Minio) Url(objectName string, expire time.Duration, https ...bool) stri } baseUrl := s.config.BaseUrl - if len(https) > 1 && https[0] { + if len(https) > 0 && https[0] { if strings.HasPrefix(baseUrl, "http://") { baseUrl = "https://" + strings.TrimPrefix(baseUrl, "http://") } From d2ae9e258be093121d0a37f3c42f639e8a0c7b5a Mon Sep 17 00:00:00 2001 From: wangfuduo Date: Thu, 16 Apr 2026 15:45:21 +0800 Subject: [PATCH 28/31] refactor: reuse httpClient --- util/http.go | 77 ++++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/util/http.go b/util/http.go index 2b006b2..7194d7f 100644 --- a/util/http.go +++ b/util/http.go @@ -9,26 +9,36 @@ import ( "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 请求 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)) if err != nil { return nil, err } - if header != nil { - for k, v := range header { - req.Header.Add(k, v) - } + + for k, v := range header { + req.Header.Add(k, v) } req.Header.Add("Content-Type", "application/json") - resp, err := client.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -41,24 +51,17 @@ func HttpPostJson(link string, header map[string]string, json []byte) ([]byte, e // PostJson 请求 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)) if err != nil { return nil, err } - if header != nil { - for k, v := range header { - req.Header.Add(k, v) - } + + for k, v := range header { + req.Header.Add(k, v) } req.Header.Add("Content-Type", "application/json") - resp, err := client.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -71,22 +74,15 @@ func HttpPutJson(link string, header map[string]string, json []byte) ([]byte, er // Get 请求 link:请求url 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) if err != nil { return nil, err } - if header != nil { - for k, v := range header { - req.Header.Add(k, v) - } + + for k, v := range header { + req.Header.Add(k, v) } - resp, err := client.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -99,22 +95,15 @@ func HttpGet(link string, header map[string]string) ([]byte, error) { // Get 请求 link:请求url 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) if err != nil { return nil, err } - if header != nil { - for k, v := range header { - req.Header.Add(k, v) - } + + for k, v := range header { + req.Header.Add(k, v) } - resp, err := client.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } From 10a63309ee67392c4b135a02467163b8f8d9f7f7 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Sun, 19 Apr 2026 18:29:42 +0800 Subject: [PATCH 29/31] enterprise --- enterprise/enterprise.go | 19 ++++++++++ enterprise/staff_user.go | 76 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 enterprise/enterprise.go create mode 100644 enterprise/staff_user.go diff --git a/enterprise/enterprise.go b/enterprise/enterprise.go new file mode 100644 index 0000000..5dbf606 --- /dev/null +++ b/enterprise/enterprise.go @@ -0,0 +1,19 @@ +package global + +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" +} diff --git a/enterprise/staff_user.go b/enterprise/staff_user.go new file mode 100644 index 0000000..007c1c0 --- /dev/null +++ b/enterprise/staff_user.go @@ -0,0 +1,76 @@ +package global + +import ( + "fmt" + + "errors" + + "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 +} From 48e8dbb4d6938022a8fe5a7f156373db49c2b849 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Sun, 19 Apr 2026 18:32:12 +0800 Subject: [PATCH 30/31] package --- enterprise/enterprise.go | 2 +- enterprise/staff_user.go | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/enterprise/enterprise.go b/enterprise/enterprise.go index 5dbf606..0c1c65f 100644 --- a/enterprise/enterprise.go +++ b/enterprise/enterprise.go @@ -1,4 +1,4 @@ -package global +package enterprise type Enterprise struct { token string diff --git a/enterprise/staff_user.go b/enterprise/staff_user.go index 007c1c0..0ff2ef4 100644 --- a/enterprise/staff_user.go +++ b/enterprise/staff_user.go @@ -1,10 +1,8 @@ -package global +package enterprise import ( - "fmt" - "errors" - + "fmt" "git.u8t.cn/open/goutil" "github.com/tidwall/gjson" ) From f70c80c40e0c3af375e638690710be685f1aa213 Mon Sep 17 00:00:00 2001 From: jiangyong Date: Sun, 19 Apr 2026 18:34:00 +0800 Subject: [PATCH 31/31] sort --- enterprise/staff_user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/enterprise/staff_user.go b/enterprise/staff_user.go index 0ff2ef4..2f82aee 100644 --- a/enterprise/staff_user.go +++ b/enterprise/staff_user.go @@ -3,6 +3,7 @@ package enterprise import ( "errors" "fmt" + "git.u8t.cn/open/goutil" "github.com/tidwall/gjson" )