diff --git a/base/util/http.go b/base/util/http.go new file mode 100644 index 0000000..4c28732 --- /dev/null +++ b/base/util/http.go @@ -0,0 +1,79 @@ +package util + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "time" +) + +// Get 请求 link:请求url +func HttpGet(link string, header map[string]string) ([]byte, error) { + client := &http.Client{Timeout: 20 * time.Second} + + 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) + } + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%d:%s", resp.StatusCode, resp.Status) + } + return io.ReadAll(resp.Body) +} + +func Donwload(link string, localFile string) error { + res, err := http.Get(link) + if err != nil { + return err + } + f, err := os.Create(localFile) + if err != nil { + return err + } + io.Copy(f, res.Body) + return nil +} + +// 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 + } + req.Header.Add("Content-Type", "application/json") + if header != nil { + for k, v := range header { + req.Header.Set(k, v) + } + } + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%d:%s", resp.StatusCode, resp.Status) + } + return io.ReadAll(resp.Body) +} diff --git a/common/config/config.go b/common/config/config.go index d5097d9..604d800 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -35,10 +35,21 @@ type Redis struct { Password string `toml:"password"` } +type QyWeixin struct { + Corpid string `toml:"corpid"` + CheckinAgent string `toml:"checkin_agent"` + CheckinSecret string `toml:"checkin_secret"` + EnterpriseAgent string `toml:"enterprise_agent"` + EnterpriseSecret string `toml:"enterprise_secret"` + HrAgent string `toml:"hr_agent"` + HrSecret string `toml:"hr_secret"` +} + type Config struct { - Server *Server `toml:"server"` - Mysql *Mysql `toml:"mysql"` - Redis *Redis `toml:"redis"` + Server *Server `toml:"server"` + Mysql *Mysql `toml:"mysql"` + Redis *Redis `toml:"redis"` + QyWeixin *QyWeixin `toml:"qyweixin"` } func GetEnv() string { diff --git a/common/dao/checkin.go b/common/dao/checkin.go new file mode 100644 index 0000000..f204f16 --- /dev/null +++ b/common/dao/checkin.go @@ -0,0 +1,67 @@ +package dao + +import ( + "enterprise/common/model" + "gorm.io/gorm" + "time" +) + +type CheckinDao struct { +} + +func NewCheckinDao() *CheckinDao { + return &CheckinDao{} +} + +func (d *CheckinDao) TableName() string { + return "checkin" +} + +func (d *CheckinDao) Create(o *model.Checkin) (int64, error) { + o.CreateTime = time.Now().Unix() + res := GetDB().Table(d.TableName()).Create(o) + return o.Id, res.Error +} + +func (d *CheckinDao) Update(o *model.Checkin) error { + o.UpdateTime = time.Now().Unix() + tx := GetDB().Table(d.TableName()) + res := tx.Save(o) + return res.Error +} + +func (d *CheckinDao) Delete(id int64) error { + res := GetDB().Table(d.TableName()).Delete(&model.Checkin{}, id) + return res.Error +} + +func (d *CheckinDao) Get(id int64) (*model.Checkin, error) { + var u model.Checkin + tx := GetDB().Table(d.TableName()) + tx = tx.Where("id = ?", id) + res := tx.First(&u) + if res.Error == gorm.ErrRecordNotFound { + return nil, nil + } + + if res.Error != nil { + return nil, res.Error + } + return &u, nil +} + +func (d *CheckinDao) GetByDay(userId, day string) (*model.Checkin, error) { + var u model.Checkin + tx := GetDB().Table(d.TableName()) + tx = tx.Where("user_id = ?", userId) + tx = tx.Where("day = ?", day) + res := tx.First(&u) + if res.Error == gorm.ErrRecordNotFound { + return nil, nil + } + + if res.Error != nil { + return nil, res.Error + } + return &u, nil +} diff --git a/common/dao/dao.go b/common/dao/dao.go new file mode 100644 index 0000000..7ef5c83 --- /dev/null +++ b/common/dao/dao.go @@ -0,0 +1,15 @@ +package dao + +import "gorm.io/gorm" + +var ( + db *gorm.DB +) + +func SetDB(d *gorm.DB) { + db = d +} + +func GetDB() *gorm.DB { + return db +} diff --git a/common/global/db.go b/common/global/db.go index e96322f..64972b9 100644 --- a/common/global/db.go +++ b/common/global/db.go @@ -3,6 +3,7 @@ package global import ( "context" "enterprise/common/config" + "enterprise/common/dao" "fmt" log "github.com/sirupsen/logrus" "gorm.io/driver/mysql" @@ -67,6 +68,7 @@ func initDB() error { return err } db.Logger = &DBLogger{threshold: int64(2000)} + dao.SetDB(db) return nil } diff --git a/common/model/checkin.go b/common/model/checkin.go new file mode 100644 index 0000000..5445494 --- /dev/null +++ b/common/model/checkin.go @@ -0,0 +1,11 @@ +package model + +type Checkin struct { + Id int64 + UserId string + Day string + StartTime int64 + EndTime int64 + CreateTime int64 + UpdateTime int64 +} diff --git a/common/model/model.go b/common/model/model.go deleted file mode 100644 index 8b53790..0000000 --- a/common/model/model.go +++ /dev/null @@ -1 +0,0 @@ -package model diff --git a/common/qyweixin/checkin.go b/common/qyweixin/checkin.go new file mode 100644 index 0000000..caedba2 --- /dev/null +++ b/common/qyweixin/checkin.go @@ -0,0 +1,7 @@ +package qyweixin + +type UserCheckIn struct { + UserId string + StartTime int64 + EndTime int64 +} diff --git a/common/qyweixin/qyweixin.go b/common/qyweixin/qyweixin.go new file mode 100644 index 0000000..c7fca9f --- /dev/null +++ b/common/qyweixin/qyweixin.go @@ -0,0 +1,135 @@ +package qyweixin + +import ( + "encoding/json" + butil "enterprise/base/util" + "errors" + "fmt" + log "github.com/sirupsen/logrus" + "github.com/smbrave/goutil" + "github.com/spf13/cast" + "gorm.io/gorm/utils" + "time" +) + +var ( + urlGetToken = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" + urlGetCheckinRlue = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcorpcheckinoption" + urlGetCheckinData = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata" +) + +type QyWeixin struct { + corpId string + secret string + token string + tokenExpire int64 +} + +func NewQyWeixin(corpId, secret string) *QyWeixin { + return &QyWeixin{ + corpId: corpId, + secret: secret, + tokenExpire: 0, + } +} + +func (q *QyWeixin) refreshToken() error { + if time.Now().Unix() <= q.tokenExpire-600 { + return nil + } + + reqUrl := fmt.Sprintf("%s?corpid=%s&corpsecret=%s", urlGetToken, q.corpId, q.secret) + rspBody, err := butil.HttpGet(reqUrl, nil) + if err != nil { + log.Errorf("http url[%s] error :%s", reqUrl, err.Error()) + return err + } + result := make(map[string]interface{}) + if err := json.Unmarshal(rspBody, &result); err != nil { + log.Errorf("http url[%s] result[%s] error :%s", reqUrl, string(rspBody), err.Error()) + return err + } + if cast.ToInt(result["errcode"]) != 0 { + log.Errorf("http url[%s] result[%s] error ", reqUrl, string(rspBody)) + return errors.New(string(rspBody)) + } + + q.token = cast.ToString(result["access_token"]) + q.tokenExpire = time.Now().Unix() + cast.ToInt64(result["expires_in"]) + return nil +} + +func (q *QyWeixin) GetCheckinEmployee(groupIds []string) ([]string, error) { + if err := q.refreshToken(); err != nil { + return nil, err + } + + reqUrl := fmt.Sprintf("%s?access_token=%s", urlGetCheckinRlue, q.token) + rspBody, err := butil.HttpPostJson(reqUrl, nil, []byte("{}")) + if err != nil { + return nil, err + } + result := make(map[string]interface{}) + if err := json.Unmarshal(rspBody, &result); err != nil { + log.Errorf("http url[%s] result[%s] error :%s", reqUrl, string(rspBody), err.Error()) + return nil, err + } + if cast.ToInt(result["errcode"]) != 0 { + log.Errorf("http url[%s] result[%s] error ", reqUrl, string(rspBody)) + return nil, errors.New(string(rspBody)) + } + + resultUser := make([]string, 0) + groups := cast.ToSlice(result["group"]) + for _, group := range groups { + g := cast.ToStringMap(group) + if !utils.Contains(groupIds, cast.ToString(g["groupid"])) { + continue + } + ranges := cast.ToStringMap(g["range"]) + userid := cast.ToStringSlice(ranges["userid"]) + resultUser = append(resultUser, userid...) + } + return resultUser, nil +} + +func (q *QyWeixin) GetCheckinData(day, userId string) (*UserCheckIn, error) { + if err := q.refreshToken(); err != nil { + return nil, err + } + dayTime, _ := time.ParseInLocation("2006-01-02", day, time.Local) + + reqData := make(map[string]interface{}) + reqData["opencheckindatatype"] = 1 + reqData["starttime"] = dayTime.Unix() + reqData["endtime"] = dayTime.Unix() + 86400 + reqData["useridlist"] = []string{userId} + reqUrl := fmt.Sprintf("%s?access_token=%s", urlGetCheckinData, q.token) + rspBody, err := butil.HttpPostJson(reqUrl, nil, []byte(goutil.EncodeJSON(reqData))) + if err != nil { + return nil, err + } + result := make(map[string]interface{}) + if err := json.Unmarshal(rspBody, &result); err != nil { + log.Errorf("http url[%s] result[%s] error :%s", reqUrl, string(rspBody), err.Error()) + return nil, err + } + if cast.ToInt(result["errcode"]) != 0 { + log.Errorf("http url[%s] result[%s] error ", reqUrl, string(rspBody)) + return nil, errors.New(string(rspBody)) + } + + checkindatas := cast.ToSlice(result["checkindata"]) + userData := new(UserCheckIn) + userData.UserId = userId + for _, checkdata := range checkindatas { + c := cast.ToStringMap(checkdata) + checkinType := cast.ToString(c["checkin_type"]) + if checkinType == "上班打卡" { + userData.StartTime = cast.ToInt64(c["checkin_time"]) + } else if checkinType == "下班打卡" { + userData.EndTime = cast.ToInt64(c["checkin_time"]) + } + } + return userData, nil +} diff --git a/conf/server.conf.dev b/conf/server.conf.dev index e69de29..56becad 100644 --- a/conf/server.conf.dev +++ b/conf/server.conf.dev @@ -0,0 +1,25 @@ +[server] +address = "0.0.0.0:9283" +#0:PAINC 1:FATAL 2:ERROR 3:WARNING 4:INFO 5:DEBUG 6:TRACE +log_level = 6 + +[mysql] +host = "100.100.199.74" +port = 3306 +user = "enterprise" +pass = "QIDAQABo4G5MIG2MAkG" +db = "enterprise" + +[redis] +addr="127.0.0.1:6379" +db=0 +password="" + +[qyweixin] +corpid = "ww43c49db2e88a17f8" +hr_agent = "3010185" +hr_secret = "Ko2UQWZPbdM9N1snukp_1CT_3J7CcReyPAzl3ww2xoo" +enterprise_agent = "1000009" +enterprise_secret = "oMB24UhKe50-XPTg7vhnwoTuhEXaq5XeiHPAUtF4hOs" +checkin_agent = "3010011" +checkin_secret = "6ljYNGt4DonZLmr9SCtgkTlOvtqmsOchBrTWwGl_GpU" diff --git a/conf/server.conf.prod b/conf/server.conf.prod index d0e5624..b5aa94e 100644 --- a/conf/server.conf.prod +++ b/conf/server.conf.prod @@ -6,11 +6,15 @@ log_level = 6 [mysql] host = "127.0.0.1" port = 3306 -user = "unify2" -pass = "MDE2LCJIYXNoSWQiOjY" -db = "unify2" +user = "enterprise" +pass = "QIDAQABo4G5MIG2MAkG" +db = "enterprise" [redis] addr="127.0.0.1:6379" db=0 password="" + +[qyweixin] +corpid = "ww43c49db2e88a17f8" +checkin_secret = "6ljYNGt4DonZLmr9SCtgkTlOvtqmsOchBrTWwGl_GpU" diff --git a/go.mod b/go.mod index 5f56f53..dc67d26 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,7 @@ module enterprise go 1.18 require ( - github.com/ArtisanCloud/PowerWeChat/v3 v3.0.55 - github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9 + github.com/go-co-op/gocron v1.31.0 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/mitchellh/mapstructure v1.5.0 github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 @@ -17,31 +16,22 @@ require ( ) require ( - github.com/ArtisanCloud/PowerLibs/v3 v3.0.12 // indirect - github.com/ArtisanCloud/PowerSocialite/v3 v3.0.6 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect - github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/lestrrat-go/strftime v1.0.6 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/redis/go-redis/v9 v9.0.3 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.21.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/worker/checkin.go b/worker/checkin.go index 4df0094..014eb9a 100644 --- a/worker/checkin.go +++ b/worker/checkin.go @@ -1 +1,56 @@ package worker + +import ( + "enterprise/common/config" + "enterprise/common/dao" + "enterprise/common/model" + "enterprise/common/qyweixin" + log "github.com/sirupsen/logrus" +) + +func SyncCheckin(day string) error { + cfg := config.GetConfig() + qyw := qyweixin.NewQyWeixin(cfg.QyWeixin.Corpid, cfg.QyWeixin.CheckinSecret) + users, err := qyw.GetCheckinEmployee([]string{"1"}) + if err != nil { + return err + } + + checkinDao := dao.NewCheckinDao() + for _, user := range users { + isNew := false + checkin, err := checkinDao.GetByDay(user, day) + if err != nil { + log.Errorf("db error :%s", err.Error()) + continue + } + if checkin == nil { + checkin = new(model.Checkin) + checkin.Day = day + checkin.UserId = user + isNew = true + } + + realCheckin, err := qyw.GetCheckinData(day, user) + if err != nil { + log.Errorf("qyweixin get checkin error :%s", err.Error()) + continue + } + if realCheckin.StartTime != 0 { + checkin.StartTime = realCheckin.StartTime + } + if realCheckin.EndTime != 0 { + checkin.EndTime = realCheckin.EndTime + } + if isNew { + _, err = checkinDao.Create(checkin) + } else { + err = checkinDao.Update(checkin) + } + if err != nil { + log.Errorf("db error :%s", err.Error()) + } + } + + return nil +} diff --git a/worker/worker.go b/worker/worker.go index 13bfdbc..3d736d8 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -1,5 +1,15 @@ package worker +import ( + "github.com/go-co-op/gocron" + "time" +) + func Init() error { + timezone, _ := time.LoadLocation("Asia/Shanghai") + cron := gocron.NewScheduler(timezone) + cron.Every(10).Minute().Do(func() { + go SyncCheckin(time.Now().Format("2006-01-02")) + }) return nil }