This commit is contained in:
jiangyong 2024-04-27 23:12:44 +08:00
parent 9272b11167
commit dd64f4f0ee
10 changed files with 278 additions and 193 deletions

View File

@ -2,9 +2,9 @@ package main
import (
"film/config"
"film/model"
"film/global"
"film/worker"
"fmt"
"film/worker/nowcar"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
log "github.com/sirupsen/logrus"
@ -44,19 +44,24 @@ func initLog() {
log.SetLevel(log.Level(cfg.Server.LogLevel))
}
func main2() {
config.LoadServerConfig()
config.LoadDadiConfig()
config.LoadHahaConfig()
initLog()
global.InitDB()
work := &worker.Worker{}
work.Run()
}
func main() {
config.LoadServerConfig()
config.LoadDadiConfig()
config.LoadHahaConfig()
initLog()
cfg := config.GetConfig()
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", cfg.Mysql.User,
cfg.Mysql.Pass, cfg.Mysql.Host, cfg.Mysql.Port, cfg.Mysql.Db)
_, err := model.Init(dsn)
if err != nil {
panic(err)
}
work := &worker.Worker{}
work.Run()
global.InitDB()
nowcar.Sync()
}

View File

@ -1,22 +1,21 @@
[server]
address = "0.0.0.0:9263"
#0:PAINC 1:FATAL 2:ERROR 3:WARNING 4:INFO 5:DEBUG 6:TRACE
log_level = 4
log_level = 5
[mysql]
host = "100.118.29.74"
port = 3307
host = "14.22.116.197"
port = 9305
user = "film"
pass = "7VCfJx7H8MymUm"
db = "film"
[weixin]
qiye_appid = "ww288920bd3e8c92b2"
qiye_secret = "3aqE6LAEKLiGtYBtX7TZm0uY_YD52w3R20AQirSICXY"
qiye_agent = "1000013"
receiver = ["jiangyong", "zhangping"]
[film]
haha_token = "d6847dfbd7b79bc18709e0311fd7813b"
dadi_token = "321566:7b84a0b1-832a-4492-a96a-23002d6c8715"

68
dao/nowcar.go Normal file
View File

@ -0,0 +1,68 @@
package dao
import (
"film/global"
"film/model"
"gorm.io/gorm"
)
type NowCarDao struct {
}
func NewNowCarDao() *NowCarDao {
return &NowCarDao{}
}
func (d *NowCarDao) TableName() string {
return "nowcar"
}
func (d *NowCarDao) Create(o *model.Nowcar) (int64, error) {
res := global.GetDB().Table(d.TableName()).Create(o)
return o.Id, res.Error
}
func (d *NowCarDao) Update(o *model.Nowcar) error {
tx := global.GetDB().Table(d.TableName())
res := tx.Save(o)
return res.Error
}
func (d *NowCarDao) GetStart(shopName, workstation string, startTime int64) (*model.Nowcar, error) {
var u model.Nowcar
tx := global.GetDB().Table(d.TableName())
tx = tx.Where("shop_name = ?", shopName)
tx = tx.Where("workstation = ?", workstation)
tx = tx.Where("start_time = ?", startTime)
tx = tx.Order("start_time DESC")
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 *NowCarDao) GetBusy(shopName, workstation string) (*model.Nowcar, error) {
var u model.Nowcar
tx := global.GetDB().Table(d.TableName())
tx = tx.Where("shop_name = ?", shopName)
tx = tx.Where("workstation = ?", workstation)
tx = tx.Where("start_time != 0")
tx = tx.Where("end_time = 0")
tx = tx.Order("start_time DESC")
res := tx.First(&u)
if res.Error == gorm.ErrRecordNotFound {
return nil, nil
}
if res.Error != nil {
return nil, res.Error
}
return &u, nil
}

View File

