Compare commits

..

No commits in common. "master" and "rzh" have entirely different histories.
master ... rzh

34 changed files with 246 additions and 1262 deletions

1
.gitignore vendored
View File

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

View File

@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"git.u8t.cn/open/goutil" "github.com/smbrave/goutil"
) )
type addressRsp struct { type addressRsp struct {

23
cache/redis.go vendored
View File

@ -1,23 +0,0 @@
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
}

View File

@ -1,59 +0,0 @@
package config
import (
"encoding/json"
"strings"
)
const (
FormTypeInput = "input" //文本输入框
FormTypeText = "text" //多行文本
FormTypeRadio = "radio" //单选框
FormTypeCheckbox = "checkbox" //多选框
FormTypeSwitch = "switch" //开关
FormTypeJson = "json" //JSON输入框
)
type FormOption struct {
Name string `json:"name"` //选项名称
Value string `json:"value"` //选项值
}
type Form struct {
Type string `json:"type"` //表单类型
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需要
}
func NewFroms(tplConfig, saveConfig string) ([]*Form, error) {
var forms []*Form
err := json.Unmarshal([]byte(tplConfig), &forms)
if err != nil {
return nil, err
}
var cfg map[string]interface{}
if strings.TrimSpace(saveConfig) != "" {
err = json.Unmarshal([]byte(saveConfig), &cfg)
if err != nil {
return nil, err
}
}
if cfg == nil {
cfg = make(map[string]interface{})
}
for _, form := range forms {
if _, ok := cfg[form.Key]; ok {
form.Value = cfg[form.Key]
} else {
form.Value = form.Default
}
}
return forms, err
}

View File

@ -7,7 +7,7 @@ import (
"fmt" "fmt"
mqtt "github.com/eclipse/paho.mqtt.golang" mqtt "github.com/eclipse/paho.mqtt.golang"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"git.u8t.cn/open/goutil" "github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"strings" "strings"

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
mqtt "github.com/eclipse/paho.mqtt.golang" mqtt "github.com/eclipse/paho.mqtt.golang"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"git.u8t.cn/open/goutil" "github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
"strings" "strings"
"sync" "sync"

View File

@ -1,53 +0,0 @@
package enterprise
import (
"errors"
"git.u8t.cn/open/goutil"
"github.com/tidwall/gjson"
)
const (
ApprovalProfitAmountTypeCost = "消耗"
ApprovalProfitAmountTypeProfit = "利润"
ApprovalProfitAmountTypeAward = "奖励"
)
type ApprovalProfitCreate struct {
SpNo string `json:"sp_no"` //申请单号,相同的单号数据将会被替换
Month string `json:"month"` //业绩对应月份
Username string `json:"username"` //业绩对应责任人
AmountType string `json:"amount_type"` //业绩类型
Amount string `json:"amount"` //业绩金额 单位元
Description string `json:"description"` //业绩描述
AppId string `json:"app_id"` //业绩对应的业务ID
}
type ApprovalProfit struct {
Enterprise
}
func NewApprovalProfit(baseUrl, token string) *ApprovalProfit {
return &ApprovalProfit{
Enterprise: Enterprise{
baseUrl: baseUrl,
token: token,
},
}
}
func (e *ApprovalProfit) Create(req *ApprovalProfitCreate) error {
var reqBody string
reqBody = goutil.EncodeJSON(req)
reqUrl := e.GetBaseUrl() + "/ext/approval/profit"
body, err := goutil.HttpPost(reqUrl, e.GetHeader(), []byte(reqBody))
if err != nil {
return err
}
g := gjson.ParseBytes(body)
if g.Get("code").Int() != 0 {
return errors.New(string(body))
}
return nil
}

View File

@ -1,19 +0,0 @@
package enterprise
type Enterprise struct {
token string
baseUrl string
}
func (e *Enterprise) GetHeader() map[string]string {
return map[string]string{
"x-token": e.token,
}
}
func (e *Enterprise) GetBaseUrl() string {
if e.baseUrl != "" {
return e.baseUrl
}
return "http://e.batiao8.com"
}

View File

@ -1,42 +0,0 @@
package enterprise
import (
"errors"
"git.u8t.cn/open/goutil"
"github.com/tidwall/gjson"
)
type StaffTarget struct {
Enterprise
}
type StaffTargetCreate struct {
Username string `json:"username"`
Month string `json:"month"`
Day string `json:"day"`
Score string `json:"score"`
Comment string `json:"comment"`
}
func NewStaffTarget(baseUrl, token string) *StaffTarget {
return &StaffTarget{
Enterprise: Enterprise{
baseUrl: baseUrl,
token: token,
},
}
}
func (e *StaffTarget) Create(req *StaffTargetCreate) error {
reqUrl := e.GetBaseUrl() + "/ext/staff/target"
body, err := goutil.HttpPost(reqUrl, e.GetHeader(), []byte(goutil.EncodeJSON(req)))
if err != nil {
return err
}
g := gjson.ParseBytes(body)
if g.Get("code").Int() != 0 {
return errors.New(string(body))
}
return nil
}

View File

@ -1,75 +0,0 @@
package enterprise
import (
"errors"
"fmt"
"git.u8t.cn/open/goutil"
"github.com/tidwall/gjson"
)
var (
corpId = 1000
)
type VerifyCode struct {
Username string `json:"username"`
Timestamp string `json:"timestamp"`
}
type StaffInfo struct {
Username string `json:"username"`
Realname string `json:"realname"`
Phone string `json:"phone"`
}
type StaffUser struct {
Enterprise
}
func NewStaffUser(baseUrl, token string) *StaffUser {
return &StaffUser{
Enterprise: Enterprise{
baseUrl: baseUrl,
token: token,
},
}
}
func (e *StaffUser) SendVerifyCode(username, scene string) (*VerifyCode, error) {
var reqBody string
reqBody = fmt.Sprintf(`{"username":"%s","scene":"%s"}`, username, scene)
reqUrl := e.GetBaseUrl() + "/ext/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() + "/ext/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
}

81
go.mod
View File

@ -1,73 +1,58 @@
module git.u8t.cn/open/gosdk module git.u8t.cn/open/gosdk
go 1.25.0 go 1.18
require ( require (
git.u8t.cn/open/goutil v0.0.0-20260502004704-85867684a371 github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/eclipse/paho.mqtt.golang v1.5.1 github.com/gin-gonic/gin v1.9.1
github.com/gin-gonic/gin v1.11.0 github.com/gomodule/redigo v1.8.9
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/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.21.0 github.com/qiniu/go-sdk/v7 v7.21.1
github.com/sirupsen/logrus v1.9.4 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cast v1.10.0 github.com/smbrave/goutil v0.0.0-20240105134047-64fe0dfafba2
github.com/tidwall/gjson v1.18.0 github.com/spf13/cast v1.6.0
github.com/wechatpay-apiv3/wechatpay-go v0.2.21 github.com/tidwall/gjson v1.17.1
golang.org/x/crypto v0.49.0 github.com/wechatpay-apiv3/wechatpay-go v0.2.18
gorm.io/gorm v1.31.1 golang.org/x/crypto v0.25.0
gorm.io/gorm v1.25.6
) )
require ( require (
github.com/BurntSushi/toml v1.3.2 // indirect github.com/BurntSushi/toml v1.3.2 // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic v1.9.1 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/fogleman/gg v1.3.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gammazero/toposort v0.1.1 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
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-20180306012644-bacd9c7ef1dd // 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/pelletier/go-toml/v2 v2.0.8 // 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
github.com/speps/go-hashids v2.0.0+incompatible // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/arch v0.20.0 // indirect golang.org/x/net v0.27.0 // indirect
golang.org/x/image v0.39.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/mod v0.34.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/text v0.16.0 // indirect
golang.org/x/sync v0.20.0 // indirect google.golang.org/protobuf v1.30.0 // indirect
golang.org/x/sys v0.43.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
modernc.org/fileutil v1.0.0 // indirect
) )

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"git.u8t.cn/open/gosdk/util" "git.u8t.cn/open/gosdk/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"git.u8t.cn/open/goutil" "github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
"time" "time"
) )

