版本更新
This commit is contained in:
parent
3f1130d367
commit
a5f4fe4946
@ -49,3 +49,6 @@ type CardVchartHeat struct {
|
||||
type CardVchartData struct {
|
||||
Values string
|
||||
}
|
||||
|
||||
type CardNotifyData struct {
|
||||
}
|
||||
|
||||
10
Type/t.go
10
Type/t.go
@ -84,3 +84,13 @@ type DecorateData struct {
|
||||
Value []int `json:"value"`
|
||||
Value2 []int `json:"value2"`
|
||||
}
|
||||
|
||||
type NotifyData struct {
|
||||
Host string `json:"host"`
|
||||
EventName string `json:"event_name"`
|
||||
Severity string `json:"severity"`
|
||||
AlarmTime string `json:"alarm_time"`
|
||||
Info string `json:"info"`
|
||||
Id string `json:"id"`
|
||||
NotifyMsg string `json:"notify_msg"`
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ type Config struct {
|
||||
|
||||
var config *Config
|
||||
|
||||
func init() {
|
||||
func Init() {
|
||||
err := loadConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
)
|
||||
|
||||
func FeishuSendInfo(c *gin.Context) {
|
||||
// TODO
|
||||
Result, err := util.GetOperation(3)
|
||||
if err != nil {
|
||||
log.Printf("failed to get operation: %v", err)
|
||||
@ -27,7 +26,6 @@ func FeishuSendInfo(c *gin.Context) {
|
||||
}
|
||||
|
||||
func FeishuSendInfo2(c *gin.Context) {
|
||||
// TODO
|
||||
err := feishu.SendOperationMsg2(3)
|
||||
if err != nil {
|
||||
log.Printf("failed to send operation message: %v", err)
|
||||
@ -35,7 +33,6 @@ func FeishuSendInfo2(c *gin.Context) {
|
||||
}
|
||||
|
||||
func FeishuSendWeekInfo(c *gin.Context) {
|
||||
// TODO
|
||||
Result, err := util.GetOperation(3)
|
||||
if err != nil {
|
||||
log.Printf("failed to get operation: %v", err)
|
||||
@ -140,3 +137,16 @@ func FeishuServerInfo(c *gin.Context) {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func FeishuNotify(c *gin.Context) {
|
||||
r := Type.NotifyData{}
|
||||
if err := c.ShouldBindJSON(&r); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
err := feishu.SendNotifyMsg(&r)
|
||||
if err != nil {
|
||||
log.Printf("failed to send notify message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var NOTIFY_TITLE_COLOR = map[string]string{
|
||||
"Not classified": "grey",
|
||||
"Information": "blue",
|
||||
"Warning": "yellow",
|
||||
"Average": "orange",
|
||||
"High": "red",
|
||||
"Disaster": "carmine",
|
||||
}
|
||||
|
||||
func SendFeishuMsg(msg string) error {
|
||||
// 创建请求体
|
||||
payload := map[string]interface{}{
|
||||
@ -475,7 +484,7 @@ func SendOperationMsg2(AppId int) error {
|
||||
}
|
||||
elementsStr := strings.Join(elements, ",")
|
||||
data := Type.Card{
|
||||
Title: "运营日报",
|
||||
Title: "运营周报",
|
||||
Elements: elementsStr,
|
||||
Tag1: "UK",
|
||||
}
|
||||
@ -489,3 +498,35 @@ func SendOperationMsg2(AppId int) error {
|
||||
c.SendGroupMsg(common.GetOperationChatId(), common.FEISHU_CART_TYPE, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendNotifyMsg(data *Type.NotifyData) error {
|
||||
TitleColor := NOTIFY_TITLE_COLOR[data.Severity]
|
||||
payload := map[string]interface{}{
|
||||
"type": "template",
|
||||
"data": map[string]interface{}{
|
||||
"template_id": "AAqB4RTngW8Wt",
|
||||
"template_version_name": "1.0.7",
|
||||
|
||||
"template_variable": map[string]interface{}{
|
||||
"Title": "待处理",
|
||||
"TitleColor": TitleColor,
|
||||
"InputMsg": "处理情况说明,选填",
|
||||
"HostName": data.Host,
|
||||
"EventName": data.EventName,
|
||||
"EventSeverity": data.Severity,
|
||||
"alarm_time": data.AlarmTime,
|
||||
"NotifyMsg": data.NotifyMsg,
|
||||
},
|
||||
},
|
||||
}
|
||||
payloadBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := client.GetClient()
|
||||
err = c.SendGroupMsg(common.GetOperationChatId(), common.FEISHU_CART_TYPE, string(payloadBytes))
|
||||
if err != nil {
|
||||
log.Printf("sendNotifyMsg error %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
54
feishu/handle/card.go
Normal file
54
feishu/handle/card.go
Normal file
@ -0,0 +1,54 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"backend/Type"
|
||||
"backend/util"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher/callback"
|
||||
)
|
||||
|
||||
var handle map[string]func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error)
|
||||
|
||||
func init() {
|
||||
handle = make(map[string]func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error))
|
||||
handle["complete_alarm"] = NotifyCard
|
||||
}
|
||||
|
||||
func Handle(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
|
||||
if h, ok := handle[event.Event.Action.Value["action"].(string)]; ok {
|
||||
return h(ctx, event)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NotifyCard(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
|
||||
d := Type.NotifyData{
|
||||
Host: event.Event.Action.Value["HostName"].(string),
|
||||
EventName: event.Event.Action.Value["EventName"].(string),
|
||||
Severity: event.Event.Action.Value["EventSeverity"].(string),
|
||||
AlarmTime: event.Event.Action.Value["AlarmTime"].(string),
|
||||
Info: event.Event.Action.FormValue["notes_input"].(string),
|
||||
NotifyMsg: event.Event.Action.Value["NotifyMsg"].(string),
|
||||
Id: event.Event.Operator.OpenID,
|
||||
}
|
||||
str := util.ParseTmpl("./template/card_notify.tmpl", d)
|
||||
r := make(map[string]interface{})
|
||||
err := json.Unmarshal([]byte(str), &r)
|
||||
if err != nil {
|
||||
log.Println(str)
|
||||
log.Printf("notify card %v", err)
|
||||
}
|
||||
return &callback.CardActionTriggerResponse{
|
||||
Toast: &callback.Toast{
|
||||
Type: "info",
|
||||
Content: "已收到",
|
||||
},
|
||||
Card: &callback.Card{
|
||||
Type: "raw",
|
||||
Data: r,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"backend/common"
|
||||
"backend/feishu/client"
|
||||
"backend/feishu/data"
|
||||
"backend/feishu/handle"
|
||||
"context"
|
||||
"log"
|
||||
|
||||
@ -15,6 +17,9 @@ import (
|
||||
)
|
||||
|
||||
func Server() {
|
||||
if common.GetNMap() {
|
||||
return
|
||||
}
|
||||
// 注册事件回调,OnP2MessageReceiveV1 为接收消息 v2.0;OnCustomizedEvent 内的 message 为接收消息 v1.0。
|
||||
eventHandler := dispatcher.NewEventDispatcher("", "").
|
||||
OnP2MessageReceiveV1(func(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
|
||||
@ -29,7 +34,7 @@ func Server() {
|
||||
})
|
||||
eventHandler.OnP2CardActionTrigger(func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
|
||||
log.Printf("[ OnP2CardActionTrigger access ], data: %s\n", larkcore.Prettify(event))
|
||||
return nil, nil
|
||||
return handle.Handle(ctx, event)
|
||||
})
|
||||
|
||||
// 创建Client
|
||||
|
||||
5083
log/backend.log
5083
log/backend.log
File diff suppressed because it is too large
Load Diff
187
log/t.json
187
log/t.json
@ -0,0 +1,187 @@
|
||||
{
|
||||
"schema": "2.0",
|
||||
"config": {
|
||||
"update_multi": true,
|
||||
"style": {
|
||||
"text_size": {
|
||||
"normal_v2": {
|
||||
"default": "normal",
|
||||
"pc": "normal",
|
||||
"mobile": "heading"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"direction": "vertical",
|
||||
"padding": "12px 12px 12px 12px",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">告警内容</font>\n移动端打开异常率达到5%",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">错误信息</font>\n服务请求数量过多,超出限频",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">告警等级</font>\nP0",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">告警时间</font>\n${alarm_time}",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"direction": "vertical",
|
||||
"horizontal_spacing": "8px",
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "hr",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "form",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "input",
|
||||
"placeholder": {
|
||||
"tag": "plain_text",
|
||||
"content": "处理情况说明,选填"
|
||||
},
|
||||
"default_value": "",
|
||||
"width": "fill",
|
||||
"name": "notes_input",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "auto",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "button",
|
||||
"text": {
|
||||
"tag": "plain_text",
|
||||
"content": "处理完成"
|
||||
},
|
||||
"type": "primary",
|
||||
"width": "default",
|
||||
"behaviors": [
|
||||
{
|
||||
"type": "callback",
|
||||
"value": {
|
||||
"action": "complete_alarm",
|
||||
"time": "${alarm_time}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"form_action_type": "submit",
|
||||
"name": "Button_m6vy7xom"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top"
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"direction": "vertical",
|
||||
"padding": "4px 0px 4px 0px",
|
||||
"margin": "0px 0px 0px 0px",
|
||||
"name": "Form_m6vy7xol"
|
||||
}
|
||||
]
|
||||
},
|
||||
"header": {
|
||||
"title": {
|
||||
"tag": "plain_text",
|
||||
"content": "[待处理] 告警通知:服务器出错请及时处理"
|
||||
},
|
||||
"subtitle": {
|
||||
"tag": "plain_text",
|
||||
"content": ""
|
||||
},
|
||||
"template": "red",
|
||||
"icon": {
|
||||
"tag": "standard_icon",
|
||||
"token": "succeed-hollow_filled"
|
||||
},
|
||||
"padding": "12px 12px 12px 12px"
|
||||
}
|
||||
}
|
||||
11
main.go
11
main.go
@ -1,7 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"backend/common"
|
||||
"backend/controller"
|
||||
"backend/feishu/server"
|
||||
"backend/util"
|
||||
"log"
|
||||
"os"
|
||||
@ -11,6 +13,11 @@ import (
|
||||
|
||||
// GOOS=linux GOARCH=amd64 go build -o /data/backend/release/backend main.go
|
||||
|
||||
func init() {
|
||||
common.Init()
|
||||
// 初始化日志文件夹
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
// 以追加模式打开或创建日志文件
|
||||
@ -58,9 +65,9 @@ func main() {
|
||||
api.POST("/feishu/sendWeekInfo", controller.FeishuSendWeekInfo)
|
||||
api.POST("/feishu/updateApp", controller.FeishuUpdateApp)
|
||||
api.POST("/feishu/serverInfo", controller.FeishuServerInfo)
|
||||
api.POST("/feishu/notify", controller.FeishuNotify)
|
||||
}
|
||||
go controller.AppPortNmap()
|
||||
go util.ScheduleDailyTask()
|
||||
// go server.Server()
|
||||
go server.Server()
|
||||
r.Run(":5320") // 在 0.0.0.0:5320 上监听并服务
|
||||
}
|
||||
|
||||
@ -65,8 +65,8 @@ func (s *Statistics) StatisticsInfo() (interface{}, error) {
|
||||
if LogDb == nil {
|
||||
return nil, fmt.Errorf("failed to get mysql database")
|
||||
}
|
||||
StartTime := util.ZeroTimestampByTz(AppConfig.Tz)
|
||||
EndTime := StartTime + 86400
|
||||
StartTime, _ := util.GetZeroTimestamp(AppConfig.Tz, 0)
|
||||
EndTime, _ := util.GetZeroTimestamp(AppConfig.Tz, 1)
|
||||
var Register int
|
||||
var TotalRegistger int
|
||||
var Recharge float64
|
||||
|
||||
90
model/log.go
90
model/log.go
@ -12,11 +12,17 @@ type Log struct {
|
||||
CurrentPage int `json:"CurrentPage"`
|
||||
AppId int `json:"AppId"`
|
||||
EventParam string `json:"Event"`
|
||||
StartTime string `json:"StartTime"`
|
||||
EndTime string `json:"EndTime"`
|
||||
ItemId int `json:"ItemId"`
|
||||
}
|
||||
|
||||
type ResAsset struct {
|
||||
Total int `json:"total"`
|
||||
Data []*ResAssetDetail `json:"data"`
|
||||
Sum int `json:"sum"` // 总和
|
||||
NSum int `json:"nsum"` // 负数和
|
||||
PSum int `json:"psum"` // 正数和
|
||||
}
|
||||
|
||||
type ResEvent struct {
|
||||
@ -71,28 +77,67 @@ func (m *Log) Asset() (*ResAsset, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
StartTime, err := util.ParseTimeToTimestamp(m.StartTime, AppConfig.Tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
EndTime, err := util.ParseTimeToTimestamp(m.EndTime, AppConfig.Tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Db := util.MPool.GetTopicDB(AppConfig.Topic)
|
||||
defer Db.Close()
|
||||
if Db == nil {
|
||||
return nil, fmt.Errorf("failed to get mysql database")
|
||||
}
|
||||
assets := []*Event{}
|
||||
err = Db.Select(&assets, "SELECT * FROM log_event WHERE Uid = ? and `Event` = 'asset_change' ORDER BY Timestamp DESC LIMIT ?, ?", m.Uid, (m.CurrentPage-1)*m.PageSize, m.PageSize)
|
||||
value := make([]interface{}, 0)
|
||||
totalValue := make([]interface{}, 0)
|
||||
Sql := "SELECT * FROM log_event WHERE Uid = ? and `Event` = 'asset_change' and Timestamp >= ? and Timestamp <= ? "
|
||||
TotalSql := "SELECT COUNT(*) FROM log_event WHERE Uid = ? and `Event` = 'asset_change' and Timestamp >= ? and Timestamp <= ? "
|
||||
totalValue = append(totalValue, m.Uid, StartTime, EndTime)
|
||||
value = append(value, m.Uid, StartTime, EndTime)
|
||||
if m.ItemId != 0 {
|
||||
Sql += "and Param like ? "
|
||||
value = append(value, fmt.Sprintf("%%\"item_id\":%d%%", m.ItemId))
|
||||
TotalSql += "and Param like ? "
|
||||
totalValue = append(totalValue, fmt.Sprintf("%%\"item_id\":%d%%", m.ItemId))
|
||||
}
|
||||
if m.EventParam != "" {
|
||||
Sql += "and Param like ? "
|
||||
value = append(value, fmt.Sprintf("%%\"change_type\":\"%s\"%%", m.EventParam))
|
||||
TotalSql += "and Param like ? "
|
||||
totalValue = append(totalValue, fmt.Sprintf("%%\"change_type\":\"%s\"%%", m.EventParam))
|
||||
}
|
||||
|
||||
value = append(value, (m.CurrentPage-1)*m.PageSize, m.PageSize)
|
||||
Sql += "ORDER BY Timestamp DESC LIMIT ?, ?"
|
||||
err = Db.Select(&assets, Sql, value...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get asset list: %v", err)
|
||||
}
|
||||
var total int
|
||||
err = Db.QueryRow("SELECT COUNT(*) FROM log_event WHERE Uid = ? and `Event` = 'asset_change'", m.Uid).Scan(&total)
|
||||
err = Db.QueryRow(TotalSql, totalValue...).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get asset count: %v", err)
|
||||
}
|
||||
resData := []*ResAssetDetail{}
|
||||
Sum := 0
|
||||
NSum := 0
|
||||
PSum := 0
|
||||
for _, asset := range assets {
|
||||
param := map[string]interface{}{}
|
||||
err := json.Unmarshal([]byte(asset.Param), ¶m)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
Sum += util.Int(param["change_num"])
|
||||
if param["change_type"].(string) == "gain" {
|
||||
PSum += util.Int(param["change_num"])
|
||||
} else {
|
||||
NSum += util.Int(param["change_num"])
|
||||
}
|
||||
resData = append(resData, &ResAssetDetail{
|
||||
Uid: asset.Uid,
|
||||
ChangeType: param["change_type"].(string),
|
||||
@ -105,6 +150,9 @@ func (m *Log) Asset() (*ResAsset, error) {
|
||||
return &ResAsset{
|
||||
Total: total,
|
||||
Data: resData,
|
||||
Sum: Sum,
|
||||
NSum: NSum,
|
||||
PSum: PSum,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -119,17 +167,43 @@ func (m *Log) Event() (*ResEvent, error) {
|
||||
if Db == nil {
|
||||
return nil, fmt.Errorf("failed to get mysql database")
|
||||
}
|
||||
assets := []*Event{}
|
||||
if m.EventParam != "" {
|
||||
err = Db.Select(&assets, "SELECT * FROM log_event WHERE Uid = ? and `Event` = ? ORDER BY Timestamp DESC LIMIT ?, ?", m.Uid, m.EventParam, (m.CurrentPage-1)*m.PageSize, m.PageSize)
|
||||
} else {
|
||||
err = Db.Select(&assets, "SELECT * FROM log_event WHERE Uid = ? and `Event` != 'asset_change' ORDER BY Timestamp DESC LIMIT ?, ?", m.Uid, (m.CurrentPage-1)*m.PageSize, m.PageSize)
|
||||
StartTime, err := util.ParseTimeToTimestamp(m.StartTime, AppConfig.Tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
EndTime, err := util.ParseTimeToTimestamp(m.EndTime, AppConfig.Tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assets := []*Event{}
|
||||
Sql := "SELECT * FROM log_event WHERE Uid = ? and `Event` != 'asset_change' and Timestamp >= ? and Timestamp <= ? "
|
||||
Value := make([]interface{}, 0)
|
||||
Value = append(Value, m.Uid, StartTime, EndTime)
|
||||
if m.EventParam != "" {
|
||||
Sql += "and `Event` = ? "
|
||||
Value = append(Value, m.EventParam)
|
||||
}
|
||||
Sql += "ORDER BY Timestamp DESC LIMIT ?, ?"
|
||||
Value = append(Value, (m.CurrentPage-1)*m.PageSize, m.PageSize)
|
||||
err = Db.Select(&assets, Sql, Value...)
|
||||
// if m.EventParam != "" {
|
||||
// err = Db.Select(&assets, "SELECT * FROM log_event WHERE Uid = ? and `Event` = ? ORDER BY Timestamp DESC LIMIT ?, ?", m.Uid, m.EventParam, (m.CurrentPage-1)*m.PageSize, m.PageSize)
|
||||
// } else {
|
||||
// err = Db.Select(&assets, "SELECT * FROM log_event WHERE Uid = ? and `Event` != 'asset_change' ORDER BY Timestamp DESC LIMIT ?, ?", m.Uid, (m.CurrentPage-1)*m.PageSize, m.PageSize)
|
||||
// }
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get event list: %v", err)
|
||||
}
|
||||
var total int
|
||||
err = Db.QueryRow("SELECT COUNT(*) FROM log_event WHERE Uid = ? and `Event` != 'asset_change'", m.Uid).Scan(&total)
|
||||
totalSql := "SELECT COUNT(*) FROM log_event WHERE Uid = ? and `Event` != 'asset_change' and Timestamp >= ? and Timestamp <= ? "
|
||||
totalValue := make([]interface{}, 0)
|
||||
totalValue = append(totalValue, m.Uid, StartTime, EndTime)
|
||||
if m.EventParam != "" {
|
||||
totalSql += "and `Event` = ? "
|
||||
totalValue = append(totalValue, m.EventParam)
|
||||
}
|
||||
err = Db.QueryRow(totalSql, totalValue...).Scan(&total)
|
||||
//err = Db.QueryRow("SELECT COUNT(*) FROM log_event WHERE Uid = ? and `Event` != 'asset_change'", m.Uid).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get event count: %v", err)
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ type Mail struct {
|
||||
Items string `json:"items" db:"items"`
|
||||
RegisterTime int64 `json:"register_time" db:"register_time"`
|
||||
MailType int `json:"mail_type" db:"mail_type"`
|
||||
SendType int `json:"send_type" db:"send_type"`
|
||||
ToUids string `json:"to_uids" db:"to_uids"`
|
||||
CreateTime int64 `json:"create_time" db:"create_time"`
|
||||
}
|
||||
@ -39,7 +40,7 @@ func (m *Mail) MailList() (*Result, error) {
|
||||
Db := util.MPool.GetMysqlDB(AppCfg.ServerName, AppCfg.MysqlName, m.ServerId)
|
||||
defer Db.Close()
|
||||
var mail []*Mail
|
||||
err = Db.Select(&mail, "SELECT `mail_id`, `title`, `content`, `start_time`, `end_time`, `items`, `register_time`, `mail_type`, `to_uids`, `create_time` FROM system_mail_info")
|
||||
err = Db.Select(&mail, "SELECT `mail_id`, `title`, `content`, `start_time`, `end_time`, `items`, `register_time`, `mail_type`,`send_type`, `to_uids`, `create_time` FROM system_mail_info")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan rows: %v", err)
|
||||
}
|
||||
@ -60,7 +61,7 @@ func (m *Mail) SendMail() error {
|
||||
}
|
||||
Db := util.MPool.GetMysqlDB(AppCfg.ServerName, AppCfg.MysqlName, m.ServerId)
|
||||
defer Db.Close()
|
||||
_, err = Db.Exec("INSERT INTO system_mail_info (`title`, `content`, `start_time`, `end_time`, `items`, `register_time`, `mail_type`, `to_uids`, `create_time`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", m.Title, m.Content, m.StartTime, m.EndTime, m.Items, m.RegisterTime, m.MailType, m.ToUids, m.CreateTime)
|
||||
_, err = Db.Exec("INSERT INTO system_mail_info (`title`, `content`, `start_time`, `end_time`, `items`, `register_time`, `mail_type`, `send_type`, `to_uids`, `create_time`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.Title, m.Content, m.StartTime, m.EndTime, m.Items, m.RegisterTime, m.MailType, m.SendType, m.ToUids, m.CreateTime)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert mail: %v", err)
|
||||
}
|
||||
|
||||
BIN
release/backend
BIN
release/backend
Binary file not shown.
BIN
release/zabbix
Normal file
BIN
release/zabbix
Normal file
Binary file not shown.
62
scripts/zabbix/main.go
Normal file
62
scripts/zabbix/main.go
Normal file
@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"backend/msg"
|
||||
"backend/util"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 获取命令行参数
|
||||
args := os.Args
|
||||
Status := 0
|
||||
|
||||
if len(args) < 2 {
|
||||
fmt.Print(Status)
|
||||
return
|
||||
}
|
||||
Host := args[1]
|
||||
Port, err := strconv.Atoi(args[2])
|
||||
if err != nil {
|
||||
fmt.Print(Status)
|
||||
return
|
||||
}
|
||||
ws, err := getwebsocket(Host, Port)
|
||||
if err != nil {
|
||||
fmt.Print(Status)
|
||||
return
|
||||
}
|
||||
ws.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
req := &msg.ReqServerInfo{}
|
||||
_, err = util.SendAdminMsg(ws, req)
|
||||
if err != nil {
|
||||
//log.Printf("failed to send admin message: %v", err)
|
||||
fmt.Print(Status)
|
||||
return
|
||||
}
|
||||
Status = 1
|
||||
fmt.Print(Status)
|
||||
}
|
||||
|
||||
func getwebsocket(Host string, Port int) (*websocket.Conn, error) {
|
||||
origin := "http://localhost/"
|
||||
url := fmt.Sprintf("ws://%s:%d/", Host, Port)
|
||||
config, err := websocket.NewConfig(url, origin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create websocket config: %v", err)
|
||||
}
|
||||
// config.Dialer = &net.Dialer{
|
||||
// Timeout: 5 * time.Second,
|
||||
// }
|
||||
|
||||
ws, err := websocket.DialConfig(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to websocket: %v", err)
|
||||
}
|
||||
return ws, nil
|
||||
}
|
||||
BIN
scripts/zabbix/zabbix
Normal file
BIN
scripts/zabbix/zabbix
Normal file
Binary file not shown.
173
template/c1.tmpl
Normal file
173
template/c1.tmpl
Normal file
@ -0,0 +1,173 @@
|
||||
{
|
||||
"schema": "2.0",
|
||||
"config": {
|
||||
"update_multi": true,
|
||||
"style": {
|
||||
"text_size": {
|
||||
"normal_v2": {
|
||||
"default": "normal",
|
||||
"pc": "normal",
|
||||
"mobile": "heading"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"direction": "vertical",
|
||||
"padding": "12px 12px 12px 12px",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">来源</font>\n**{{.Host}}**",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">错误信息</font>\n**{{.EventName}}**",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">告警等级</font>\n**{{.Severity}}**",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">告警时间</font>\n**{{.AlarmTime}}**",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"direction": "vertical",
|
||||
"horizontal_spacing": "8px",
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "hr",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">情况说明</font>\n**{{.Info}}**",
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">处理人</font>\n<at id='{{.Id}}'></at>",
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
]
|
||||
},
|
||||
"header": {
|
||||
"title": {
|
||||
"tag": "plain_text",
|
||||
"content": "[已处理] 告警通知:{{.NotifyMsg}}"
|
||||
},
|
||||
"subtitle": {
|
||||
"tag": "plain_text",
|
||||
"content": ""
|
||||
},
|
||||
"template": "red",
|
||||
"icon": {
|
||||
"tag": "standard_icon",
|
||||
"token": "succeed-hollow_filled"
|
||||
},
|
||||
"padding": "12px 12px 12px 12px"
|
||||
}
|
||||
}
|
||||
173
template/card_notify.tmpl
Normal file
173
template/card_notify.tmpl
Normal file
@ -0,0 +1,173 @@
|
||||
{
|
||||
"schema": "2.0",
|
||||
"config": {
|
||||
"update_multi": true,
|
||||
"style": {
|
||||
"text_size": {
|
||||
"normal_v2": {
|
||||
"default": "normal",
|
||||
"pc": "normal",
|
||||
"mobile": "heading"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"direction": "vertical",
|
||||
"padding": "12px 12px 12px 12px",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">来源</font>\n**{{.Host}}**",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">错误信息</font>\n**{{.EventName}}**",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">告警等级</font>\n**{{.Severity}}**",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">告警时间</font>\n**{{.AlarmTime}}**",
|
||||
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"direction": "vertical",
|
||||
"horizontal_spacing": "8px",
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "hr",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
},
|
||||
{
|
||||
"tag": "column_set",
|
||||
"horizontal_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"columns": [
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">情况说明</font>\n**{{.Info}}**",
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"tag": "column",
|
||||
"width": "weighted",
|
||||
"elements": [
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": "<font color=\"grey\">处理人</font>\n<at id='{{.Id}}'></at>",
|
||||
"text_align": "left",
|
||||
"text_size": "normal_v2",
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
],
|
||||
"vertical_spacing": "8px",
|
||||
"horizontal_align": "left",
|
||||
"vertical_align": "top",
|
||||
"weight": 1
|
||||
}
|
||||
],
|
||||
"margin": "0px 0px 0px 0px"
|
||||
}
|
||||
]
|
||||
},
|
||||
"header": {
|
||||
"title": {
|
||||
"tag": "plain_text",
|
||||
"content": "[已处理] 告警通知:{{.NotifyMsg}}"
|
||||
},
|
||||
"subtitle": {
|
||||
"tag": "plain_text",
|
||||
"content": ""
|
||||
},
|
||||
"template": "green",
|
||||
"icon": {
|
||||
"tag": "standard_icon",
|
||||
"token": "succeed-hollow_filled"
|
||||
},
|
||||
"padding": "12px 12px 12px 12px"
|
||||
}
|
||||
}
|
||||
23
unit_test.go
23
unit_test.go
@ -1,26 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"backend/Type"
|
||||
"backend/controller"
|
||||
"backend/feishu"
|
||||
"backend/util"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXxx1(t *testing.T) {
|
||||
// controller.FeishuSendInfo(nil)
|
||||
str := `
|
||||
## 日期:2021-09-01
|
||||
## 代办事项
|
||||
- [ ] 任务1
|
||||
- [ ] 任务2
|
||||
- [ ] 任务3
|
||||
`
|
||||
str = util.ToTmplStr(str)
|
||||
fmt.Println(str)
|
||||
d := Type.NotifyData{
|
||||
Host: "Zabbix tencent-test",
|
||||
AlarmTime: "2025-03-06 19:12:56",
|
||||
Severity: "High",
|
||||
EventName: "游戏进程down-test-node-1",
|
||||
NotifyMsg: "服务器出错请及时处理",
|
||||
}
|
||||
feishu.SendNotifyMsg(&d)
|
||||
}
|
||||
func TestXxx(t *testing.T) {
|
||||
controller.FeishuSendInfo2(nil)
|
||||
controller.FeishuSendInfo(nil)
|
||||
// util.GetOrderData(3)
|
||||
}
|
||||
|
||||
func TestFeishu(t *testing.T) {
|
||||
|
||||
@ -51,7 +51,7 @@ func remain(Day int, LogDb *Db, AppConfig *Type.AppStruct) {
|
||||
}
|
||||
}()
|
||||
|
||||
now := ZeroTimestampByTz(AppConfig.Tz) + int64(Day)*86400
|
||||
now, _ := GetZeroTimestamp(AppConfig.Tz, Day)
|
||||
Date := time.Unix(now, 0).In(loc).Format("2006-01-02")
|
||||
SecondRemain, _ := DayRemain(now, 1, LogDb) // 计算次留
|
||||
ThirdRemain, _ := DayRemain(now, 2, LogDb) // 计算三留
|
||||
@ -218,10 +218,8 @@ func GetOperation(AppId int) (*Type.Operation, error) {
|
||||
defer Db.Close()
|
||||
Retain := []*Type.Retain{}
|
||||
|
||||
ZeroTimestamp := ZeroTimestampByTz("Europe/London") - 86400
|
||||
ZeroTime := time.Unix(ZeroTimestamp, 0).In(time.UTC)
|
||||
StartDate := ZeroTime.AddDate(0, 0, -14).Format("2006-01-02")
|
||||
EndDate := ZeroTime.Format("2006-01-02")
|
||||
StartDate, _ := GetDateStr("Europe/London", -14)
|
||||
EndDate, _ := GetDateStr("Europe/London", -1)
|
||||
err = Db.Select(&Retain, "SELECT `Date`, `Register`, `SecondRemain`, `ThirdRemain`, `SeventhRemain`, `ThirtiethRemain`, `Recharge`, `Login`, `Ext` FROM remain where `Date` >= ? and `Date` <= ? order by `Date` desc", StartDate, EndDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select data: %v", err)
|
||||
@ -236,8 +234,9 @@ func GetOperation(AppId int) (*Type.Operation, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select data: %v", err)
|
||||
}
|
||||
InactiveTime, _ := GetZeroTimestamp("Europe/London", -8)
|
||||
var InactiveUsers int
|
||||
err = Db.Get(&InactiveUsers, "SELECT count(distinct Uid) as count from (SELECT Uid, MAX(Timestamp) as LastLogin FROM log_login WHERE Event = 'Login_log' GROUP BY Uid) as lt where lastlogin < ?", ZeroTimestamp-7*86400)
|
||||
err = Db.Get(&InactiveUsers, "SELECT count(distinct Uid) as count from (SELECT Uid, MAX(Timestamp) as LastLogin FROM log_login WHERE Event = 'Login_log' GROUP BY Uid) as lt where lastlogin < ?", InactiveTime)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select data: %v", err)
|
||||
}
|
||||
@ -292,7 +291,7 @@ func GetDailyHeat(AppId int) (*Type.HeatData, error) {
|
||||
if LogDb == nil {
|
||||
return nil, fmt.Errorf("failed to get mysql database")
|
||||
}
|
||||
now := ZeroTimestampByTz(AppConfig.Tz)
|
||||
now, _ := GetZeroTimestamp(AppConfig.Tz, 0)
|
||||
value := make([]int, 0, 24)
|
||||
value2 := make([]int, 0, 24)
|
||||
key := make([]string, 0, 24)
|
||||
@ -354,7 +353,7 @@ func GetLevelData(AppId int) (*Type.LevelData, error) {
|
||||
Uid int `db:"Uid"`
|
||||
}
|
||||
InactiveUsers := []Users{}
|
||||
ZeroTimestamp := ZeroTimestampByTz(AppConfig.Tz)
|
||||
ZeroTimestamp, _ := GetZeroTimestamp(AppConfig.Tz, 0)
|
||||
err = LogDb.Select(&InactiveUsers, "SELECT Uid from (SELECT Uid, MAX(Timestamp) as LastLogin FROM log_login WHERE Event = 'Login_log' GROUP BY Uid) as lt where lastlogin < ?", ZeroTimestamp-7*86400)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select data: %v", err)
|
||||
@ -445,7 +444,7 @@ func GetDecorateData(AppId int) (*Type.DecorateData, error) {
|
||||
Uid int `db:"Uid"`
|
||||
}
|
||||
InactiveUsers := []Users{}
|
||||
ZeroTimestamp := ZeroTimestampByTz(AppConfig.Tz)
|
||||
ZeroTimestamp, _ := GetZeroTimestamp(AppConfig.Tz, 0)
|
||||
err = LogDb.Select(&InactiveUsers, "SELECT Uid from (SELECT Uid, MAX(Timestamp) as LastLogin FROM log_login WHERE Event = 'Login_log' GROUP BY Uid) as lt where lastlogin < ?", ZeroTimestamp-7*86400)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select data: %v", err)
|
||||
@ -516,3 +515,97 @@ func GetDecorateData(AppId int) (*Type.DecorateData, error) {
|
||||
Value2: value2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetOrderData(AppId int) (interface{}, error) {
|
||||
AppConfig, err := GetAppConfig(AppId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
LogDb := MPool.GetTopicDB(AppConfig.Topic)
|
||||
defer LogDb.Close()
|
||||
if LogDb == nil {
|
||||
return nil, fmt.Errorf("failed to get mysql database")
|
||||
}
|
||||
type data struct {
|
||||
Param string `db:"param"`
|
||||
Uid int `db:"Uid"`
|
||||
Timestamp int64 `db:"Timestamp"`
|
||||
}
|
||||
dataList := []data{}
|
||||
err = LogDb.Select(&dataList, "SELECT `param`, `Uid`, `Timestamp` FROM log_event WHERE `Event` = 'order_finish'")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select data: %v", err)
|
||||
}
|
||||
type Users struct {
|
||||
Uid int `db:"Uid"`
|
||||
}
|
||||
InactiveUsers := []Users{}
|
||||
ZeroTimestamp, _ := GetZeroTimestamp(AppConfig.Tz, 0)
|
||||
err = LogDb.Select(&InactiveUsers, "SELECT Uid from (SELECT Uid, MAX(Timestamp) as LastLogin FROM log_login WHERE Event = 'Login_log' GROUP BY Uid) as lt where lastlogin < ?", ZeroTimestamp-7*86400)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select data: %v", err)
|
||||
}
|
||||
d0 := make([]int, 0)
|
||||
for _, v := range InactiveUsers {
|
||||
d0 = append(d0, v.Uid)
|
||||
}
|
||||
d1 := make(map[int]data)
|
||||
for _, v := range dataList {
|
||||
if _, ok := d1[v.Uid]; !ok {
|
||||
d1[v.Uid] = v
|
||||
}
|
||||
if v.Timestamp > d1[v.Uid].Timestamp {
|
||||
d1[v.Uid] = v
|
||||
}
|
||||
}
|
||||
type data2 struct {
|
||||
Num int
|
||||
Churn int
|
||||
}
|
||||
d2 := make(map[int]*data2)
|
||||
for _, v := range d1 {
|
||||
var jd struct {
|
||||
Step int `json:"order_id"`
|
||||
}
|
||||
err := json.Unmarshal([]byte(v.Param), &jd)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := d2[jd.Step]; !ok {
|
||||
d2[jd.Step] = &data2{
|
||||
Num: 0,
|
||||
Churn: 0,
|
||||
}
|
||||
}
|
||||
d2[jd.Step].Num++
|
||||
if InArray(v.Uid, d0) {
|
||||
d2[jd.Step].Churn++
|
||||
}
|
||||
}
|
||||
|
||||
key := make([]int, 0, len(d2))
|
||||
for k := range d2 {
|
||||
key = append(key, k)
|
||||
}
|
||||
sort.Slice(key, func(i, j int) bool {
|
||||
return i < j
|
||||
})
|
||||
value := make([]int, 0, len(d2))
|
||||
value2 := make([]int, 0, len(d2))
|
||||
for _, v := range key {
|
||||
value = append(value, d2[v].Num)
|
||||
value2 = append(value2, d2[v].Churn)
|
||||
}
|
||||
for i := 1; i < 30; i++ {
|
||||
_, ok := d2[i]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("key:%d, value:%.2f%%, value2:%d\n", i+1, float64(d2[i].Num)/13, d2[i].Churn)
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"Key": key,
|
||||
"Value": value,
|
||||
"Value2": value2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
101
util/time.go
101
util/time.go
@ -1,6 +1,9 @@
|
||||
package util
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ZeroTimestamp() int64 {
|
||||
now := time.Now()
|
||||
@ -8,14 +11,96 @@ func ZeroTimestamp() int64 {
|
||||
return midnight.Unix()
|
||||
}
|
||||
|
||||
func ZeroTimestampByTz(tz string) int64 {
|
||||
loc, _ := time.LoadLocation(tz)
|
||||
now := time.Now().In(loc)
|
||||
midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
return midnight.Unix()
|
||||
}
|
||||
|
||||
func GetHour(ts int64, tz string) int {
|
||||
loc, _ := time.LoadLocation(tz)
|
||||
return time.Unix(ts, 0).In(loc).Hour()
|
||||
}
|
||||
|
||||
// 将ISO 8601格式的时间字符串转换为指定时区的时间戳
|
||||
// timeStr: ISO 8601格式的时间字符串,如:"2025-03-17T16:00:00.000Z"
|
||||
// timezone: 时区字符串,如:"Asia/Shanghai","America/New_York",留空则使用UTC
|
||||
func ParseTimeToTimestamp(timeStr string, timezone string) (int64, error) {
|
||||
// 先解析为UTC时间
|
||||
t, err := time.Parse(time.RFC3339, timeStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 如果没有指定时区,直接返回UTC时间戳
|
||||
if timezone == "" {
|
||||
return t.Unix(), nil
|
||||
}
|
||||
|
||||
// 加载指定时区
|
||||
loc, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid timezone: %v", err)
|
||||
}
|
||||
|
||||
// 将时间转换到指定时区
|
||||
t = t.In(loc)
|
||||
return t.Unix(), nil
|
||||
}
|
||||
|
||||
// GetZeroTimestamp 获取指定时区的当天0点时间戳
|
||||
// timezone: 时区字符串,如:"Asia/Shanghai","America/New_York",留空则使用UTC
|
||||
// offset: 日期偏移,0表示今天,-1表示昨天,1表示明天,以此类推
|
||||
func GetZeroTimestamp(timezone string, offset int) (int64, error) {
|
||||
var loc *time.Location
|
||||
var err error
|
||||
|
||||
// 如果没有指定时区,使用UTC
|
||||
if timezone == "" {
|
||||
loc = time.UTC
|
||||
} else {
|
||||
// 加载指定时区
|
||||
loc, err = time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid timezone: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前时间并转换到指定时区
|
||||
now := time.Now().In(loc)
|
||||
|
||||
// 应用日期偏移
|
||||
if offset != 0 {
|
||||
now = now.AddDate(0, 0, offset)
|
||||
}
|
||||
|
||||
// 获取当天0点时间
|
||||
zero := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
|
||||
|
||||
return zero.Unix(), nil
|
||||
}
|
||||
|
||||
// GetDateStr 获取指定时区的日期字符串
|
||||
// timezone: 时区字符串,如:"Asia/Shanghai","America/New_York",留空则使用UTC
|
||||
// offset: 日期偏移,0表示今天,-1表示昨天,1表示明天,以此类推
|
||||
// 返回格式:"2006-01-02"
|
||||
func GetDateStr(timezone string, offset int) (string, error) {
|
||||
var loc *time.Location
|
||||
var err error
|
||||
|
||||
// 如果没有指定时区,使用UTC
|
||||
if timezone == "" {
|
||||
loc = time.UTC
|
||||
} else {
|
||||
// 加载指定时区
|
||||
loc, err = time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid timezone: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前时间并转换到指定时区
|
||||
now := time.Now().In(loc)
|
||||
|
||||
// 应用日期偏移
|
||||
if offset != 0 {
|
||||
now = now.AddDate(0, 0, offset)
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
return now.Format("2006-01-02"), nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user