@ -1,7 +1,9 @@
package model
package global
import (
"context"
"film/config"
"fmt"
log "github.com/sirupsen/logrus"
"gorm.io/driver/mysql"
"gorm.io/gorm"
@ -13,16 +15,13 @@ var (
db *gorm.DB = nil
)
func SetDb(b *gorm.DB) {
db = b
}
func GetOrm() *gorm.DB {
func GetDB() *gorm.DB {
return db
}
type DBLogger struct {
level logger.LogLevel
level logger.LogLevel
threshold int64
}
func (d *DBLogger) LogMode(level logger.LogLevel) logger.Interface {
@ -48,27 +47,31 @@ func (d *DBLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql st
if err != nil && err != gorm.ErrRecordNotFound {
log.Errorf("[SQL]sql=%s affect=%d cost=%dms error=%v", sql, affects, time.Since(begin).Milliseconds(), err)
} else {
if time.Since(begin).Milliseconds() > 1000 {
log.Errorf("[SQL]sql=%s affect=%d cost=%dms", sql, affects, time.Since(begin).Milliseconds())
if time.Since(begin).Milliseconds() > d.threshold {
log.Warnf("[SQL]sql=%s affect=%d cost=%dms", sql, affects, time.Since(begin).Milliseconds())
} else {
log.Tracef("[SQL]sql=%s affect=%d cost=%dms", sql, affects, time.Since(begin).Milliseconds())
log.Debugf("[SQL]sql=%s affect=%d cost=%dms", sql, affects, time.Since(begin).Milliseconds())
}
}
}
func Init(dsn string) (*gorm.DB, error) {
func InitDB() error {
cfg := config.GetConfig()
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", cfg.Mysql.User,
cfg.Mysql.Pass, cfg.Mysql.Host, cfg.Mysql.Port, cfg.Mysql.Db)
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Errorf("open dsn[%s] error[%s]", dsn, err)
return nil, err
return err
}
db.Logger = &DBLogger{}
return db, err
db.Logger = &DBLogger{threshold: 1000}
return nil
}
func Ping() error {
func DBPing() error {
d, err := db.DB()
if err != nil {
return err

3
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/smbrave/goutil v0.0.0-20230208141215-e3360c3bfd1b
github.com/spf13/cast v1.5.0
github.com/spf13/viper v1.15.0
github.com/tidwall/gjson v1.17.1
gitlab.com/jiangyong27/gobase v1.0.23
gorm.io/driver/mysql v1.4.7
gorm.io/gorm v1.24.6
@ -38,6 +39,8 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect

9
model/nowcar.go Normal file
View File

@ -0,0 +1,9 @@
package model
type Nowcar struct {
Id int64
ShopName string
Workstation string
StartTime int64
EndTime int64
}

View File

@ -1,6 +1,7 @@
package model
import (
"film/global"
"fmt"
"github.com/smbrave/goutil"
"gorm.io/gorm"
@ -48,13 +49,13 @@ func (o *Order) String() string {
func AddOrder(o *Order) (int64, error) {
o.CreateTime = time.Now().Unix()
o.CreateDate = time.Now().Format("2006-01-02")
res := db.Table(tableNameOrder).Create(o)
res := global.GetDB().Table(tableNameOrder).Create(o)
return o.Id, res.Error
}
func GetOrder(id int64) (*Order, error) {
var u Order
tx := db.Table(tableNameOrder)
tx := global.GetDB().Table(tableNameOrder)
tx = tx.Where("id = ?", id)
res := tx.First(&u)
if res.Error == gorm.ErrRecordNotFound {
@ -69,7 +70,7 @@ func GetOrder(id int64) (*Order, error) {
func UpdateOrder(o *Order) error {
o.UpdateTime = time.Now().Unix()
tx := db.Table(tableNameOrder)
tx := global.GetDB().Table(tableNameOrder)
res := tx.Save(o)
return res.Error
}

View File

@ -7,9 +7,7 @@ import (
"film/model"
"film/worker/common"
"fmt"
"github.com/go-co-op/gocron"
log "github.com/sirupsen/logrus"
"github.com/smbrave/goutil"
"github.com/spf13/cast"
"io"
"net/http"
@ -18,40 +16,26 @@ import (
)
type SyncOrderConfig struct {
Token string
NewOrder chan *model.Order
UpdateOrder chan *model.Order
SyncInterval int
Token string
NewOrder chan *model.Order
UpdateOrder chan *model.Order
}
type SyncOrder struct {
token string
syncInterval int
newOrder chan *model.Order
updateOrder chan *model.Order
token string
newOrder chan *model.Order
updateOrder chan *model.Order
}
func NewSyncOrder(cfg *SyncOrderConfig) *SyncOrder {
return &SyncOrder{
token: cfg.Token,
newOrder: cfg.NewOrder,
updateOrder: cfg.UpdateOrder,
syncInterval: goutil.If(cfg.SyncInterval == 0, 30, cfg.SyncInterval),
token: cfg.Token,
newOrder: cfg.NewOrder,
updateOrder: cfg.UpdateOrder,
}
}
func (s *SyncOrder) Sync() {
timezone, _ := time.LoadLocation("Asia/Shanghai")
cron := gocron.NewScheduler(timezone)
cron.Every(s.syncInterval).Seconds().Do(func() {
s.run()
})
cron.StartAsync()
}
func (s *SyncOrder) run() {
orderUrl := "https://hahapiao.cn/api/Synchro/pcToList"
body, err := s.httpPost(orderUrl)
if err != nil {

130
worker/nowcar/nowcar.go Normal file
View File

@ -0,0 +1,130 @@
package nowcar
import (
"bytes"
"film/dao"
"film/model"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"io"
"net/http"
"net/url"
"time"
)
func Sync() {
devIds, err := getDevId()
if err != nil {
return
}
for _, devId := range devIds {
if devId == "" {
continue
}
getInfo(devId)
}
}
func getInfo(devId string) ([]string, error) {
reqUrl := "https://wx.wdfcar.com/NowZiZhuXiCheApp/userSiteOrder/getInfo"
params := make(url.Values)
params.Set("user_lng", "106.55072784423828")
params.Set("user_lat", "29.56471061706543")
params.Set("dev_id", devId)
params.Set("lt-id", "1811")
params.Set("lt-token", "c5e139a7e118b346149750489c57560b")
resp, err := http.Post(reqUrl, "application/x-www-form-urlencoded",
bytes.NewBuffer([]byte(params.Encode())))
if err != nil {
log.Error("http poset error :%s", err.Error())
return nil, err
}
defer resp.Body.Close()
rspbody, _ := io.ReadAll(resp.Body)
code := gjson.GetBytes(rspbody, "code").Int()
if code != 0 {
log.Errorf("devId:%s rspbody error :%s", devId, string(rspbody))
return nil, fmt.Errorf("code is :%d", code)
}
data := gjson.GetBytes(rspbody, "data")
shopName := data.Get("addr_name").String()
doors := data.Get("doors").Array()
m := new(model.Nowcar)
m.ShopName = shopName
for _, door := range doors {
workstation := door.Get("number").String()
status := door.Get("status").String()
if status != "空闲" {
startTime, _ := time.ParseInLocation("2006-01-02 15:04:05", status, time.Local)
m1, err := dao.NewNowCarDao().GetStart(shopName, workstation, startTime.Unix())
if err != nil {
log.Errorf("db error :%s", err.Error())
continue
}
if m1 != nil {
continue
}
m.Id = 0
m.Workstation = workstation
m.StartTime = startTime.Unix()
dao.NewNowCarDao().Create(m)
} else {
m2, err := dao.NewNowCarDao().GetBusy(shopName, workstation)
if err != nil {
log.Errorf("db error :%s", err.Error())
continue
}
if m2 == nil || m2.EndTime != 0 {
continue
}
m2.EndTime = time.Now().Unix()
err = dao.NewNowCarDao().Update(m2)
if err != nil {
log.Errorf("db error :%s", err.Error())
}
}
}
return nil, nil
}
func getDevId() ([]string, error) {
reqUrl := "https://wx.wdfcar.com/NowZiZhuXiCheApp/userSiteOrder/nearSite"
params := make(url.Values)
params.Set("user_lng", "106.55072784423828")
params.Set("user_lat", "29.56471061706543")
resp, err := http.Post(reqUrl, "application/x-www-form-urlencoded",
bytes.NewBuffer([]byte(params.Encode())))
if err != nil {
log.Error("http poset error :%s", err.Error())
return nil, err
}
defer resp.Body.Close()
rspbody, _ := io.ReadAll(resp.Body)
code := gjson.GetBytes(rspbody, "code").Int()
if code != 0 {
log.Errorf("rspbody error :%s", string(rspbody))
return nil, fmt.Errorf("code is :%d", code)
}
devids := make([]string, 0)
results := gjson.GetBytes(rspbody, "data").Array()
for _, res := range results {
devId := res.Get("dev_id").String()
shopName := res.Get("addr_name").String()
log.Infof("shop=%s devid=%s", shopName, devId)
devids = append(devids, devId)
}
return devids, nil
}

View File

@ -1,15 +1,12 @@
package worker
import (
"film/base/util"
"film/config"
"film/model"
"film/worker/common"
"film/worker/dadi"
"film/worker/haha"
"fmt"
"film/worker/nowcar"
"github.com/go-co-op/gocron"
log "github.com/sirupsen/logrus"
"github.com/smbrave/goutil"
"gitlab.com/jiangyong27/gobase/wxapi"
"strings"
"time"
@ -18,9 +15,7 @@ import (
type Worker struct {
qyClient *wxapi.WxQiye
qyImpClient *wxapi.WxQiye
dadiProcessor common.Processor
hahaProcessor *haha.Processor
notifyMap map[string]int64
}
func (w *Worker) initQyWeixin() {
@ -37,151 +32,39 @@ func (w *Worker) initQyWeixin() {
})
}
func (w *Worker) getOrderMesasge(order *model.Order) []string {
message := make([]string, 0)
message = append(message, "【哈哈票订单信息】")
message = append(message, fmt.Sprintf("城市:%s", order.CityName))
message = append(message, fmt.Sprintf("影院:%s", order.CinemaName))
message = append(message, fmt.Sprintf("影片:%s", order.MovieName))
message = append(message, fmt.Sprintf("影厅:%s", order.Ting))
message = append(message, fmt.Sprintf("时间:%s", time.Unix(order.ShowTime, 0).Format("2006-01-02 15:04")))
message = append(message, fmt.Sprintf("座位:%s%s%s", order.Seats,
goutil.If(order.IsSeat == 3, "可以调座", "不可调座"),
goutil.If(order.LoverSeat == 1, "情侣座", "普通座")))
message = append(message, fmt.Sprintf("原价:%.2f", float64(order.MaoyanPrice)/100))
message = append(message, fmt.Sprintf("编号:%s", order.OrderNo))
message = append(message, fmt.Sprintf("总金额:%.2f%d", float64(order.TotalPrice)/100, order.SeatNum))
message = append(message, fmt.Sprintf("报价范围:%.2f~%.2f", float64(order.MinPrice)/100, float64(order.MaxPrice)/100))
message = append(message, fmt.Sprintf("支付时间:%s", time.Unix(order.PayTime, 0).Format("2006-01-02 15:04")))
message = append(message, fmt.Sprintf("创建时间:%s", time.Unix(order.OrderCreateTime, 0).Format("2006-01-02 15:04")))
message = append(message, fmt.Sprintf("入库时间:%s", time.Unix(order.CreateTime, 0).Format("2006-01-02 15:04")))
return message
}
func (w *Worker) sendOrderMessage(order *model.Order, message string) {
messages := w.getOrderMesasge(order)
messages = append(messages, "\n", message)
w.qyClient.SendText(config.GetConfig().Weixin.Receiver, strings.Join(messages, "\n"))
}
func (w *Worker) processorDadi(order *model.Order) {
checkInfo, err := w.dadiProcessor.CheckOrder(order)
if err != nil {
log.Errorf("check orrder[%s] error : %s", order.String(), err.Error())
return
}
cfg := config.GetDadiConfig()
bidStatus := false
realCost := int64(float64(checkInfo.TotalRealPrice) * cfg.Discount)
realUnitCost := realCost / int64(order.SeatNum)
bidPrice := realUnitCost + config.GetDadiConfig().BidProfit
if bidPrice < order.MaxPrice {
if err := w.hahaProcessor.BidPrice(order.Id, bidPrice); err != nil {
log.Errorf("BidPrice order[%s] error[%s]", order.String(), err.Error())
if err := w.dadiProcessor.CancelOrder(order, checkInfo); err != nil {
log.Errorf("cancel order[%s] error[%s]", order.OrderNo, err.Error())
}
message := w.getOrderMesasge(order)
message = append(message, "\n", fmt.Sprintf("竞价失败:%s", err.Error()))
w.qyClient.SendText(config.GetConfig().Weixin.Receiver, strings.Join(message, "\n"))
return
}
bidStatus = true
}
// 发送信息
message := w.getOrderMesasge(order)
message = append(message, "\n")
message = append(message, "【大地订单信息】")
message = append(message, fmt.Sprintf("订单原价:%s", util.MoneyFen(checkInfo.TotalOriginPrice)))
message = append(message, fmt.Sprintf("优惠金额:%s", util.MoneyFen(checkInfo.CouponPrice)))
message = append(message, fmt.Sprintf("应付金额:%s", util.MoneyFen(checkInfo.TotalRealPrice)))
message = append(message, fmt.Sprintf("实际成本:%s", util.MoneyFen(realCost)))
message = append(message, fmt.Sprintf("实际单价:%s", util.MoneyFen(realUnitCost)))
message = append(message, "\n")
message = append(message, "【竞价信息】")
message = append(message, fmt.Sprintf("状态:%s", goutil.If(bidStatus, "竞价中", "不可竞价")))
message = append(message, fmt.Sprintf("出价:%s", util.MoneyFen(bidPrice)))
w.qyClient.SendText(config.GetConfig().Weixin.Receiver, strings.Join(message, "\n"))
}
func (w *Worker) processBidWin() {
bidOrders, err := w.hahaProcessor.QueryBidWin()
if err != nil {
return
}
for _, o := range bidOrders {
order, err := model.GetOrder(o.OrderId)
if err != nil {
log.Errorf("get order error : %s", err.Error())
continue
}
nowTime := time.Now().Unix()
if v, ok := w.notifyMap[order.OrderNo]; ok && nowTime-v < 300 {
continue
}
message := w.getOrderMesasge(order)
message = append(message, "\n")
message = append(message, "【竞价成功】")
message = append(message, fmt.Sprintf("出价:%s", util.MoneyFen(o.BidPrice)))
message = append(message, fmt.Sprintf("时间:%s", goutil.TimeToDateTime(o.BidTime)))
if err := w.qyImpClient.SendText(config.GetConfig().Weixin.Receiver, strings.Join(message, "\n")); err == nil {
w.notifyMap[order.OrderNo] = nowTime
}
}
}
func (w *Worker) Run() {
timezone, _ := time.LoadLocation("Asia/Shanghai")
cron := gocron.NewScheduler(timezone)
w.initQyWeixin()
newOrderChan := make(chan *model.Order, 100000)
updateOrderChan := make(chan *model.Order, 100000)
hahaSyncer := haha.NewSyncOrder(&haha.SyncOrderConfig{
Token: config.GetHahaConfig().Token,
NewOrder: newOrderChan,
UpdateOrder: updateOrderChan,
SyncInterval: config.GetHahaConfig().SyncInterval,
Token: config.GetHahaConfig().Token,
NewOrder: newOrderChan,
UpdateOrder: updateOrderChan,
})
hahaSyncer.Sync()
w.notifyMap = make(map[string]int64)
cron.Every(config.GetHahaConfig().SyncInterval).Seconds().Do(func() {
hahaSyncer.Sync()
})
w.hahaProcessor = haha.NewProcessor(&haha.ProcessorConfig{Token: config.GetHahaConfig().Token})
if config.GetDadiConfig().Enable {
dadiProcessor, err := dadi.NewProcessor(&dadi.ProcessorConfig{Token: config.GetDadiConfig().Token})
if err != nil {
panic(err)
}
w.dadiProcessor = dadiProcessor
}
cron.Every(1).Minute().Do(func() {
nowcar.Sync()
})
//order, _ := model.GetOrder(594315)
//_, err := w.dadiProcessor.CreateOrder(order)
//if err != nil {
//log.Errorf("create order[%s] error [%s]", order.String(), err.Error())
//}
//newOrderChan <- order
cron.StartAsync()
bidUploadTimer := time.NewTicker(10 * time.Second)
for {
select {
case order := <-newOrderChan:
if !strings.Contains(order.CinemaName, "大地影院") {
continue
}
if config.GetDadiConfig().Enable {
w.processorDadi(order)
}
case updateOrder := <-updateOrderChan:
log.Tracef("%s update", updateOrder.String())
case <-bidUploadTimer.C:
w.processBidWin()
}
}