From 3cc042ba661f0c23fe92e00a43c5c64135eacd37 Mon Sep 17 00:00:00 2001 From: Leufolium Date: Wed, 3 Jan 2024 16:16:41 +0800 Subject: [PATCH] by Robin at 20230103; add daily statement --- api/consts/consts.go | 6 ++ api/errcode/errcode.go | 8 ++ .../proto/daily_statement_op.go | 68 ++++++++++++++ api/proto/vas/proto/vas.go | 8 ++ app/mix/cmd/main.go | 2 + app/mix/controller/daily_statement_op.go | 37 ++++++++ app/mix/controller/init.go | 7 +- app/mix/dao/mongo.go | 73 +++++++++++++++ app/mix/dao/mysql.go | 47 +++++++++- app/mix/service/cronservice.go | 86 +++++++++++++++++- app/mix/service/logic/daily_statement.go | 65 +++++++++++++ app/mix/service/logic/vas.go | 13 ++- app/mix/service/scripts_service.go | 32 +++++++ app/mix/service/service.go | 15 +++ bizcommon/util/util.go | 12 +++ codecreate/codecreate.go | 56 ++++++------ codecreate/resource/EntityDefine.xlsx | Bin 40118 -> 41355 bytes dbstruct/daily_statement.go | 15 +++ dbstruct/vas_mysql.go | 5 + library/idgenerator/genid.go | 29 ++---- 20 files changed, 523 insertions(+), 61 deletions(-) create mode 100644 api/proto/daily_statement/proto/daily_statement_op.go create mode 100644 app/mix/controller/daily_statement_op.go create mode 100644 app/mix/service/logic/daily_statement.go create mode 100644 app/mix/service/scripts_service.go create mode 100644 dbstruct/daily_statement.go diff --git a/api/consts/consts.go b/api/consts/consts.go index 4b901769..7b005ce9 100644 --- a/api/consts/consts.go +++ b/api/consts/consts.go @@ -72,3 +72,9 @@ const ProductionConfigPath = PackageRootPath + "/etc/mix/mix-prod.yaml" const LocalConfigPath = "/Users/erwin/wishpal/wishpal-ironfan/etc/mix/mix-local.yaml" const ReservedUserIdRegexesConfig = PackageRootPath + "/etc/mix/resource/reg_reserved_user_id_config.xml" + +// H5调用路径 +const H5CallUrl = "/api/streamer/list_ext_by_user_id" + +// 应用上线时间,2024-01-02 14:00:00 +const AppEnterProductionTime = 1704175200 diff --git a/api/errcode/errcode.go b/api/errcode/errcode.go index b61e493f..7f3383e8 100644 --- a/api/errcode/errcode.go +++ b/api/errcode/errcode.go @@ -130,6 +130,9 @@ var ErrCodeMsgMap = map[ErrCode]string{ ErrCodeVeriCodeSendTimesSrvFail: "验证码频次表服务错误", ErrCodeVeriCodeSendTimesNotExist: "验证码频次表不存在", ErrCodeVeriCodeSendTimesReachedDailyUpperbound: "验证码发送次数已达每日上限", + + ErrCodeDailyStatementSrvFail: "每日报表表服务错误", + ErrCodeDailyStatementNotExist: "每日报表表不存在", } const ( @@ -310,6 +313,11 @@ const ( ErrCodeVeriCodeSendTimesNotExist ErrCode = -25002 // 验证码频次表不存在 ErrCodeVeriCodeSendTimesReachedDailyUpperbound ErrCode = -25003 // 验证码发送次数已达每日上限 + // DailyStatement: 26xxx + ErrCodeDailyStatementSrvOk ErrCode = ErrCodeOk + ErrCodeDailyStatementSrvFail ErrCode = -26001 // 每日报表表服务错误 + ErrCodeDailyStatementNotExist ErrCode = -26002 // 每日报表表不存在 + // Media: 60xxx ErrCodeMediaSrvOk ErrCode = ErrCodeOk ErrCodeMediaSrvFail ErrCode = -60001 // 媒体服务错误 diff --git a/api/proto/daily_statement/proto/daily_statement_op.go b/api/proto/daily_statement/proto/daily_statement_op.go new file mode 100644 index 00000000..0421261f --- /dev/null +++ b/api/proto/daily_statement/proto/daily_statement_op.go @@ -0,0 +1,68 @@ +package proto + +import ( + "service/api/base" + "service/dbstruct" +) + +// op 创建 +type OpCreateReq struct { + base.BaseRequest + *dbstruct.DailyStatement +} + +type OpCreateData struct { +} + +type OpCreateResp struct { + base.BaseResponse + Data *OpCreateData `json:"data"` +} + +// op 删除 +type OpDeleteReq struct { + base.BaseRequest + Id int64 `json:"id"` +} + +type OpDeleteData struct { +} + +type OpDeleteResp struct { + base.BaseResponse + Data *OpDeleteData `json:"data"` +} + +// op 更新 +type OpUpdateReq struct { + base.BaseRequest + *dbstruct.DailyStatement +} + +type OpUpdateData struct { +} + +type OpUpdateResp struct { + base.BaseResponse + Data *OpUpdateData `json:"data"` +} + +// op 列表 +type OpListReq struct { + base.BaseRequest + CtUpperBound *int64 `json:"ct_upper_bound"` + CtLowerBound *int64 `json:"ct_lower_bound"` + Offset int `json:"offset"` + Limit int `json:"limit"` +} + +type OpListData struct { + List []*dbstruct.DailyStatement `json:"list"` + Offset int `json:"offset"` + More int `json:"more"` +} + +type OpListResp struct { + base.BaseResponse + Data *OpListData `json:"data"` +} diff --git a/api/proto/vas/proto/vas.go b/api/proto/vas/proto/vas.go index e25dc3e2..ffba78ad 100644 --- a/api/proto/vas/proto/vas.go +++ b/api/proto/vas/proto/vas.go @@ -79,3 +79,11 @@ type GetCHListData struct { Offset int `json:"offset"` More int `json:"more"` } + +// 订单查询 +type GetOrderByStatusReq struct { + base.BaseRequest + OrderStatuses []int32 `json:"order_statuses"` + CtStart *int64 `json:"ct_start"` + CtEnd *int64 `json:"ct_end"` +} diff --git a/app/mix/cmd/main.go b/app/mix/cmd/main.go index ae796b64..caeaa0d7 100644 --- a/app/mix/cmd/main.go +++ b/app/mix/cmd/main.go @@ -69,11 +69,13 @@ func main() { service.DefaultService = service.NewService() service.DefaultConfigService = service.NewConfigService() service.DefaultCronService = service.NewCronService() + service.DefaultScriptsService = service.NewScriptsService() err = service.DefaultService.Init(cfg) if err != nil { msg := fmt.Sprintf("Service init fail, err: %v", err) PrintAndExit(msg) } + service.DefaultCronService.ReadLoggerConfig(cfg.Log) // 连接到图像审核任务 service.DefaultService.ConnectToImageAudit() diff --git a/app/mix/controller/daily_statement_op.go b/app/mix/controller/daily_statement_op.go new file mode 100644 index 00000000..d0c3d474 --- /dev/null +++ b/app/mix/controller/daily_statement_op.go @@ -0,0 +1,37 @@ +package controller + +import ( + "service/api/consts" + "service/api/errcode" + daily_statementproto "service/api/proto/daily_statement/proto" + "service/app/mix/service" + "service/bizcommon/util" + "service/library/logger" + + "github.com/gin-gonic/gin" +) + +func OpGetDailyStatementList(ctx *gin.Context) { + req := ctx.MustGet("client_req").(*daily_statementproto.OpListReq) + + //设置默认页长 + if req.Limit == 0 { + req.Limit = consts.DefaultPageSize + } + + list, ec := service.DefaultService.OpGetDailyStatementList(ctx, req) + if ec != errcode.ErrCodeDailyStatementSrvOk { + logger.Error("OpGetDailyStatementList fail, req: %v, ec: %v", util.ToJson(req), ec) + ReplyErrCodeMsg(ctx, ec) + return + } + + data := &daily_statementproto.OpListData{ + List: list, + Offset: req.Offset + len(list), + } + if len(list) >= req.Limit { + data.More = 1 + } + ReplyOk(ctx, data) +} diff --git a/app/mix/controller/init.go b/app/mix/controller/init.go index 4bc372e3..3eb5c1db 100644 --- a/app/mix/controller/init.go +++ b/app/mix/controller/init.go @@ -23,6 +23,7 @@ import ( callhistoryproto "service/api/proto/callhistory/proto" contact_customer_serviceproto "service/api/proto/contact_customer_service/proto" contact_customer_service_sessionproto "service/api/proto/contact_customer_service_session/proto" + daily_statementproto "service/api/proto/daily_statement/proto" feedbackproto "service/api/proto/feedback/proto" footprintproto "service/api/proto/footprint/proto" loginproto "service/api/proto/login/proto" @@ -354,9 +355,13 @@ func Init(r *gin.Engine) { opSupportWxIdGroup.POST("list", middleware.JSONParamValidator(base.BaseRequest{}), middleware.JwtAuthenticator(), OpGetSupportWxIdList) // 上传媒体失败配置 - opUploadMediaFailConfig := r.Group("/op/upload_media_fail_config", PrepareToC()) + opUploadMediaFailConfig := r.Group("/op/upload_media_fail_config", PrepareOp()) opUploadMediaFailConfig.POST("list", middleware.JSONParamValidator(base.BaseRequest{}), OpGetUploadMediaFailConfigList) + // 每日报表表 + opDailyStatementGroup := r.Group("/op/daily_statement", PrepareOp()) + opDailyStatementGroup.POST("list", middleware.JSONParamValidator(daily_statementproto.OpListReq{}), OpGetDailyStatementList) + // 账号相关 //accountGroup := r.Group("/account") diff --git a/app/mix/dao/mongo.go b/app/mix/dao/mongo.go index 04f3194e..5f8b81d2 100644 --- a/app/mix/dao/mongo.go +++ b/app/mix/dao/mongo.go @@ -18,6 +18,7 @@ import ( callhistoryproto "service/api/proto/callhistory/proto" contact_customer_service_proto "service/api/proto/contact_customer_service/proto" contact_customer_service_sessionproto "service/api/proto/contact_customer_service_session/proto" + daily_statementproto "service/api/proto/daily_statement/proto" feedbackproto "service/api/proto/feedback/proto" footprintproto "service/api/proto/footprint/proto" imageaudittaskproto "service/api/proto/imageaudittask/proto" @@ -139,6 +140,9 @@ const ( DBContactCustomerServiceSession = "contact_customer_service_session" COLContactCustomerServiceSession = "contact_customer_service_session" + + DBDailyStatement = "daily_statement" + COLDailyStatement = "daily_statement" ) // 商品表 @@ -320,6 +324,11 @@ func (m *Mongo) getColContactCustomerServiceSession() *qmgo.Collection { return m.clientMix.Database(DBContactCustomerServiceSession).Collection(COLContactCustomerServiceSession) } +// 每日报表表表 +func (m *Mongo) getColDailyStatement() *qmgo.Collection { + return m.clientMix.Database(DBDailyStatement).Collection(COLDailyStatement) +} + // 商品相关 func (m *Mongo) CreateProduct(ctx *gin.Context, product *dbstruct.Product) error { col := m.getColProduct() @@ -2660,3 +2669,67 @@ func (m *Mongo) GetContactCustomerServiceSessionList(ctx *gin.Context, req *cont } return list, err } + +// 每日报表表相关 +func (m *Mongo) CreateDailyStatement(ctx *gin.Context, daily_statement *dbstruct.DailyStatement) error { + col := m.getColDailyStatement() + _, err := col.InsertOne(ctx, daily_statement) + return err +} + +func (m *Mongo) UpdateDailyStatement(ctx *gin.Context, daily_statement *dbstruct.DailyStatement) error { + col := m.getColDailyStatement() + set := util.EntityToM(daily_statement) + set["ut"] = time.Now().Unix() + up := qmgo.M{ + "$set": set, + } + err := col.UpdateId(ctx, daily_statement.Id, up) + return err +} + +func (m *Mongo) DeleteDailyStatement(ctx *gin.Context, id int64) error { + col := m.getColDailyStatement() + update := qmgo.M{ + "$set": qmgo.M{ + "del_flag": 1, + }, + } + err := col.UpdateId(ctx, id, update) + return err +} + +func (m *Mongo) GetDailyStatementList(ctx *gin.Context, req *daily_statementproto.OpListReq) ([]*dbstruct.DailyStatement, error) { + list := make([]*dbstruct.DailyStatement, 0) + col := m.getColDailyStatement() + query := qmgo.M{ + "del_flag": 0, + } + + filterInClause := []qmgo.M{} + if req.CtLowerBound != nil { + filterInClause = append(filterInClause, qmgo.M{ + "start_time": qmgo.M{ + "$gte": util.DerefInt64(req.CtLowerBound), + }, + }) + } + if req.CtUpperBound != nil { + filterInClause = append(filterInClause, qmgo.M{ + "end_time": qmgo.M{ + "$lte": util.DerefInt64(req.CtUpperBound), + }, + }) + } + + if len(filterInClause) > 0 { + query["$and"] = filterInClause + } + + err := col.Find(ctx, query).Sort("-ct").Skip(int64(req.Offset)).Limit(int64(req.Limit)).All(&list) + if err == qmgo.ErrNoSuchDocuments { + err = nil + return list, err + } + return list, err +} diff --git a/app/mix/dao/mysql.go b/app/mix/dao/mysql.go index f147ce47..8b433e68 100644 --- a/app/mix/dao/mysql.go +++ b/app/mix/dao/mysql.go @@ -3,15 +3,17 @@ package dao import ( "errors" "fmt" - "github.com/gin-gonic/gin" - "github.com/jmoiron/sqlx" - goproto "google.golang.org/protobuf/proto" "service/app/mix/conf" "service/bizcommon/util" "service/dbstruct" "service/library/logger" "service/library/mysqldb" + "strings" "time" + + "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" + goproto "google.golang.org/protobuf/proto" ) type Mysql struct { @@ -532,3 +534,42 @@ func (m *Mysql) GetUCHList(ctx *gin.Context, tx *sqlx.Tx, mid int64, typ int32, } return } + +// 获取订单 +func (m *Mysql) GetOrderCountGroupByStatus(ctx *gin.Context, tx *sqlx.Tx, orderStatuses []int32, ctStart *int64, ctEnd *int64) (list []*dbstruct.VasOrderStatusCount, err error) { + var sql strings.Builder + var args []interface{} + sql.WriteString(fmt.Sprintf("select order_status, count(1) as count from %s where 1 = 1", TableOrder)) + + if ctStart != nil { + sql.WriteString(" and ct >= ?") + args = append(args, util.DerefInt64(ctStart)) + } + if ctEnd != nil { + sql.WriteString(" and ct <= ?") + args = append(args, util.DerefInt64(ctEnd)) + } + if len(orderStatuses) > 0 { + inClause := strings.Builder{} + inClause.WriteString("?") + args = append(args, orderStatuses[0]) + for i := 1; i < len(orderStatuses); i++ { + inClause.WriteString(",?") + args = append(args, orderStatuses[i]) + } + sql.WriteString(fmt.Sprintf(" and order_status in (%s)", inClause.String())) + } + sql.WriteString(" group by order_status") + + list = make([]*dbstruct.VasOrderStatusCount, 0) + if tx != nil { + err = tx.SelectContext(ctx, &list, sql.String(), args...) + } else { + db := m.getDBVas() + err = db.SelectContext(ctx, &list, sql.String(), args...) + } + if err != nil { + return + } + return +} diff --git a/app/mix/service/cronservice.go b/app/mix/service/cronservice.go index 964e16cc..307c7986 100644 --- a/app/mix/service/cronservice.go +++ b/app/mix/service/cronservice.go @@ -1,10 +1,16 @@ package service import ( + "fmt" "service/api/consts" "service/api/errcode" - "service/api/proto/streamer/proto" + accountproto "service/api/proto/account/proto" + daily_statementproto "service/api/proto/daily_statement/proto" + streamerproto "service/api/proto/streamer/proto" + vasproto "service/api/proto/vas/proto" "service/bizcommon/util" + "service/dbstruct" + "service/library/configcenter" "service/library/contentaudit/imageaudit" "service/library/contentaudit/textaudit" "service/library/logger" @@ -13,6 +19,7 @@ import ( "github.com/gin-gonic/gin" "github.com/go-co-op/gocron" + goproto "google.golang.org/protobuf/proto" ) var ( @@ -20,17 +27,23 @@ var ( ) type CronService struct { + fileAbsPath string } func NewCronService() *CronService { return new(CronService) } +func (s *CronService) ReadLoggerConfig(config configcenter.LoggerConfig) { + s.fileAbsPath = config.FileAbsPath +} + func (s *CronService) Run() { s.ReloadRecommList() s.ImageAuditBatch() s.TextAuditBatch() s.ClearVeriCodeSendTimes() + s.CreateDailyStatement() } func (s *CronService) ReloadRecommList() { @@ -45,7 +58,7 @@ func (s *CronService) ReloadRecommList() { // logger.Error("OpGetAccountRelationCount fail, ec: %v", ec) // scheduler.Stop() // } - list, ec := DefaultService.OpGetStreamerList(ctx, &proto.OpListReq{ + list, ec := DefaultService.OpGetStreamerList(ctx, &streamerproto.OpListReq{ Sort: "-fans", }) if ec != errcode.ErrCodeAccountRelationSrvOk { @@ -95,3 +108,72 @@ func (s *CronService) ClearVeriCodeSendTimes() { }) scheduler.StartAsync() } + +// 统计每日报表 +func (s *CronService) CreateDailyStatement() { + loc, _ := time.LoadLocation("Asia/Shanghai") + scheduler := gocron.NewScheduler(loc) + + scheduler.CronWithSeconds("0 0 * * * *").Do(func() { + + //拿到现在的时间戳 + nowTimeStamp := util.GetHourStartTimeStamp(time.Now()) + + //获取上个小时时间段 + startTimeStamp := nowTimeStamp - int64(60*60) + endTimeStamp := nowTimeStamp - int64(1) + starttime := time.Unix(startTimeStamp, 0) + endtime := time.Unix(endTimeStamp, 0) + + //获取H5接口访问量 + logpath := fmt.Sprintf("%v_%02d%02d%02d.Global", s.fileAbsPath, starttime.Year(), starttime.Month(), starttime.Day()) + count, err := DefaultScriptsService.QueryCallCount(consts.H5CallUrl, logpath) + if err != nil { + logger.Error("query h5 call count fail : %v", err) + } + + //获取用户总量 + accountCount, err := _DefaultAccount.OpCount(&gin.Context{}, &accountproto.OpCountReq{ + CtLowerBound: goproto.Int64(int64(consts.AppEnterProductionTime)), + }) + if err != nil { + logger.Error("_DefaultAccount OpCount fail : %v", err) + } + + //获取订单总量 + orderCounts, err := _DefaultVas.GetOrderCountGroupByStatus(&gin.Context{}, &vasproto.GetOrderByStatusReq{ + CtStart: goproto.Int64(consts.AppEnterProductionTime), + CtEnd: nil, + }) + if err != nil { + logger.Error("_DefaultVas GetOrderCountByStatus fail : %v", err) + } + + finishedOrderCount := int32(0) + allOrderCount := int32(0) + for _, orderCount := range orderCounts { + if util.DerefInt32(orderCount.OrderStatus) != 0 { + finishedOrderCount += util.DerefInt32(orderCount.Count) + } + allOrderCount += util.DerefInt32(orderCount.Count) + } + + dailyStatement := &dbstruct.DailyStatement{ + H5CallCount: goproto.Int64(int64(count)), + RegisteredUserCount: goproto.Int64(accountCount), + OrderCreatedCount: goproto.Int64(int64(allOrderCount)), + OrderFinishedCount: goproto.Int64(int64(finishedOrderCount)), + StartTime: goproto.Int64(startTimeStamp), + EndTime: goproto.Int64(endTimeStamp), + } + err = _DefaultDailyStatement.OpCreate(&gin.Context{}, &daily_statementproto.OpCreateReq{ + DailyStatement: dailyStatement, + }) + if err != nil { + logger.Error("_DefaultDailyStatement OpCreate fail : %v", err) + } + + logger.Info("%v - %v statement data has created...", starttime, endtime) + }) + scheduler.StartAsync() +} diff --git a/app/mix/service/logic/daily_statement.go b/app/mix/service/logic/daily_statement.go new file mode 100644 index 00000000..7973061f --- /dev/null +++ b/app/mix/service/logic/daily_statement.go @@ -0,0 +1,65 @@ +package logic + +import ( + "service/api/consts" + daily_statementproto "service/api/proto/daily_statement/proto" + "service/app/mix/dao" + "service/dbstruct" + "service/library/idgenerator" + "service/library/logger" + "time" + + "github.com/gin-gonic/gin" + goproto "google.golang.org/protobuf/proto" +) + +type DailyStatement struct { + store *dao.Store +} + +func NewDailyStatement(store *dao.Store) (a *DailyStatement) { + a = &DailyStatement{ + store: store, + } + return +} + +func (p *DailyStatement) OpCreate(ctx *gin.Context, req *daily_statementproto.OpCreateReq) error { + req.DailyStatement.Id = goproto.Int64(idgenerator.GenDailyStatementId()) + req.DailyStatement.Ct = goproto.Int64(time.Now().Unix()) + req.DailyStatement.Ut = goproto.Int64(time.Now().Unix()) + req.DailyStatement.DelFlag = goproto.Int64(consts.Exist) + err := p.store.CreateDailyStatement(ctx, req.DailyStatement) + if err != nil { + logger.Error("CreateDailyStatement fail, err: %v", err) + return err + } + return nil +} + +func (p *DailyStatement) OpUpdate(ctx *gin.Context, req *daily_statementproto.OpUpdateReq) error { + err := p.store.UpdateDailyStatement(ctx, req.DailyStatement) + if err != nil { + logger.Error("UpdateDailyStatement fail, err: %v", err) + return err + } + return nil +} + +func (p *DailyStatement) OpDelete(ctx *gin.Context, id int64) error { + err := p.store.DeleteDailyStatement(ctx, id) + if err != nil { + logger.Error("DeleteDailyStatement fail, err: %v", err) + return err + } + return nil +} + +func (p *DailyStatement) OpList(ctx *gin.Context, req *daily_statementproto.OpListReq) ([]*dbstruct.DailyStatement, error) { + list, err := p.store.GetDailyStatementList(ctx, req) + if err != nil { + logger.Error("GetDailyStatementList fail, err: %v", err) + return make([]*dbstruct.DailyStatement, 0), err + } + return list, nil +} diff --git a/app/mix/service/logic/vas.go b/app/mix/service/logic/vas.go index bfc7ee7c..7e119882 100644 --- a/app/mix/service/logic/vas.go +++ b/app/mix/service/logic/vas.go @@ -4,10 +4,6 @@ import ( "database/sql" "errors" "fmt" - "github.com/gin-gonic/gin" - "github.com/jmoiron/sqlx" - "github.com/samber/lo" - goproto "google.golang.org/protobuf/proto" "service/api/base" "service/api/errs" vasproto "service/api/proto/vas/proto" @@ -19,6 +15,11 @@ import ( "service/library/logger" "service/library/payclients/alipaycli" "time" + + "github.com/gin-gonic/gin" + "github.com/jmoiron/sqlx" + "github.com/samber/lo" + goproto "google.golang.org/protobuf/proto" ) type Vas struct { @@ -1671,3 +1672,7 @@ func (v *Vas) AlipayCallback(ctx *gin.Context, p *vasproto.AlipayCallbackParamIn func (v *Vas) GetCoinOrderById(ctx *gin.Context, id string) (*dbstruct.CoinOrder, error) { return v.store.GetCoinOrderById(ctx, nil, id) } + +func (v *Vas) GetOrderCountGroupByStatus(ctx *gin.Context, req *vasproto.GetOrderByStatusReq) ([]*dbstruct.VasOrderStatusCount, error) { + return v.store.GetOrderCountGroupByStatus(ctx, nil, req.OrderStatuses, req.CtStart, req.CtEnd) +} diff --git a/app/mix/service/scripts_service.go b/app/mix/service/scripts_service.go new file mode 100644 index 00000000..47b745ed --- /dev/null +++ b/app/mix/service/scripts_service.go @@ -0,0 +1,32 @@ +package service + +import ( + "fmt" + "os/exec" + "service/library/logger" + "strconv" +) + +var ( + DefaultScriptsService *ScriptsService +) + +type ScriptsService struct { +} + +func NewScriptsService() *ScriptsService { + return new(ScriptsService) +} + +// 放置一些执行服务器脚本的service +func (s *ScriptsService) QueryCallCount(url string, logpath string) (count int, err error) { + cmdStr := fmt.Sprintf("grep -c %v %v", url, logpath) + cmd := exec.Command(cmdStr) + out, err := cmd.Output() + if err != nil { + logger.Error("could not run command: %v", err) + return + } + count, err = strconv.Atoi(string(out)) + return +} diff --git a/app/mix/service/service.go b/app/mix/service/service.go index d64fa962..40b5f8b6 100644 --- a/app/mix/service/service.go +++ b/app/mix/service/service.go @@ -14,6 +14,7 @@ import ( catalogproto "service/api/proto/catalog/proto" contact_customer_service_proto "service/api/proto/contact_customer_service/proto" contact_customer_service_sessionproto "service/api/proto/contact_customer_service_session/proto" + daily_statementproto "service/api/proto/daily_statement/proto" feedbackproto "service/api/proto/feedback/proto" footprintproto "service/api/proto/footprint/proto" imageauditproto "service/api/proto/imageaudit/proto" @@ -92,6 +93,7 @@ var ( _DefaultTextAudit *logic.TextAudit _DefaultTextAuditTask *logic.TextAuditTask _DefaultContactCustomerServiceSession *logic.ContactCustomerServiceSession + _DefaultDailyStatement *logic.DailyStatement ) type Service struct { @@ -157,6 +159,7 @@ func (s *Service) Init(c any) (err error) { _DefaultTextAuditTask = logic.NewTextAuditTask(store) _DefaultVas = logic.NewVas(store, _DefaultStreamer) _DefaultContactCustomerServiceSession = logic.NewContactCustomerServiceSession(store) + _DefaultDailyStatement = logic.NewDailyStatement(store) return } @@ -2499,3 +2502,15 @@ func (s *Service) OpGetContactCustomerServiceSessionList(ctx *gin.Context, req * } return } + +// DailyStatement +func (s *Service) OpGetDailyStatementList(ctx *gin.Context, req *daily_statementproto.OpListReq) (list []*dbstruct.DailyStatement, ec errcode.ErrCode) { + ec = errcode.ErrCodeDailyStatementSrvOk + list, err := _DefaultDailyStatement.OpList(ctx, req) + if err != nil { + logger.Error("OpGetDailyStatementList fail, req: %v, err: %v", util.ToJson(req), err) + ec = errcode.ErrCodeDailyStatementSrvFail + return + } + return +} diff --git a/bizcommon/util/util.go b/bizcommon/util/util.go index 8b540bd8..695df762 100644 --- a/bizcommon/util/util.go +++ b/bizcommon/util/util.go @@ -8,6 +8,7 @@ import ( "reflect" "service/library/logger" "strings" + "time" "github.com/qiniu/qmgo" ) @@ -62,3 +63,14 @@ func UderscoreToUpperCamelCase(s string) string { func Convert2SqlArr(a ...any) string { return strings.Replace(strings.Trim(fmt.Sprint(a), "[]"), " ", ",", -1) } + +// 获取整点时间戳 +func GetHourStartTimeStamp(t time.Time) int64 { + loc, _ := time.LoadLocation("Asia/Shanghai") + timeStr := fmt.Sprintf("%02d-%02d-%02d %02d:00:00", t.Year(), t.Month(), t.Day(), t.Hour()) + duetimecst, err := time.ParseInLocation("2006-1-2 15:04:05", timeStr, loc) + if err != nil { + logger.Error("parse error : %v", err) + } + return duetimecst.Unix() +} diff --git a/codecreate/codecreate.go b/codecreate/codecreate.go index 994fb891..77a1b3d9 100644 --- a/codecreate/codecreate.go +++ b/codecreate/codecreate.go @@ -9,48 +9,48 @@ import ( func main() { genSource := &generator.GenSource{ - EntityName: "ContactCustomerServiceSession", - ModuleName: "contact_customer_service_session", - EntityCNName: "联系客服对话表", - ErrCodeSeq: "24", + EntityName: "DailyStatement", + ModuleName: "daily_statement", + EntityCNName: "每日报表表", + ErrCodeSeq: "26", } generator.CreateFileDirectory(genSource) - // genSource.InPath = consts.EntityInPath - // genSource.OutPath = fmt.Sprintf("%v%v%v.go", consts.RootPath, consts.EntityOutPath, genSource.ModuleName) - // generator.GenerateEntity(genSource) + genSource.InPath = consts.EntityInPath + genSource.OutPath = fmt.Sprintf("%v%v%v.go", consts.RootPath, consts.EntityOutPath, genSource.ModuleName) + generator.GenerateEntity(genSource) - // genSource.InPath = consts.IdGeneratorInPath - // genSource.OutPath = fmt.Sprintf("%v%v%v_idgenerator.go", consts.RootPath, consts.IdgeneratorOutPath, genSource.ModuleName) - // generator.GenerateModule(genSource) + genSource.InPath = consts.IdGeneratorInPath + genSource.OutPath = fmt.Sprintf("%v%v%v_idgenerator.go", consts.RootPath, consts.IdgeneratorOutPath, genSource.ModuleName) + generator.GenerateModule(genSource) - // genSource.InPath = consts.MongoInPath - // genSource.OutPath = fmt.Sprintf("%v%v%v_mongo.go", consts.RootPath, consts.MongoOutPath, genSource.ModuleName) - // generator.GenerateModule(genSource) + genSource.InPath = consts.MongoInPath + genSource.OutPath = fmt.Sprintf("%v%v%v_mongo.go", consts.RootPath, consts.MongoOutPath, genSource.ModuleName) + generator.GenerateModule(genSource) genSource.InPath = consts.ProtoInPath genSource.OutPath = fmt.Sprintf("%v%v%v/proto/%v_op.go", consts.RootPath, consts.ProtoOutPath, genSource.ModuleName, genSource.ModuleName) generator.GenerateModule(genSource) - // genSource.InPath = consts.ServiceInPath - // genSource.OutPath = fmt.Sprintf("%v%v%v.go", consts.RootPath, consts.ServiceOutPath, genSource.ModuleName) - // generator.GenerateModule(genSource) + genSource.InPath = consts.ServiceInPath + genSource.OutPath = fmt.Sprintf("%v%v%v.go", consts.RootPath, consts.ServiceOutPath, genSource.ModuleName) + generator.GenerateModule(genSource) - // genSource.InPath = consts.ServiceCenterInPath - // genSource.OutPath = fmt.Sprintf("%v%v%v_service_center.go", consts.RootPath, consts.ServiceCenterOutPath, genSource.ModuleName) - // generator.GenerateModule(genSource) + genSource.InPath = consts.ServiceCenterInPath + genSource.OutPath = fmt.Sprintf("%v%v%v_service_center.go", consts.RootPath, consts.ServiceCenterOutPath, genSource.ModuleName) + generator.GenerateModule(genSource) - // genSource.InPath = consts.ErrCodeInPath - // genSource.OutPath = fmt.Sprintf("%v%v%v_errcode.go", consts.RootPath, consts.ErrcodeOutPath, genSource.ModuleName) - // generator.GenerateModule(genSource) + genSource.InPath = consts.ErrCodeInPath + genSource.OutPath = fmt.Sprintf("%v%v%v_errcode.go", consts.RootPath, consts.ErrcodeOutPath, genSource.ModuleName) + generator.GenerateModule(genSource) - // genSource.InPath = consts.ControllerInPath - // genSource.OutPath = fmt.Sprintf("%v%v%v_op.go", consts.RootPath, consts.ControllerOutPath, genSource.ModuleName) - // generator.GenerateModule(genSource) + genSource.InPath = consts.ControllerInPath + genSource.OutPath = fmt.Sprintf("%v%v%v_op.go", consts.RootPath, consts.ControllerOutPath, genSource.ModuleName) + generator.GenerateModule(genSource) - // genSource.InPath = consts.ControllerCenterInPath - // genSource.OutPath = fmt.Sprintf("%v%v%v_controller_center.go", consts.RootPath, consts.ControllerCenterOutPath, genSource.ModuleName) - // generator.GenerateModule(genSource) + genSource.InPath = consts.ControllerCenterInPath + genSource.OutPath = fmt.Sprintf("%v%v%v_controller_center.go", consts.RootPath, consts.ControllerCenterOutPath, genSource.ModuleName) + generator.GenerateModule(genSource) } diff --git a/codecreate/resource/EntityDefine.xlsx b/codecreate/resource/EntityDefine.xlsx index f384b89b69718351c24f6917dd134942e4b871b3..9be680f00c6fa2ce756db45a6253e441b2d2896d 100644 GIT binary patch delta 8445 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 delta 7340 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 X?GA@*R5WS;H9-B