View File

@ -1,104 +0,0 @@
package push
import (
"encoding/json"
"errors"
"fmt"
"git.u8t.cn/open/gosdk/util"
"git.u8t.cn/open/goutil"
log "github.com/sirupsen/logrus"
"github.com/spf13/cast"
"time"
)
type GetuiSms struct {
appid string
appkey string
masterSecret string
templateId string
token string
}
func NewGetuiSms(appid, appkey, masterSecret, templateId string) *GetuiSms {
return &GetuiSms{
appkey: appkey,
appid: appid,
masterSecret: masterSecret,
templateId: templateId,
}
}
func (g *GetuiSms) getResult(rspBody []byte) (map[string]interface{}, error) {
result := make(map[string]interface{})
if err := json.Unmarshal(rspBody, &result); err != nil {
log.Errorf("json[%s] error :%s", string(rspBody), err.Error())
return nil, err
}
code := cast.ToInt(result["result"])
data := cast.ToStringMap(result["data"])
if code == 20000 {
return data, nil
}
if code != 0 {
log.Errorf("json[%s] rsp error", string(rspBody))
return nil, errors.New(string(rspBody))
}
return data, nil
}
func (g *GetuiSms) Token() string {
timestamp := cast.ToString(time.Now().UnixMilli())
signStr := fmt.Sprintf("%s%s%s", g.appkey, timestamp, g.masterSecret)
reqUrl := "https://openapi-smsp.getui.com/v1/sps/auth_sign"
params := make(map[string]string)
params["timestamp"] = timestamp
params["appId"] = g.appid
params["sign"] = util.Sha256(signStr)
reqBody, _ := json.Marshal(params)
rspBody, err := HttpPostJson(reqUrl, nil, reqBody)
if err != nil {
log.Errorf("http post [%s] error :%s", string(reqBody), err.Error())
return ""
}
result, err := g.getResult(rspBody)
if err != nil {
log.Errorf("get Result error :%s", err.Error())
return ""
}
token := cast.ToString(result["authToken"])
g.token = token
return g.token
}
func (g *GetuiSms) Send(phone string, data string) (interface{}, error) {
reqUrl := "https://openapi-smsp.getui.com/v1/sps/push_sms_list"
params := make(map[string]interface{})
params["authToken"] = g.Token()
params["appId"] = g.appid
params["smsTemplateId"] = g.templateId
params["smsParam"] = data
params["recNum"] = []string{goutil.Md5(phone)}
reqBody, _ := json.Marshal(params)
rspBody, err := HttpPostJson(reqUrl, nil, reqBody)
if err != nil {
log.Errorf("http post [%s] error :%s", string(reqBody), err.Error())
return nil, err
}
_, err = g.getResult(rspBody)
if err != nil {
log.Errorf("get Result error :%s", err.Error())
return nil, err
}
return nil, nil
}

View File

@ -5,20 +5,16 @@ import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"git.u8t.cn/open/gosdk/util" "git.u8t.cn/open/gosdk/util"
"git.u8t.cn/open/gosdk/wechat" "git.u8t.cn/open/gosdk/wechat"
"git.u8t.cn/open/gosdk/wechat/cache" "git.u8t.cn/open/gosdk/wechat/cache"
"git.u8t.cn/open/gosdk/wechat/message" "git.u8t.cn/open/gosdk/wechat/message"
wutil "git.u8t.cn/open/gosdk/wechat/util" wutil "git.u8t.cn/open/gosdk/wechat/util"
"git.u8t.cn/open/goutil"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/tidwall/gjson"
"io" "io"
"io/ioutil" "io/ioutil"
"mime/multipart" "mime/multipart"
@ -72,12 +68,12 @@ type BaseResponse struct {
} }
type AppConfig struct { type AppConfig struct {
Corpid string `json:"corpid"` Corpid string
Secret string `json:"secret"` Secret string
Agent string `json:"agent"` Agent string
Token string `json:"token"` Token string
AesKey string `json:"aes_key"` AesKey string
Replay func(message.MixMessage) *message.Reply `json:"-"` Replay func(message.MixMessage) *message.Reply
} }
type App struct { type App struct {
@ -316,11 +312,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 {
@ -363,7 +354,7 @@ func (q *App) Callback(ctx *gin.Context) {
_, resp, err := wutil.DecryptMsg(wechatConfig.AppID, ctx.Query("echostr"), wechatConfig.EncodingAESKey) _, resp, err := wutil.DecryptMsg(wechatConfig.AppID, ctx.Query("echostr"), wechatConfig.EncodingAESKey)
if err != nil { if err != nil {
log.Errorf("DecryptMsg failed! wechatConfig[%s] error:%s ", goutil.EncodeJSON(q.config), err.Error()) log.Errorf("DecryptMsg failed! appid[%s] aeskey[%s] error:%s ", wechatConfig.AppID, wechatConfig.EncodingAESKey, err.Error())
return return
} }
ctx.Data(http.StatusOK, "Content-type: text/plain", resp) ctx.Data(http.StatusOK, "Content-type: text/plain", resp)
@ -380,7 +371,7 @@ func (q *App) Callback(ctx *gin.Context) {
server.SetDebug(true) server.SetDebug(true)
err := server.Serve() err := server.Serve()
if err != nil { if err != nil {
log.Errorf("qiye weixin Service [%s] err:%s", goutil.EncodeJSON(q.config), err.Error()) log.Errorf("qiye weixin Service [%s] err:%s", goutil.EncodeJSON(wechatConfig), err.Error())
return return
} }
err = server.Send() err = server.Send()
@ -426,16 +417,3 @@ func (q *App) refreshToken() error {
q.tokenExpire = time.Now().Unix() + cast.ToInt64(result["expires_in"]) q.tokenExpire = time.Now().Unix() + cast.ToInt64(result["expires_in"])
return nil return nil
} }
func (q *App) UpdateMenu(menu string) error {
reqUrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/menu/create?access_token=%s&agentid=%s", q.GetToken(), q.config.Agent)
rspBody, err := util.HttpPostJson(reqUrl, nil, []byte(menu))
if err != nil {
return err
}
g := gjson.ParseBytes(rspBody)
if g.Get("errcode").Int() != 0 {
return errors.New(string(rspBody))
}
return nil
}

View File

@ -3,11 +3,9 @@ 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"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
) )
@ -56,7 +54,6 @@ type ApplyValue struct {
Vacation *Vacation `json:"vacation"` Vacation *Vacation `json:"vacation"`
PunchCorrection interface{} `json:"punch_correction"` PunchCorrection interface{} `json:"punch_correction"`
BankAccount interface{} `json:"bank_account"` BankAccount interface{} `json:"bank_account"`
NewNumber string `json:"new_number"`
} }
type ApplyContent struct { type ApplyContent struct {
@ -91,25 +88,23 @@ type AppApprove struct {
} }
func (d *ApproveDetail) GetValue(title string) string { func (d *ApproveDetail) GetValue(title string) string {
data := d.GetData()
return data[title]
}
func (d *ApproveDetail) GetData() map[string]string {
data := make(map[string]string)
for _, content := range d.ApplyData.Contents { 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 var value string
if content.Control == "Selector" { if content.Control == "Selector" {
if len(content.Value.Selector.Options) > 0 { value = content.Value.Selector.Options[0].Value[0].Text
values := make([]string, 0)
for _, v := range content.Value.Selector.Options[0].Value {
if v.Text != "" {
values = append(values, v.Text)
}
}
value = strings.Join(values, ",")
}
} else if content.Control == "Text" || content.Control == "Textarea" { } else if content.Control == "Text" || content.Control == "Textarea" {
value = content.Value.Text value = content.Value.Text
} else if content.Control == "Date" { } else if content.Control == "Date" {
@ -117,11 +112,7 @@ 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) 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) +
@ -138,17 +129,10 @@ func (d *ApproveDetail) GetData() map[string]string {
} else if content.Control == "BankAccount" { } else if content.Control == "BankAccount" {
mp := cast.ToStringMap(content.Value.BankAccount) mp := cast.ToStringMap(content.Value.BankAccount)
value = cast.ToString(mp["account_type"]) + "," + cast.ToString(mp["account_name"]) + "," + cast.ToString(mp["account_number"]) 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
for _, ti := range content.Title {
if ti.Lang == "zh_CN" {
data[ti.Text] = value
} }
} return ""
}
return data
} }
func (d *ApproveDetail) String() string { func (d *ApproveDetail) String() string {
@ -174,7 +158,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

@ -6,11 +6,9 @@ import (
"fmt" "fmt"
"git.u8t.cn/open/gosdk/util" "git.u8t.cn/open/gosdk/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"git.u8t.cn/open/goutil" "github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/tidwall/gjson"
"gorm.io/gorm/utils" "gorm.io/gorm/utils"
"strings"
"time" "time"
) )
@ -18,7 +16,6 @@ type UserCheckIn struct {
Day string Day string
Month string Month string
UserId string UserId string
UserName string
Exception string Exception string
Rawdata string Rawdata string
StartTime int64 StartTime int64
@ -121,7 +118,6 @@ func (q *AppCheckin) GetCheckinData(startDay, endDay string, userIds []string) (
key := fmt.Sprintf("%s_%s", userid, checkDay) key := fmt.Sprintf("%s_%s", userid, checkDay)
var userData *UserCheckIn = nil var userData *UserCheckIn = nil
var ok bool var ok bool
if userData, ok = checkData[key]; !ok { if userData, ok = checkData[key]; !ok {
userData = new(UserCheckIn) userData = new(UserCheckIn)
userData.UserId = userid userData.UserId = userid
@ -133,14 +129,11 @@ func (q *AppCheckin) GetCheckinData(startDay, endDay string, userIds []string) (
userData.Exception += goutil.If(userData.Exception != "", ",", "") userData.Exception += goutil.If(userData.Exception != "", ",", "")
userData.Exception += checkinType + ":" + exceptionType userData.Exception += checkinType + ":" + exceptionType
} }
userData.Rawdata = goutil.If(userData.Rawdata == "", "", "\n") + goutil.EncodeJSON(dat)
userData.Rawdata += goutil.If(userData.Rawdata == "", "", "\n") + goutil.EncodeJSON(dat)
if checkinType == "上班打卡" { if checkinType == "上班打卡" {
userData.StartTime = goutil.If(userData.StartTime == 0 || checkinTime < userData.StartTime, checkinTime, userData.StartTime) userData.StartTime = goutil.If(userData.StartTime == 0 || checkinTime < userData.StartTime, checkinTime, userData.StartTime)
userData.StartTime = goutil.If(strings.Contains(exceptionType, "未打卡"), 0, userData.StartTime)
} else if checkinType == "下班打卡" { } else if checkinType == "下班打卡" {
userData.EndTime = goutil.If(checkinTime > userData.EndTime, checkinTime, userData.EndTime) userData.EndTime = goutil.If(checkinTime > userData.EndTime, checkinTime, userData.EndTime)
userData.EndTime = goutil.If(strings.Contains(exceptionType, "未打卡"), 0, userData.EndTime)
} else { } else {
log.Errorf("不支持的打卡类型:%s %s", checkinType, goutil.EncodeJSON(dat)) log.Errorf("不支持的打卡类型:%s %s", checkinType, goutil.EncodeJSON(dat))
} }
@ -155,73 +148,3 @@ func (q *AppCheckin) GetCheckinData(startDay, endDay string, userIds []string) (
} }
return userDatas, nil return userDatas, nil
} }
func (q *AppCheckin) GetCheckinDataV2(startDay, endDay string, userIds []string) ([]*UserCheckIn, error) {
dayTime, _ := time.ParseInLocation("2006-01-02", startDay, time.Local)
endTime, _ := time.ParseInLocation("2006-01-02", endDay, time.Local)
reqData := make(map[string]interface{})
reqData["starttime"] = dayTime.Unix()
reqData["endtime"] = endTime.Unix() + 86400 - 1
reqData["useridlist"] = userIds
reqUrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_daydata?access_token=%s", q.GetToken())
rspBody, err := util.HttpPostJson(reqUrl, nil, []byte(goutil.EncodeJSON(reqData)))
if err != nil {
return nil, err
}
g := gjson.ParseBytes(rspBody)
if g.Get("errcode").Int() != 0 {
log.Errorf("http url[%s] result[%s] error ", reqUrl, string(rspBody))
return nil, errors.New(string(rspBody))
}
userDatas := make([]*UserCheckIn, 0)
for _, dat := range g.Get("datas").Array() {
ruleCheckinTimes := dat.Get("base_info.rule_info.checkintime").Array()
if len(ruleCheckinTimes) < 1 {
continue
}
userData := new(UserCheckIn)
dayTimestamp := dat.Get("base_info.date").Int()
userId := dat.Get("base_info.acctid").String()
userName := dat.Get("base_info.name").String()
summaryInfo := dat.Get("summary_info").Map()
earliestTime := summaryInfo["earliest_time"].Int()
lastestTime := summaryInfo["lastest_time"].Int()
userData.Day = time.Unix(dayTimestamp, 0).Format("2006-01-02")
userData.Month = time.Unix(dayTimestamp, 0).Format("200601")
userData.UserId = userId
userData.UserName = userName
userData.Rawdata = dat.Raw
if earliestTime > 0 {
userData.StartTime = dayTimestamp + earliestTime
}
if lastestTime > 0 {
userData.EndTime = dayTimestamp + lastestTime
}
if lastestTime == 0 || earliestTime == 0 {
userData.Exception = "上班未打卡;下班未打卡"
userDatas = append(userDatas, userData)
continue
}
if earliestTime == lastestTime {
if earliestTime <= ruleCheckinTimes[0].Get("off_work_sec").Int() {
userData.Exception = "下班未打卡"
} else {
userData.Exception = "上班未打卡"
}
} else {
if dat.Get("summary_info.regular_work_sec").Int() < dat.Get("summary_info.standard_work_sec").Int() {
userData.Exception = "出勤异常"
}
}
userDatas = append(userDatas, userData)
}
return userDatas, nil
}

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"git.u8t.cn/open/gosdk/util" "git.u8t.cn/open/gosdk/util"
"git.u8t.cn/open/gosdk/wechat/message" "git.u8t.cn/open/gosdk/wechat/message"
"git.u8t.cn/open/goutil" "github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
"time" "time"
) )

View File

@ -2,20 +2,16 @@ 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"
"github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
"time"
) )
var ( var (
urlQyWeixinHrGetAllField = "https://qyapi.weixin.qq.com/cgi-bin/hr/get_fields" urlQyWeixinHrGetAllField = "https://qyapi.weixin.qq.com/cgi-bin/hr/get_fields"
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"
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 {
@ -23,14 +19,6 @@ type AppHr struct {
config *AppConfig config *AppConfig
} }
type Department struct {
Id int64 `json:"id"`
Pid int64 `json:"pid"`
Name string `json:"name"`
Leader []string `json:"leader"`
Childchren []*Department `json:"childchren"`
}
type StaffInfo struct { type StaffInfo struct {
UserName string UserName string
RealName string RealName string
@ -38,18 +26,15 @@ type StaffInfo struct {
Phone string Phone string
StaffType string StaffType string
Idno string Idno string
Salary float64
PerfSalary float64
Stock float64
EntryDate string EntryDate string
BirthDate string BirthDate string
OfficialDate string OfficialDate string
BankName string BankName string
BankCard string BankCard string
} AlipayUid string
type ContactInfo struct {
Realname string
DirectLeader []string
DepartmentId []int64
IsDepartmentLeader []int
} }
func NewAppHr(cfg *AppConfig) *AppHr { func NewAppHr(cfg *AppConfig) *AppHr {
@ -58,25 +43,6 @@ func NewAppHr(cfg *AppConfig) *AppHr {
} }
} }
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{})
@ -110,7 +76,8 @@ func (h *AppHr) GetStaffInfo(userId string) (*StaffInfo, error) {
staff.UserName = userId staff.UserName = userId
staff.RealName = userInfo.RealName staff.RealName = userInfo.RealName
staff.Salary = cast.ToFloat64(h.getFieldValue(fieldMap["20001"]))
staff.Stock = cast.ToFloat64(h.getFieldValue(fieldMap["20002"]))
staff.Phone = cast.ToString(h.getFieldValue(fieldMap["17003"])) staff.Phone = cast.ToString(h.getFieldValue(fieldMap["17003"]))
staff.StaffType = cast.ToString(h.getFieldValue(fieldMap["12003"])) staff.StaffType = cast.ToString(h.getFieldValue(fieldMap["12003"]))
staff.Idno = cast.ToString(h.getFieldValue(fieldMap["11015"])) staff.Idno = cast.ToString(h.getFieldValue(fieldMap["11015"]))
@ -119,60 +86,13 @@ func (h *AppHr) GetStaffInfo(userId string) (*StaffInfo, error) {
staff.EntryDate = time.Unix(cast.ToInt64(h.getFieldValue(fieldMap["12018"])), 0).Format("2006-01-02") staff.EntryDate = time.Unix(cast.ToInt64(h.getFieldValue(fieldMap["12018"])), 0).Format("2006-01-02")
staff.BirthDate = time.Unix(cast.ToInt64(h.getFieldValue(fieldMap["11005"])), 0).Format("2006-01-02") staff.BirthDate = time.Unix(cast.ToInt64(h.getFieldValue(fieldMap["11005"])), 0).Format("2006-01-02")
staff.OfficialDate = time.Unix(cast.ToInt64(h.getFieldValue(fieldMap["12023"])), 0).Format("2006-01-02") staff.OfficialDate = time.Unix(cast.ToInt64(h.getFieldValue(fieldMap["12023"])), 0).Format("2006-01-02")
staff.AlipayUid = cast.ToString(h.getFieldValue(fieldMap["20004"]))
staff.PerfSalary = cast.ToFloat64(h.getFieldValue(fieldMap["20004"]))
//fmt.Println(goutil.EncodeJSON(staff))
return staff, nil return staff, nil
} }
func (h *AppHr) GetDepartment(id int64) ([]*Department, error) {
reqUrl := fmt.Sprintf("%s?access_token=%s&id=%d", urlQyWeixinHrGetDepartment, h.GetToken(), id)
rspBody, err := util.HttpGet(reqUrl, nil)
if err != nil {
return nil, err
}
resp, err := h.GetResult(rspBody)
if err != nil {
return nil, err
}
result := make([]*Department, 0)
departments := cast.ToSlice(resp["department"])
for _, dd := range departments {
d := cast.ToStringMap(dd)
r := new(Department)
r.Name = cast.ToString(d["name"])
r.Leader = cast.ToStringSlice(d["department_leader"])
r.Id = cast.ToInt64(d["id"])
r.Pid = cast.ToInt64(d["parentid"])
result = append(result, r)
}
return result, nil
}
func (h *AppHr) GetDepartmentUserId(id int64) ([]string, error) {
reqUrl := fmt.Sprintf("%s?access_token=%s&department_id=%d", urlQyWeixinHrGetDepartmentUser, h.GetToken(), id)
rspBody, err := util.HttpGet(reqUrl, nil)
if err != nil {
return nil, err
}
resp, err := h.GetResult(rspBody)
if err != nil {
return nil, err
}
result := make([]string, 0)
userlist := cast.ToSlice(resp["userlist"])
for _, dd := range userlist {
d := cast.ToStringMap(dd)
result = append(result, cast.ToString(d["userid"]))
}
return result, nil
}
func (h *AppHr) getFieldValue(fieldInfo map[string]interface{}) string { func (h *AppHr) getFieldValue(fieldInfo map[string]interface{}) string {
valueType := cast.ToInt(fieldInfo["value_type"]) valueType := cast.ToInt(fieldInfo["value_type"])
if valueType == 1 { if valueType == 1 {

View File

@ -8,7 +8,7 @@ import (
"fmt" "fmt"
butil "git.u8t.cn/open/gosdk/util" butil "git.u8t.cn/open/gosdk/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"git.u8t.cn/open/goutil" "github.com/smbrave/goutil"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/wechatpay-apiv3/wechatpay-go/core" "github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/option" "github.com/wechatpay-apiv3/wechatpay-go/core/option"

View File

@ -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,27 +108,19 @@ 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()
} }
if expire > time.Hour*24*7 || expire == -1 { if expire > time.Hour*24*7 {
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, 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 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
} }

View File

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

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

View File

@ -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 ""
} }
}
// detectContentType 从文件内容检测 MIME 类型
func detectContentType(filePath string) string {
f, err := os.Open(filePath)
if err != nil {
return "" 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

@ -121,10 +121,7 @@ func (s *Sdk) Match(c *Request) (*Result, error) {
params.Add("channel", c.Channel) params.Add("channel", c.Channel)
params.Add("version", c.Version) params.Add("version", c.Version)
params.Add("os_version", c.OsVersion) params.Add("os_version", c.OsVersion)
params.Add("package", c.Package)
params.Add("did", c.Did)
params.Add("active", strconv.FormatBool(c.Active)) params.Add("active", strconv.FormatBool(c.Active))
params.Add("active_time", c.ActiveTime)
if c.Extra != nil { if c.Extra != nil {
extra, _ := json.Marshal(c.Extra) extra, _ := json.Marshal(c.Extra)
params.Add("extra", string(extra)) params.Add("extra", string(extra))

View File

@ -11,7 +11,6 @@ type BaseResponse struct {
} }
type Request struct { type Request struct {
Package string //安装包的包名
Channel string //安装包的渠道 Channel string //安装包的渠道
Version string //安装包版本 Version string //安装包版本
Os string //手机系统类别 android、ioss Os string //手机系统类别 android、ioss
@ -25,11 +24,9 @@ type Request struct {
Paid string //客户端的广告idios时候有效百度特有 Paid string //客户端的广告idios时候有效百度特有
Oaid string //客户端的广告idandroid时有效 Oaid string //客户端的广告idandroid时有效
Imei string //设备唯一识别码 Imei string //设备唯一识别码
Did string //设备唯一识别码android是android_idios是idfv
OsVersion string //操作系统版本号 OsVersion string //操作系统版本号
Extra map[string]string //其他额外数据 Extra map[string]string //其他额外数据
Active bool // 是否直接激活 Active bool // 是否直接激活
ActiveTime string //用户真实激活时间
} }
type Result struct { type Result struct {

View File

@ -3,10 +3,8 @@ package unify
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url"
"git.u8t.cn/open/gosdk/util" "git.u8t.cn/open/gosdk/util"
"git.u8t.cn/open/goutil" "net/url"
) )
type Message struct { type Message struct {
@ -26,22 +24,8 @@ func NewMessage(addreess string, token string, sender string) *Message {
} }
} }
func (m *Message) Send(args ...string) error { func (m *Message) Send(receiver, content string) error {
return m.SendText(args...) reqUrl := fmt.Sprintf("%s/admin/message/send?sender=%s&receiver=%s&content=%s", m.address, m.sender, receiver, url.QueryEscape(content))
}
func (m *Message) SendText(args ...string) error {
var receiver, content, fingerprint, level, count string
if len(args) == 2 {
receiver, content = args[0], args[1]
} else if len(args) == 3 {
receiver, content, fingerprint = args[0], args[1], args[2]
} else if len(args) == 4 {
receiver, content, fingerprint, level = args[0], args[1], args[2], args[3]
} else if len(args) > 4 {
receiver, content, fingerprint, level, count = args[0], args[1], args[2], args[3], args[4]
}
reqUrl := fmt.Sprintf("%s/admin/message/send?sender=%s&receiver=%s&type=text&fingerprint=%s&count=%s&level=%s&content=%s", m.address, m.sender, receiver, fingerprint, count, level, url.QueryEscape(content))
body, err := util.HttpGet(reqUrl, map[string]string{ body, err := util.HttpGet(reqUrl, map[string]string{
"x-token": m.token, "x-token": m.token,
}) })
@ -57,27 +41,3 @@ func (m *Message) SendText(args ...string) error {
} }
return nil return nil
} }
func (m *Message) SendTable(receiver string, table *goutil.TableData) error {
content := goutil.EncodeJSON(table)
reqUrl := fmt.Sprintf("%s/admin/message/send", m.address)
reqData := make(map[string]interface{})
reqData["type"] = "table"
reqData["content"] = content
reqData["receiver"] = receiver
reqData["sender"] = m.sender
body, err := util.HttpPostJson(reqUrl, map[string]string{"x-token": m.token},
[]byte(goutil.EncodeJSON(reqData)))
if err != nil {
return err
}
var rsp util.Response
if err := json.Unmarshal(body, &rsp); err != nil {
return err
}
if rsp.Code != 0 {
return fmt.Errorf("%d:%s", rsp.Code, rsp.Message)
}
return nil
}

View File

@ -4,10 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/url"
"git.u8t.cn/open/gosdk/util" "git.u8t.cn/open/gosdk/util"
"git.u8t.cn/open/goutil"
) )
var ( var (
@ -179,7 +176,7 @@ func (p *Pay) RefundOrder(req *RefundOrderReq) error {
errors.New("outTradeNo is nil") errors.New("outTradeNo is nil")
} }
reqUrl := fmt.Sprintf("%s/api/pay/order?outTradeNo=%s&reason=%s&refundFee=%d&refundTarget=%s", p.address, req.OutTradeNo, url.QueryEscape(req.Reason), req.RefundFee, req.RefundTarget) reqUrl := fmt.Sprintf("%s/api/pay/order?outTradeNo=%s&reason=%s&refundFee=%d", p.address, req.OutTradeNo, req.Reason, req.RefundFee)
result, err := util.HttpDelete(reqUrl, map[string]string{ result, err := util.HttpDelete(reqUrl, map[string]string{
"x-token": p.token, "x-token": p.token,
}) })
@ -203,7 +200,7 @@ func (p *Pay) RefundPartnerOrder(req *RefundOrderReq) error {
errors.New("outTradeNo is nil") errors.New("outTradeNo is nil")
} }
reqUrl := fmt.Sprintf("%s/api/pay/partner/order?outTradeNo=%s&reason=%s&refundFee=%d", p.address, req.OutTradeNo, url.QueryEscape(req.Reason), req.RefundFee) reqUrl := fmt.Sprintf("%s/api/pay/partner/order?outTradeNo=%s&reason=%s&refundFee=%d", p.address, req.OutTradeNo, req.Reason, req.RefundFee)
result, err := util.HttpDelete(reqUrl, map[string]string{ result, err := util.HttpDelete(reqUrl, map[string]string{
"x-token": p.token, "x-token": p.token,
}) })
@ -221,27 +218,3 @@ func (p *Pay) RefundPartnerOrder(req *RefundOrderReq) error {
return nil return nil
} }
func (p *Pay) Transfer(req *TransferReq) error {
if err := req.Check(); err != nil {
return err
}
reqUrl := fmt.Sprintf("%s/api/pay/transfer?", p.address)
result, err := util.HttpPostJson(reqUrl, map[string]string{
"x-token": p.token,
}, []byte(goutil.EncodeJSON(req)))
if err != nil {
return err
}
var rsp CommonResponse
if err := json.Unmarshal([]byte(result), &rsp); err != nil {
return err
}
if rsp.Code != 0 {
return fmt.Errorf("%d:%s", rsp.Code, rsp.Message)
}
return nil
}

View File

@ -1,7 +1,5 @@
package unify package unify
import "errors"
type OrderUser struct { type OrderUser struct {
UserId string `json:"userId"` UserId string `json:"userId"`
UserName string `json:"userName"` UserName string `json:"userName"`
@ -43,7 +41,6 @@ type CreatePartnerOrderReq struct {
OutTradeNo string `json:"outTradeNo"` OutTradeNo string `json:"outTradeNo"`
NotifyUrl string `json:"notifyUrl"` NotifyUrl string `json:"notifyUrl"`
PartnerId string `json:"partnerId"` PartnerId string `json:"partnerId"`
PayChannel string `json:"payChannel"`
Extra interface{} `json:"extra"` Extra interface{} `json:"extra"`
} }
@ -51,17 +48,6 @@ type RefundOrderReq struct {
OutTradeNo string `json:"outTradeNo"` OutTradeNo string `json:"outTradeNo"`
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
RefundFee int64 `json:"refundFee,omitempty"` RefundFee int64 `json:"refundFee,omitempty"`
RefundTarget string `json:"refundTarget,omitempty"`
}
type TransferReq struct {
PayAmount int64 `json:"payAmount"`
PayType string `json:"payType"`
PayTitle string `json:"payTitle"`
PayChannel string `json:"payChannel"`
UserId string `json:"userId"`
OutTradeNo string `json:"outTradeNo"`
Extra map[string]interface{} `json:"extra"`
} }
type CommonResponse struct { type CommonResponse struct {
@ -69,19 +55,3 @@ type CommonResponse struct {
Message string `json:"message"` Message string `json:"message"`
Data map[string]interface{} `json:"data"` Data map[string]interface{} `json:"data"`
} }
func (r *TransferReq) Check() error {
if r.PayTitle == "" {
return errors.New("PayTitle is nil")
}
if r.PayType == "" {
return errors.New("PayType is nil")
}
if r.UserId == "" {
return errors.New("UserId is nil")
}
if r.PayAmount < 0 {
return errors.New("PayAmount is nil")
}
return nil
}

View File

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

View File

@ -37,7 +37,6 @@ const (
//MsgTypeEvent 表示事件推送消息 //MsgTypeEvent 表示事件推送消息
MsgTypeEvent = "event" MsgTypeEvent = "event"
MsgTypeChannels = "channels" MsgTypeChannels = "channels"
MsgTypeChnageContract = "change_contact"
) )
const ( const (
@ -84,99 +83,94 @@ const (
// MixMessage 存放所有微信发送过来的消息和事件 // MixMessage 存放所有微信发送过来的消息和事件
type MixMessage struct { type MixMessage struct {
CommonToken `json:"commonToken"` CommonToken
//基本消息 //基本消息
MsgID int64 `xml:"MsgId" json:"msgID,omitempty"` MsgID int64 `xml:"MsgId"`
Content string `xml:"Content" json:"content,omitempty"` Content string `xml:"Content"`
Recognition string `xml:"Recognition" json:"recognition,omitempty"` Recognition string `xml:"Recognition"`
PicURL string `xml:"PicUrl" json:"picURL,omitempty"` PicURL string `xml:"PicUrl"`
MediaID string `xml:"MediaId" json:"mediaID,omitempty"` MediaID string `xml:"MediaId"`
Format string `xml:"Format" json:"format,omitempty"` Format string `xml:"Format"`
ThumbMediaID string `xml:"ThumbMediaId" json:"thumbMediaID,omitempty"` ThumbMediaID string `xml:"ThumbMediaId"`
LocationX float64 `xml:"Location_X" json:"locationX,omitempty"` LocationX float64 `xml:"Location_X"`
LocationY float64 `xml:"Location_Y" json:"locationY,omitempty"` LocationY float64 `xml:"Location_Y"`
Scale float64 `xml:"Scale" json:"scale,omitempty"` Scale float64 `xml:"Scale"`
Label string `xml:"Label" json:"label,omitempty"` Label string `xml:"Label"`
Title string `xml:"Title" json:"title,omitempty"` Title string `xml:"Title"`
Description string `xml:"Description" json:"description,omitempty"` Description string `xml:"Description"`
URL string `xml:"Url" json:"URL,omitempty"` URL string `xml:"Url"`
//事件相关 //事件相关
Event EventType `xml:"Event" json:"event,omitempty"` Event EventType `xml:"Event"`
EventKey string `xml:"EventKey" json:"eventKey,omitempty"` EventKey string `xml:"EventKey"`
Ticket string `xml:"Ticket" json:"ticket,omitempty"` Ticket string `xml:"Ticket"`
Latitude string `xml:"Latitude" json:"latitude,omitempty"` Latitude string `xml:"Latitude"`
Longitude string `xml:"Longitude" json:"longitude,omitempty"` Longitude string `xml:"Longitude"`
Precision string `xml:"Precision" json:"precision,omitempty"` Precision string `xml:"Precision"`
MenuID string `xml:"MenuId" json:"menuID,omitempty"` MenuID string `xml:"MenuId"`
Status string `xml:"Status" json:"status,omitempty"` Status string `xml:"Status"`
SessionFrom string `xml:"SessionFrom" json:"sessionFrom,omitempty"` SessionFrom string `xml:"SessionFrom"`
ScanCodeInfo struct { ScanCodeInfo struct {
ScanType string `xml:"ScanType" json:"scanType,omitempty"` ScanType string `xml:"ScanType"`
ScanResult string `xml:"ScanResult" json:"scanResult,omitempty"` ScanResult string `xml:"ScanResult"`
} `xml:"ScanCodeInfo" json:"scanCodeInfo"` } `xml:"ScanCodeInfo"`
// 企业微信客服相关 // 企业微信客服相关
OpenKfId string `xml:"OpenKfId" json:"openKfId,omitempty"` OpenKfId string `xml:"OpenKfId"`
Token string `xml:"Token" json:"token,omitempty"` Token string `xml:"Token"`
// 企业微信审核相关 // 企业微信审核相关
ApprovalInfo struct { ApprovalInfo struct {
SpNo string `xml:"SpNo" json:"spNo,omitempty"` SpNo string `xml:"SpNo"`
SpName string `xml:"SpName" json:"spName,omitempty"` SpName string `xml:"SpName"`
SpStatus int `xml:"SpStatus" json:"spStatus,omitempty"` SpStatus int `xml:"SpStatus"`
TemplateId string `xml:"TemplateId" json:"templateId,omitempty"` TemplateId string `xml:"TemplateId"`
ApplyTime int64 `xml:"ApplyTime" json:"applyTime,omitempty"` ApplyTime int64 `xml:"ApplyTime"`
Applyer struct { Applyer struct {
UserId string `xml:"UserId" json:"userId,omitempty"` UserId string `xml:"UserId"`
} `xml:"Applyer" json:"applyer"` } `xml:"Applyer"`
} `xml:"ApprovalInfo" json:"approvalInfo"` } `xml:"ApprovalInfo"`
SendPicsInfo struct { SendPicsInfo struct {
Count int32 `xml:"Count" json:"count,omitempty"` Count int32 `xml:"Count"`
PicList []EventPic `xml:"PicList>item" json:"picList,omitempty"` PicList []EventPic `xml:"PicList>item"`
} `xml:"SendPicsInfo" json:"sendPicsInfo"` } `xml:"SendPicsInfo"`
SendLocationInfo struct { SendLocationInfo struct {
LocationX float64 `xml:"Location_X" json:"locationX,omitempty"` LocationX float64 `xml:"Location_X"`
LocationY float64 `xml:"Location_Y" json:"locationY,omitempty"` LocationY float64 `xml:"Location_Y"`
Scale float64 `xml:"Scale" json:"scale,omitempty"` Scale float64 `xml:"Scale"`
Label string `xml:"Label" json:"label,omitempty"` Label string `xml:"Label"`
Poiname string `xml:"Poiname" json:"poiname,omitempty"` Poiname string `xml:"Poiname"`
} `json:"sendLocationInfo"` }
// 第三方平台相关 // 第三方平台相关
InfoType InfoType `xml:"InfoType" json:"infoType,omitempty"` InfoType InfoType `xml:"InfoType"`
AppID string `xml:"AppId" json:"appID,omitempty"` AppID string `xml:"AppId"`
ComponentVerifyTicket string `xml:"ComponentVerifyTicket" json:"componentVerifyTicket,omitempty"` ComponentVerifyTicket string `xml:"ComponentVerifyTicket"`
AuthorizerAppid string `xml:"AuthorizerAppid" json:"authorizerAppid,omitempty"` AuthorizerAppid string `xml:"AuthorizerAppid"`
AuthorizationCode string `xml:"AuthorizationCode" json:"authorizationCode,omitempty"` AuthorizationCode string `xml:"AuthorizationCode"`
AuthorizationCodeExpiredTime int64 `xml:"AuthorizationCodeExpiredTime" json:"authorizationCodeExpiredTime,omitempty"` AuthorizationCodeExpiredTime int64 `xml:"AuthorizationCodeExpiredTime"`
PreAuthCode string `xml:"PreAuthCode" json:"preAuthCode,omitempty"` PreAuthCode string `xml:"PreAuthCode"`
// 卡券相关 // 卡券相关
CardID string `xml:"CardId" json:"cardID,omitempty"` CardID string `xml:"CardId"`
RefuseReason string `xml:"RefuseReason" json:"refuseReason,omitempty"` RefuseReason string `xml:"RefuseReason"`
IsGiveByFriend int32 `xml:"IsGiveByFriend" json:"isGiveByFriend,omitempty"` IsGiveByFriend int32 `xml:"IsGiveByFriend"`
FriendUserName string `xml:"FriendUserName" json:"friendUserName,omitempty"` FriendUserName string `xml:"FriendUserName"`
UserCardCode string `xml:"UserCardCode" json:"userCardCode,omitempty"` UserCardCode string `xml:"UserCardCode"`
OldUserCardCode string `xml:"OldUserCardCode" json:"oldUserCardCode,omitempty"` OldUserCardCode string `xml:"OldUserCardCode"`
OuterStr string `xml:"OuterStr" json:"outerStr,omitempty"` OuterStr string `xml:"OuterStr"`
IsRestoreMemberCard int32 `xml:"IsRestoreMemberCard" json:"isRestoreMemberCard,omitempty"` IsRestoreMemberCard int32 `xml:"IsRestoreMemberCard"`
UnionID string `xml:"UnionId" json:"unionID,omitempty"` UnionID string `xml:"UnionId"`
// 内容审核相关 // 内容审核相关
IsRisky bool `xml:"isrisky" json:"isRisky,omitempty"` IsRisky bool `xml:"isrisky"`
ExtraInfoJSON string `xml:"extra_info_json" json:"extraInfoJSON,omitempty"` ExtraInfoJSON string `xml:"extra_info_json"`
TraceID string `xml:"trace_id" json:"traceID,omitempty"` TraceID string `xml:"trace_id"`
StatusCode int `xml:"status_code" json:"statusCode,omitempty"` StatusCode int `xml:"status_code"`
//通讯录同步相关
ChangeType string `xml:"ChangeType"`
UserID string `xml:"UserID"`
Department string `xml:"Department"`
} }
// EventPic 发图事件推送 // EventPic 发图事件推送

View File

@ -1,11 +1,11 @@
package weixin package weixin
type OpenSdk struct { type AppSdk struct {
BaseSdk BaseSdk
} }
func NewOpenSdk(appid, secret string) *OpenSdk { func NewAppSdk(appid, secret string) *AppSdk {
return &OpenSdk{ return &AppSdk{
BaseSdk{ BaseSdk{
appid: appid, appid: appid,
secret: secret, secret: secret,
@ -13,6 +13,6 @@ func NewOpenSdk(appid, secret string) *OpenSdk {
} }
} }
func (o *OpenSdk) GetUserInfoByCode(code string) (*UserInfo, error) { func (o *AppSdk) GetUserInfoByCode(code string) (*UserInfo, error) {
return o.getUserInfoByCode(code, true) return o.getUserInfoByCode(code, true)
} }

View File

@ -2,15 +2,11 @@ package weixin
import ( import (
"fmt" "fmt"
"github.com/tidwall/gjson"
"io" "io"
"net/http" "net/http"
"sync" "sync"
"time" "time"
cache2 "git.u8t.cn/open/gosdk/cache"
"github.com/go-redis/redis"
uuid2 "github.com/google/uuid"
"github.com/tidwall/gjson"
) )
const ( const (
@ -19,7 +15,6 @@ const (
accessTokenUrl string = "https://api.weixin.qq.com/cgi-bin/token" accessTokenUrl string = "https://api.weixin.qq.com/cgi-bin/token"
userPhoneNumberUrl string = "https://api.weixin.qq.com/wxa/business/getuserphonenumber" userPhoneNumberUrl string = "https://api.weixin.qq.com/wxa/business/getuserphonenumber"
getWxACodeUnLimitUrl string = "https://api.weixin.qq.com/wxa/getwxacodeunlimit" getWxACodeUnLimitUrl string = "https://api.weixin.qq.com/wxa/getwxacodeunlimit"
getWxScheme string = "https://api.weixin.qq.com/wxa/generatescheme"
code2UserinfoUrl string = "https://api.weixin.qq.com/sns/userinfo" code2UserinfoUrl string = "https://api.weixin.qq.com/sns/userinfo"
oaQrCodeCreateUrl string = "https://api.weixin.qq.com/cgi-bin/qrcode/create" oaQrCodeCreateUrl string = "https://api.weixin.qq.com/cgi-bin/qrcode/create"
userInfoByOpenid string = "https://api.weixin.qq.com/cgi-bin/user/info" userInfoByOpenid string = "https://api.weixin.qq.com/cgi-bin/user/info"
@ -91,65 +86,6 @@ func (o *BaseSdk) getAccessToken() (string, error) {
return o.accessToken, nil return o.accessToken, nil
} }
// redis share version
func (o *BaseSdk) getAccessToken2() (string, error) {
redix := o.getRedis()
tokenKey := fmt.Sprintf("weixin_oa_access_token_v2_%s", o.appid)
lockKey := fmt.Sprintf("get_access_token_unique_%s", o.appid)
uuid := uuid2.New().String()
// get token
token := redix.Get(tokenKey).Val()
if token != "" {
return token, nil
}
// setEX and expire
// if not set key. wait 200ms and get again and retry 5 times
if ok := redix.SetNX(lockKey, uuid, 10*time.Second).Val(); !ok {
for i := range 5 {
time.Sleep(time.Duration(200*(i+2)) * time.Millisecond)
if token = redix.Get(tokenKey).Val(); token != "" {
return token, nil
}
}
return "", fmt.Errorf("<get access_token v2> wait 5 times,but still not get token")
}
defer func() {
if redix.Get(lockKey).Val() == uuid {
redix.Del(lockKey)
}
}()
// get new token
url := fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", accessTokenUrl, o.appid, o.secret)
res, err := http.Get(url)
if err != nil {
return "", err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
g := gjson.ParseBytes(body)
token = g.Get("access_token").String()
if token == "" {
return "", fmt.Errorf("%d:%s", g.Get("errcode").Int(), g.Get("errmsg").String())
}
expire := g.Get("expires_in").Int() - 10
redix.Set(tokenKey, token, time.Second*time.Duration(expire))
return token, nil
}
func (o *BaseSdk) getRedis() *redis.Client {
return cache2.GetRedis()
}
func (o *BaseSdk) getUserInfoByCode(code string, auth bool) (*UserInfo, error) { func (o *BaseSdk) getUserInfoByCode(code string, auth bool) (*UserInfo, error) {
url := fmt.Sprintf("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code", url := fmt.Sprintf("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
code2AccessTokenUrl, o.appid, o.secret, code) code2AccessTokenUrl, o.appid, o.secret, code)

View File

@ -39,9 +39,6 @@ func (o *MpSdk) GetUserInfoByJsCode(code string) (*UserInfo, error) {
userInfo := new(UserInfo) userInfo := new(UserInfo)
g := gjson.ParseBytes(body) g := gjson.ParseBytes(body)
if g.Get("errcode").Int() != 0 {
return nil, errors.New(string(body))
}
userInfo.Openid = g.Get("openid").String() userInfo.Openid = g.Get("openid").String()
userInfo.Unionid = g.Get("unionid").String() userInfo.Unionid = g.Get("unionid").String()
return userInfo, nil return userInfo, nil
@ -111,43 +108,3 @@ func (o *MpSdk) GetUnlimitedQRCode(params map[string]interface{}) ([]byte, error
return body, nil return body, nil
} }
func (o *MpSdk) GetScheme(params map[string]interface{}) (string, error) {
newParams := make(map[string]interface{})
jump_wxa := make(map[string]interface{})
if _, ok := params["env_version"]; !ok {
jump_wxa["env_version"] = "release"
} else {
jump_wxa["env_version"] = cast.ToString(params["env_version"])
}
jump_wxa["path"] = cast.ToString(params["path"])
jump_wxa["query"] = cast.ToString(params["query"])
newParams["jump_wxa"] = jump_wxa
newParams["expire_interval"] = 30
newParams["expire_type"] = 1
newParams["is_expire"] = true
marshal, _ := json.Marshal(newParams)
accessToken, err := o.getAccessToken()
if err != nil {
return "", err
}
url := fmt.Sprintf("%s?access_token=%s", getWxScheme, accessToken)
res, _ := http.Post(url, "application/json", bytes.NewBuffer(marshal))
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
if err != nil {
return "", err
}
g := gjson.ParseBytes(body)
errcode := g.Get("errcode").Int()
if errcode != 0 {
return "", fmt.Errorf("%d:%s", errcode, g.Get("errmsg"))
}
return g.Get("openlink").String(), nil
}

View File

@ -4,12 +4,11 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/tidwall/gjson"
"io" "io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"time" "time"
"github.com/tidwall/gjson"
) )
type OaSdk struct { type OaSdk struct {
@ -36,7 +35,7 @@ func (o *OaSdk) GetQrCode(sceneStr string) (string, error) {
} }
marshal, _ := json.Marshal(params) marshal, _ := json.Marshal(params)
accessToken, err := o.getAccessToken2() accessToken, err := o.getAccessToken()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -69,7 +68,7 @@ func (o *OaSdk) GetUserInfoByCodeNoAuth(code string) (*UserInfo, error) {
} }
func (o *OaSdk) GetUserInfoByOpenid(openid string) (*UserInfo, error) { func (o *OaSdk) GetUserInfoByOpenid(openid string) (*UserInfo, error) {
accessToken, err := o.getAccessToken2() accessToken, err := o.getAccessToken()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -116,7 +115,7 @@ func (o *OaSdk) UploadFileByByte(file []byte, ext string) (string, string, error
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
token, err := o.getAccessToken2() token, err := o.getAccessToken()
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@ -156,7 +155,7 @@ func (o *OaSdk) UploadFileByByte(file []byte, ext string) (string, string, error
} }
func (o *OaSdk) CreateMenu(buttons []*OaMenuButton) error { func (o *OaSdk) CreateMenu(buttons []*OaMenuButton) error {
accessToken, err := o.getAccessToken2() accessToken, err := o.getAccessToken()
if err != nil { if err != nil {
return err return err
} }
@ -183,7 +182,7 @@ func (o *OaSdk) CreateMenu(buttons []*OaMenuButton) error {
} }
func (o *OaSdk) DeleteMenu() error { func (o *OaSdk) DeleteMenu() error {
accessToken, err := o.getAccessToken2() accessToken, err := o.getAccessToken()
if err != nil { if err != nil {
return err return err
} }