diff --git a/admin/apps/game/domain/gamelog.go b/admin/apps/game/domain/gamelog.go index 623c75c..43b5825 100644 --- a/admin/apps/game/domain/gamelog.go +++ b/admin/apps/game/domain/gamelog.go @@ -8,9 +8,9 @@ import ( "admin/internal/model/dto" "admin/lib/xlog" "context" - "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "github.com/jmoiron/sqlx" "gorm.io/gorm" + "strconv" "time" ) @@ -19,11 +19,11 @@ type GameLogService struct { repo repo.IGameLogRepo } -func NewGameLogSvc(db *gorm.DB, metaDb *gorm.DB, ckDb driver.Conn, clickHouseSqlx *sqlx.DB) *GameLogService { - return &GameLogService{projectRepo: repo.NewProjectRepo(db), repo: repo.NewGameLogRepo(metaDb, ckDb, clickHouseSqlx)} +func NewGameLogSvc(db *gorm.DB, metaDb *gorm.DB, clickHouseSqlx *sqlx.DB) *GameLogService { + return &GameLogService{projectRepo: repo.NewProjectRepo(db), repo: repo.NewGameLogRepo(metaDb, clickHouseSqlx)} } -func (svc *GameLogService) QueryEventList(projectId, appId int, eventName []string, serverId int, account, roleId string, +func (svc *GameLogService) QueryEventList(projectId int, eventName []string, serverId int, account, roleId string, pageNo, pageLen int, dateStart, dateEnd time.Time) (totalCount int, fieldsDescInfo []*dto.GameLogFieldInfo, rows [][]any, err error) { _, projectEt, find, err := svc.projectRepo.GetById(projectId) @@ -33,6 +33,11 @@ func (svc *GameLogService) QueryEventList(projectId, appId int, eventName []stri if !find { return 0, nil, nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId) } + appId := projectEt.Po.BiAdminAppId + + if appId == 0 { + return 0, make([]*dto.GameLogFieldInfo, 0), make([][]any, 0), nil + } var attrList []*repo.AttrInfo attrList, err = svc.repo.QueryAttrListByEvent(appId, eventName) @@ -40,7 +45,7 @@ func (svc *GameLogService) QueryEventList(projectId, appId int, eventName []stri return } - querier := svc.repo.NewEventListQuerier("xwl_event41", eventName, attrList) + querier := svc.repo.NewEventListQuerier("xwl_event"+strconv.Itoa(appId), eventName, attrList) totalCount, fieldsDescInfo, rows, err = querier.CondRoleId(serverId, roleId).CondAccount(serverId, account). Go(context.Background(), pageNo, pageLen, dateStart, dateEnd) if err != nil { diff --git a/admin/apps/game/domain/projects/smdl/gamelog.go b/admin/apps/game/domain/projects/smdl/gamelog.go index bb9e40d..5fa1fec 100644 --- a/admin/apps/game/domain/projects/smdl/gamelog.go +++ b/admin/apps/game/domain/projects/smdl/gamelog.go @@ -5,7 +5,11 @@ import ( dto2 "admin/internal/model/dto" "admin/lib/xlog" "encoding/base64" + "encoding/json" + "fmt" "strconv" + "strings" + "time" ) type GameLogHook struct { @@ -17,7 +21,7 @@ var removeFields = map[string]struct{}{ "pub_udid": {}, } -var alias = map[string]string{ +var fieldsAlias = map[string]string{ "pub_viplev": "vip等级", "pub_userid": "账号", "pub_totalcash": "总充值金额", @@ -61,14 +65,15 @@ func (hook *GameLogHook) Trim(projectInfo *entity.Project, eventName []string, t // 找到这个字段在去掉的列表里 fieldsDescInfo = append(fieldsDescInfo[:i], fieldsDescInfo[i+1:]...) // 对应所有数据行里也删除这个值 - for _, row := range rows { + for rowI, row := range rows { row = append(row[:i], row[i+1:]...) + rows[rowI] = row } } else { // 查找要不要把value做base64解码 if _, find := base64decodeFields[field.Name]; find { for _, row := range rows { - newValue, err := base64.StdEncoding.DecodeString((*(row[i].(*interface{}))).(string)) + newValue, err := base64.StdEncoding.DecodeString(row[i].(string)) if err != nil { xlog.Warnf("base64 decode field:%v value:%v error:%v", field.Name, row[i], err) } else { @@ -80,31 +85,206 @@ func (hook *GameLogHook) Trim(projectInfo *entity.Project, eventName []string, t } } - // 查找别名 - for _, en := range eventName { - // 查找全局eventName描述表信息,对里面没有别的字段名改为表里的名字 - descInfo, find := globEventList[en] - if find { - for _, f := range fieldsDescInfo { - find := false - for _, f1 := range descInfo.fields { - if f.Name == f1.Name && f1.Alias != "" { - if f.Name == f.Alias { - f.Alias = f1.Alias - } - find = true - break - } + //fBin, _ := json.Marshal(&fieldsDescInfo) + //rBin, _ := json.Marshal(&rows) + //xlog.Tracef("gamelog1 query result:%v, rows:%v", string(fBin), string(rBin)) + + fieldsDescInfo, rows = (&queryResultInfo{fields: fieldsDescInfo, rows: rows}).tidyByEventDescInfo(eventName) + + //fBin, _ = json.Marshal(&fieldsDescInfo) + //rBin, _ = json.Marshal(&rows) + //xlog.Tracef("gamelog1 query result:%v, rows:%v", string(fBin), string(rBin)) + + for i, f := range fieldsDescInfo { + // 修改每一行日志别名 + if f.Name == "xwl_part_event" { + for j := range rows { + opEventName := rows[j][i].(string) + descInfo, find := globEventList[opEventName] + if find { + rows[j][i] = descInfo.Alias } - if !find { - aliasName, find1 := alias[f.Name] - if find1 && f.Name == f.Alias && f.Alias != "" { - f.Alias = aliasName + } + } else if f.Name == "xwl_part_date" { + for j := range rows { + eventDate := rows[j][i].(time.Time) + //eventDate, err := time.Parse("2006-01-02T15:04:05+07:00", eventDateString) + rows[j][i] = eventDate.Format(time.DateTime) + } + } + // 修改每一行公共属性别名 + alias, find := fieldsAlias[f.Name] + if find && f.Name == f.Alias { + f.Alias = alias + } + } + + fBin, _ := json.Marshal(&fieldsDescInfo) + rBin, _ := json.Marshal(&rows) + xlog.Tracef("gamelog2 query result:%v, rows:%v", string(fBin), string(rBin)) + + i = 0 + for { + if i >= len(fieldsDescInfo) { + break + } + f := fieldsDescInfo[i] + swapI := 2 + if f.Name == "pub_serverid" || f.Name == "pub_roleid" || f.Name == "pub_rolename" { + pubF := fieldsDescInfo[i] + for moveI := i; moveI > swapI; moveI-- { + fieldsDescInfo[moveI] = fieldsDescInfo[moveI-1] + } + fieldsDescInfo[swapI] = pubF + + for _, row := range rows { + pubValue := row[i] + for moveI := i; moveI > swapI; moveI-- { + row[moveI] = row[moveI-1] + } + row[swapI] = pubValue + } + swapI++ + } + i++ + } + + return totalCount, fieldsDescInfo, rows +} + +type queryResultInfo struct { + fields []*dto2.GameLogFieldInfo + rows [][]any +} + +func (info *queryResultInfo) tidyByEventDescInfo(eventNameList []string) ([]*dto2.GameLogFieldInfo, [][]any) { + if len(eventNameList) == 0 { + return info.fields, info.rows + } + if len(eventNameList) == 1 { + en := eventNameList[0] + enDescInfo, find := globEventList[en] + if !find { + panic(en) + } + for _, f := range info.fields { + for _, df := range enDescInfo.fields { + fName := removeFieldEventName(en, f.Name) + if fName == df.Name && f.Name == f.Alias { + f.Alias = df.Alias + break + } + } + } + return info.fields, info.rows + } + + // 神魔的埋点里字段名是日志名_字段名,所以如果一次游戏日志查询包含多个事件名,会查出稀疏数据, + // 例如gainitem,loseitem两个日志,都会存在gainitem_itemid,loseitem_itemid,需要把对应 + // 为0的稀疏字段去掉 + + // 具体方案: + // 1.创建新的行数据,将自己日志的字段值先追加进来,然后前面补上日志名,后面补上公共字段, + // 2.字段描述信息直接全部去重 + + newRows := make([][]any, len(info.rows)) + for rowI, row := range info.rows { + curRowEventName := row[0].(string) + enDescInfo, find := globEventList[curRowEventName] + if !find { + // 不存在描述信息,先崩溃下 + panic(curRowEventName) + } + + for _, fieldDesc := range enDescInfo.fields { + for i, field := range info.fields { + fName := removeFieldEventName(curRowEventName, field.Name) + if fName == fieldDesc.Name { + newRows[rowI] = append(newRows[rowI], row[i]) + break + } + } + } + } + + // 上面步骤走完,newRows每一行都是各自日志的字段值了,但是每一行长度可能不一样,例如gainitem就比loseitem多一个字段 + // 下面步骤会把字段描述信息通过事件名具有的所有字段去重追加,例如gainitem就是全量的字段,loseitem就是子集 + newFieldsDescInfo := make([]*dto2.GameLogFieldInfo, 0, len(info.fields)) + for _, en := range eventNameList { + enDescInfo, find := globEventList[en] + if !find { + panic(en) + } + find = false + for _, fd := range enDescInfo.fields { + for _, newFd := range newFieldsDescInfo { + if fd.Name == newFd.Name { + find = true + break + } + } + if !find { + // 追加 + for _, oldFd := range info.fields { + fName := removeFieldEventName(en, oldFd.Name) + if fName == fd.Name { + newFieldInfo := &dto2.GameLogFieldInfo{ + Name: fName, + Alias: oldFd.Alias, + IsPublicField: oldFd.IsPublicField, + FieldType: oldFd.FieldType, + } + if oldFd.Name == oldFd.Alias { + newFieldInfo.Alias = fd.Alias + } + newFieldsDescInfo = append(newFieldsDescInfo, newFieldInfo) + break } } } } } - return totalCount, fieldsDescInfo, rows + // 走完上面这一步,newFieldsDescInfo就是事件列表具有的所有去重字段了 + allEventFieldsLen := len(newFieldsDescInfo) + + // 给newRows和newFieldsDescInfo前追加公共字段了 + // 追加前面的日志名、时间 + tmpFields := make([]*dto2.GameLogFieldInfo, 2) + copy(tmpFields, info.fields[:2]) + newFieldsDescInfo = append(tmpFields, newFieldsDescInfo...) + for i, newRow := range newRows { + if len(newRow) < allEventFieldsLen { + rowCols := len(newRow) + for j := 0; j < allEventFieldsLen-rowCols; j++ { + // 补充这一行日志没有的字段 + newRow = append(newRow, "\\") + } + } else if len(newRow) > allEventFieldsLen { + panic(fmt.Sprintf("%v,%v", len(newRow), allEventFieldsLen)) + } + tmpCols := make([]any, 2) + copy(tmpCols, info.rows[i][:2]) + newRow = append(tmpCols, newRow...) + newRows[i] = newRow + } + + // 给newRows和newFieldsDescInfo后追加公共字段了 + for i, f := range info.fields { + if len(f.Name) >= 4 && f.Name[:4] == "pub_" { + newFieldsDescInfo = append(newFieldsDescInfo, info.fields[i:]...) + for rowI, newRow := range newRows { + newRow = append(newRow, info.rows[rowI][i:]...) + newRows[rowI] = newRow + } + break + } + } + + return newFieldsDescInfo, newRows +} + +// 因为推送到clickhouse的字段名都是日志名_字段名的格式,放置不同日志出现同名字段冲突,这里直接去掉一下日志名 +func removeFieldEventName(eventName, fieldName string) string { + return strings.ReplaceAll(fieldName, eventName+"_", "") } diff --git a/admin/apps/game/domain/projects/smdl/gamelog_desc.go b/admin/apps/game/domain/projects/smdl/gamelog_desc.go index 0dd768f..abd1e81 100644 --- a/admin/apps/game/domain/projects/smdl/gamelog_desc.go +++ b/admin/apps/game/domain/projects/smdl/gamelog_desc.go @@ -37,7 +37,7 @@ func init() { param, find := event["param"+strconv.Itoa(i)] if find && param != nil { fields = append(fields, &eventFieldInfo{ - Name: eventName + "_" + param.(string), + Name: param.(string), Alias: event["paramDesc"+strconv.Itoa(i)].(string), }) } diff --git a/admin/apps/game/domain/repo/gamelog.go b/admin/apps/game/domain/repo/gamelog.go index a6cd823..b6607ee 100644 --- a/admin/apps/game/domain/repo/gamelog.go +++ b/admin/apps/game/domain/repo/gamelog.go @@ -4,10 +4,8 @@ import ( "admin/apps/game/model" "admin/internal/errcode" "admin/lib/utils" - "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "github.com/jmoiron/sqlx" "gorm.io/gorm" - "sort" ) type IGameLogRepo interface { @@ -15,13 +13,12 @@ type IGameLogRepo interface { NewEventListQuerier(tableName string, eventName []string, attrList []*AttrInfo) *EventListQuerier } -func NewGameLogRepo(metaDb *gorm.DB, ckDb driver.Conn, clickHouseSqlx *sqlx.DB) IGameLogRepo { - return &gameLogRepoImpl{metaDb: metaDb, db: ckDb, clickHouseSqlx: clickHouseSqlx} +func NewGameLogRepo(metaDb *gorm.DB, clickHouseSqlx *sqlx.DB) IGameLogRepo { + return &gameLogRepoImpl{metaDb: metaDb, clickHouseSqlx: clickHouseSqlx} } type gameLogRepoImpl struct { metaDb *gorm.DB - db driver.Conn clickHouseSqlx *sqlx.DB } @@ -56,64 +53,80 @@ func (impl *gameLogRepoImpl) QueryAttrListByEvent(appId int, eventName []string) return nil, errcode.New(errcode.DBError, "join tables get event list error:%v", err) } - attrList := make([]*AttrInfo, 0) + normalAttrList := make([]*AttrInfo, 0) + publicAttrList := make([]*AttrInfo, 0) + var eventNameAttr *AttrInfo var timeAttr *AttrInfo for _, attr := range resultList { if len(eventName) == 0 || utils.IsInSlice(eventName, attr.EventName) { if len(attr.AttrName) >= 4 && attr.AttrName[:4] == "xwl_" { - if attr.AttrName != "xwl_part_date" { + + if attr.AttrName != "xwl_part_date" && attr.AttrName != "xwl_part_event" { continue } - timeAttr = &AttrInfo{ - Name: attr.AttrName, - Alias: "时间", - DataType: attr.AttrFieldType, - IsUserAttr: attr.AttrIsPublicField != 2, + if attr.AttrName == "xwl_part_date" { + timeAttr = &AttrInfo{ + Name: attr.AttrName, + Alias: "时间", + DataType: attr.AttrFieldType, + IsUserAttr: true, + } + } else if attr.AttrName == "xwl_part_event" { + eventNameAttr = &AttrInfo{ + Name: attr.AttrName, + Alias: "操作类型", + DataType: attr.AttrFieldType, + IsUserAttr: true, + } } + continue } - attrList = append(attrList, &AttrInfo{ - Name: attr.AttrName, - Alias: attr.AttrAlias, - DataType: attr.AttrFieldType, - IsUserAttr: attr.AttrIsPublicField != 2, - }) + find := false + for _, oldAttr := range normalAttrList { + if oldAttr.Name == attr.AttrName { + find = true + break + } + } + if !find { + for _, oldAttr := range publicAttrList { + if oldAttr.Name == attr.AttrName { + find = true + break + } + } + } + if !find { + var isPublicField bool + if len(attr.AttrName) >= 4 { + isPublicField = attr.AttrName[:4] == "pub_" + } + attrInfo := &AttrInfo{ + Name: attr.AttrName, + Alias: attr.AttrAlias, + DataType: attr.AttrFieldType, + IsUserAttr: isPublicField, + } + if isPublicField { + publicAttrList = append(publicAttrList, attrInfo) + } else { + normalAttrList = append(normalAttrList, attrInfo) + } + } + } } - sort.SliceStable(attrList, func(i, j int) bool { - if attrList[i].IsUserAttr && !attrList[j].IsUserAttr { - return false - } - if attrList[i].IsUserAttr && attrList[j].IsUserAttr { - return false - } - if attrList[i].IsUserAttr && attrList[j].IsUserAttr { - return false - } - return true - }) if timeAttr != nil { - attrList = append([]*AttrInfo{timeAttr}, attrList...) + normalAttrList = append([]*AttrInfo{timeAttr}, normalAttrList...) + } + if eventNameAttr != nil { + normalAttrList = append([]*AttrInfo{eventNameAttr}, normalAttrList...) } - //retList := make(map[string][]*AttrInfo) - //for _, attr := range resultList { - // attrList, find := retList[attr.EventName] - // if !find { - // attrList = make([]*AttrInfo, 0, 1) - // } - // attrList = append(attrList, &AttrInfo{ - // Name: attr.AttrName, - // Alias: attr.AttrAlias, - // DataType: attr.AttrFieldType, - // IsUserAttr: attr.AttrIsPublicField != 2, - // }) - // retList[attr.EventName] = attrList - //} - - return attrList, nil + return append(normalAttrList, publicAttrList...), nil } type AttrInfo struct { @@ -125,7 +138,6 @@ type AttrInfo struct { func (impl *gameLogRepoImpl) NewEventListQuerier(tableName string, eventName []string, attrList []*AttrInfo) *EventListQuerier { querier := new(EventListQuerier) - querier.db = impl.db querier.clickHouseSqlx = impl.clickHouseSqlx querier.tableName = tableName querier.eventName = eventName diff --git a/admin/apps/game/domain/repo/gamelog_querier.go b/admin/apps/game/domain/repo/gamelog_querier.go index bc8c0f4..16ca9cc 100644 --- a/admin/apps/game/domain/repo/gamelog_querier.go +++ b/admin/apps/game/domain/repo/gamelog_querier.go @@ -62,6 +62,13 @@ func (querier *EventListQuerier) Go(ctx context.Context, pageNo int, pageLen int if err != nil { return } + + if xlog.GetLogLevel() <= xlog.LogLevelDebug { + argsBin, _ := json.Marshal(&sqlArgs) + rowsBin, _ := json.Marshal(&rows) + xlog.Debugf("query sql:%v with args:%v, rows result:%v", sql, string(argsBin), string(rowsBin)) + } + return } @@ -85,12 +92,17 @@ func (querier *EventListQuerier) genSql(tableName string, pageNo int, pageLen in whereList := make([]string, 0) whereArgs := make([]any, 0) if len(querier.eventName) != 0 { - eventNameWhereSql := make([]string, 0, len(querier.eventName)) - for _, v := range querier.eventName { - eventNameWhereSql = append(eventNameWhereSql, "`xwl_part_event`=?") - whereArgs = append(whereArgs, v) + if len(querier.eventName) == 1 { + whereList = append(whereList, "`xwl_part_event`=?") + whereArgs = append(whereArgs, querier.eventName[0]) + } else { + eventNameWhereSql := make([]string, 0, len(querier.eventName)) + for _, v := range querier.eventName { + eventNameWhereSql = append(eventNameWhereSql, "`xwl_part_event`=?") + whereArgs = append(whereArgs, v) + } + whereList = append(whereList, "("+strings.Join(eventNameWhereSql, " or ")+")") } - whereList = append(whereList, "("+strings.Join(eventNameWhereSql, " or ")+")") } if querier.serverId != 0 { @@ -116,7 +128,7 @@ func (querier *EventListQuerier) genSql(tableName string, pageNo int, pageLen in limitStart := (pageNo - 1) * pageLen limitLen := pageLen - sql += fmt.Sprintf(" limit %v,%v", limitStart, limitLen) + sql += fmt.Sprintf(" order by xwl_part_date desc limit %v,%v", limitStart, limitLen) return sql, countSql, whereArgs } @@ -170,6 +182,7 @@ func (querier *EventListQuerier) query(sql string, countSql string, args []any) Name: field.Name, Alias: field.Name, IsPublicField: false, + FieldType: field.DataType, } if field.Alias != "" { fieldDesc.Alias = field.Alias @@ -185,8 +198,16 @@ func (querier *EventListQuerier) query(sql string, countSql string, args []any) if err != nil { return 0, nil, nil, errcode.New(errcode.DBError, "sql:%v result scan row error:%v", sql, err) } + if xlog.GetLogLevel() <= xlog.LogLevelTrace { + argsBin, _ := json.Marshal(&args) + cacheRowBin, _ := json.Marshal(&readCacheRow) + xlog.Tracef("query sql:%v with args:%+v result row:%v", sql, string(argsBin), string(cacheRowBin)) + } + parsedRow := make([]any, len(readCacheRow)) - copy(parsedRow, readCacheRow) + for i := range readCacheRow { + parsedRow[i] = *(readCacheRow[i].(*interface{})) + } rows = append(rows, parsedRow) } diff --git a/admin/apps/game/model/project.go b/admin/apps/game/model/project.go index 6450576..542b955 100644 --- a/admin/apps/game/model/project.go +++ b/admin/apps/game/model/project.go @@ -25,13 +25,14 @@ type Project struct { // 不为空就代表项目要实现一个自己统一对外暴露的gm调用服务对内聚合、分发指令执行,本后台执行指令只调用一次; // 为空就代表command_list实现在各个逻辑服,由本后台系统在执行指令时聚合、分发指令 // 调用各个逻辑服执行,本后台执行指令需要根据逻辑服数量调用; - ApiAddr string `name:"游戏api地址" desc:"api服务器地址,例如神魔大陆就是alisrv服务器地址,用于后台调用gm"` - DfsBucket string `name:"分布式存储桶名" desc:"上传服务器列表分布式存储的桶名"` - DfsRegion string `name:"分布式存储区域" desc:"上传服务器列表分布式存储的桶区域例如ap-chengdu"` - CdnPath string `name:"cdn地址" desc:"拉取服务器列表文件的cdn"` - SortWeight int `name:"菜单排序" desc:"越大越靠前"` - CreatedAt time.Time `readonly:"true"` - UpdatedAt time.Time `readonly:"true"` + ApiAddr string `name:"游戏api地址" desc:"api服务器地址,例如神魔大陆就是alisrv服务器地址,用于后台调用gm"` + DfsBucket string `name:"分布式存储桶名" desc:"上传服务器列表分布式存储的桶名"` + DfsRegion string `name:"分布式存储区域" desc:"上传服务器列表分布式存储的桶区域例如ap-chengdu"` + CdnPath string `name:"cdn地址" desc:"拉取服务器列表文件的cdn"` + BiAdminAppId int `name:"bi后台appid" desc:"用于查找所属游戏日志"` + SortWeight int `name:"菜单排序" desc:"越大越靠前"` + CreatedAt time.Time `readonly:"true"` + UpdatedAt time.Time `readonly:"true"` } func (lm *Project) TableName() string { diff --git a/admin/apps/game/server/ctl_game.go b/admin/apps/game/server/ctl_game.go index e15e69b..b2cc624 100644 --- a/admin/apps/game/server/ctl_game.go +++ b/admin/apps/game/server/ctl_game.go @@ -3,6 +3,7 @@ package server import ( "admin/internal/consts" "admin/internal/context" + "admin/internal/errcode" "admin/internal/model/dto" "admin/lib/httpclient" "admin/lib/xlog" @@ -78,11 +79,19 @@ func (ctl *controller) GameLogEventList(ctx *context.WebContext, params *dto.Gam de = time.Now().Add(time.Hour) } - eventList := strings.Split(params.EventName, ",") + var eventList []string + if params.EventName != "" { + eventList = strings.Split(params.EventName, ",") + for _, en := range eventList { + if en == "" { + return errcode.New(errcode.ParamsInvalid, "event name invalid:[%v]", params.EventName) + } + } + } var err error rsp.TotalCount, rsp.FieldsDescInfo, rsp.Rows, err = - ctl.svc.GameLogSvc.QueryEventList(projectId, params.AppId, + ctl.svc.GameLogSvc.QueryEventList(projectId, eventList, params.ServerId, params.Account, params.RoleId, params.PageNo, params.PageLen, ds, de) return err } diff --git a/admin/apps/game/service/service.go b/admin/apps/game/service/service.go index 513fc58..0cfdfce 100644 --- a/admin/apps/game/service/service.go +++ b/admin/apps/game/service/service.go @@ -7,11 +7,12 @@ import ( dbLib "admin/internal/db" "admin/internal/errcode" "admin/internal/event" + "admin/internal/global" dto2 "admin/internal/model/dto" "context" "encoding/json" "fmt" - "github.com/ClickHouse/clickhouse-go/v2" + _ "github.com/ClickHouse/clickhouse-go/v2" "github.com/jmoiron/sqlx" "gorm.io/gorm" "log" @@ -28,30 +29,30 @@ type Service struct { } func New(db *gorm.DB) (*Service, error) { - metaDb, err := dbLib.NewDBNoMigrate("mysql", "192.168.6.83:13306", "databi", "root", "dev123") - if err != nil { - return nil, err - } + var metaDb *gorm.DB + var clickHouseSqlx *sqlx.DB + var err error - dbSource := fmt.Sprintf( - "tcp://%s:%s?username=%s&password=%s&compress=true&database=%s", - "192.168.6.83", "9000", "root", "dev123", "databi") - - clickHouseSqlx, err := NewCkDb("clickhouse", dbSource, 5, 20) - if err != nil { - panic(err) - } - - conn, err := clickhouse.Open(&clickhouse.Options{ - Addr: []string{"192.168.78.128:9000"}, - Auth: clickhouse.Auth{ - Database: "databi_prod", - Username: "root", - Password: "dev123", - }, - }) - if err != nil { - return nil, errcode.New(errcode.DBError, "open clickhouse %v error:%v", "databi_prod", err) + if global.GLOB_BOOT_FLAGS.BiMysqlAddr != "" { + metaDb, err = dbLib.NewDBNoMigrate("mysql", + global.GLOB_BOOT_FLAGS.BiMysqlAddr, + global.GLOB_BOOT_FLAGS.BiMysqlDb, + global.GLOB_BOOT_FLAGS.BiMysqlUser, + global.GLOB_BOOT_FLAGS.BiMysqlPass) + if err != nil { + return nil, err + } + dbSource := fmt.Sprintf( + "tcp://%s?username=%s&password=%s&compress=true&database=%s", + global.GLOB_BOOT_FLAGS.BiCkAddr, + global.GLOB_BOOT_FLAGS.BiCkUser, + global.GLOB_BOOT_FLAGS.BiCkPass, + global.GLOB_BOOT_FLAGS.BiCkDb) + clickHouseSqlx, err = NewCkDb("clickhouse", dbSource, 5, 20) + if err != nil { + return nil, err + } + //"192.168.6.83", "9000", "root", "dev123", "databi") } svc := &Service{ @@ -60,7 +61,7 @@ func New(db *gorm.DB) (*Service, error) { projectSvc: domain.NewProjectService(db), cdkeySvc: domain.NewCDKeyService(db), accountSvc: domain.NewAccountService(db), - GameLogSvc: domain.NewGameLogSvc(db, metaDb, conn, clickHouseSqlx), + GameLogSvc: domain.NewGameLogSvc(db, metaDb, clickHouseSqlx), } api.RegisterGameApi(svc) //err := svc.ensureProjectsDBData() diff --git a/admin/internal/config/flags.go b/admin/internal/config/flags.go index 387d39a..b0d166c 100644 --- a/admin/internal/config/flags.go +++ b/admin/internal/config/flags.go @@ -1,10 +1,18 @@ package config type CommonBootFlags struct { - ApiPort string `env:"api_port" default:"8080" desc:"api端口,客户端请求的地址端口"` - DBType string `env:"db_type" default:"sqlite" desc:"数据库类型,默认sqlite,可选:sqlite|mysql|pg"` - DBAddr string `env:"db_addr" default:"localhost" desc:"数据库地址"` - DBName string `env:"db_name" default:"uniugm" desc:"数据库名字"` - DBUser string `env:"db_user" default:"root" desc:"数据库用户名"` - DBPass string `env:"db_pass" default:"" desc:"数据库密码"` + ApiPort string `env:"api_port" default:"8080" desc:"api端口,客户端请求的地址端口"` + DBType string `env:"db_type" default:"sqlite" desc:"数据库类型,默认sqlite,可选:sqlite|mysql|pg"` + DBAddr string `env:"db_addr" default:"localhost" desc:"数据库地址"` + DBName string `env:"db_name" default:"uniugm" desc:"数据库名字"` + DBUser string `env:"db_user" default:"root" desc:"数据库用户名"` + DBPass string `env:"db_pass" default:"" desc:"数据库密码"` + BiCkAddr string `env:"bi_ck_addr" desc:"clickhouse分析数据库地址"` + BiCkDb string `env:"bi_ck_db" desc:"clickhouse数据库"` + BiCkUser string `env:"bi_ck_user" desc:"clickhouse用户名"` + BiCkPass string `env:"bi_ck_pass" desc:"clickhouse密码"` + BiMysqlAddr string `env:"bi_mysql_addr" desc:"mysql分析数据库地址"` + BiMysqlDb string `env:"bi_mysql_db" desc:"mysql数据库"` + BiMysqlUser string `env:"bi_mysql_user" desc:"mysql用户名"` + BiMysqlPass string `env:"bi_mysql_pass" desc:"mysql密码"` } diff --git a/admin/internal/model/dto/common.go b/admin/internal/model/dto/common.go index dac2b0f..0a356e1 100644 --- a/admin/internal/model/dto/common.go +++ b/admin/internal/model/dto/common.go @@ -157,4 +157,5 @@ type GameLogFieldInfo struct { Name string `json:"name"` Alias string `json:"alias"` IsPublicField bool `json:"isPublicField"` + FieldType int `json:"fieldType"` } diff --git a/admin/internal/model/dto/msg_project.go b/admin/internal/model/dto/msg_project.go index 690c43a..ca62351 100644 --- a/admin/internal/model/dto/msg_project.go +++ b/admin/internal/model/dto/msg_project.go @@ -137,7 +137,6 @@ type GetRoleDetailRsp struct { } type GameLogEventListReq struct { - AppId int EventName string ServerId int Account string diff --git a/admin/ui/static/index.html b/admin/ui/static/index.html index d7546ef..406ad55 100644 --- a/admin/ui/static/index.html +++ b/admin/ui/static/index.html @@ -5,8 +5,8 @@
d?Z(e,a,i,!0,!1,p):O(t,o,r,a,i,l,s,u,p)},K=(e,t,o,r,a,i,l,s,u)=>{let c=0;const d=t.length;let p=e.length-1,h=d-1;for(;c<=p&&c<=h;){const n=e[c],r=t[c]=u?qr(t[c]):Yr(t[c]);if(!Hr(n,r))break;m(n,r,o,null,a,i,l,s,u),c++}for(;c<=p&&c<=h;){const n=e[p],r=t[h]=u?qr(t[h]):Yr(t[h]);if(!Hr(n,r))break;m(n,r,o,null,a,i,l,s,u),p--,h--}if(c>p){if(c<=h){const e=h+1,n=e ((e,t,n)=>{const{items:o,lastVisitedIndex:r}=t;return(r>0?o[r].offset:0)>=n?Ez(e,t,0,r,n):Dz(e,t,Math.max(0,r),n)})(e,n,t),getStopIndexForStartIndex:(e,t,n,o)=>{const{height:r,total:a,layout:i,width:l}=e,s=kz(i)?l:r,u=Az(e,t,o),c=n+s;let d=u.offset+u.size,p=t;for(;p {const o=NH(e,t,n,"column");return[o.size,o.offset]},getRowPosition:(e,t,n)=>{const o=NH(e,t,n,"row");return[o.size,o.offset]},getColumnOffset:(e,t,n,o,r,a)=>KH(e,t,n,o,r,"column",a),getRowOffset:(e,t,n,o,r,a)=>KH(e,t,n,o,r,"row",a),getColumnStartIndexForOffset:(e,t,n)=>FH(e,n,t,"column"),getColumnStopIndexForStartIndex:(e,t,n,o)=>{const r=NH(e,t,o,"column"),a=n+e.width;let i=r.offset+r.size,l=t;for(;l>1,e[n[l]]0&&(t[o]=n[a-1]),n[a]=o)}}a=n.length,i=n[a-1];for(;a-- >0;)n[a]=i,i=t[i];return n}(C):n;for(y=k.length-1,c=x-1;c>=0;c--){const e=v+c,n=t[e],p=e+1(n[e]||"").split(", "),r=o(`${$a}Delay`),a=o(`${$a}Duration`),i=ja(r,a),l=o(`${Ma}Delay`),s=o(`${Ma}Duration`),u=ja(l,s);let c=null,d=0,p=0;t===$a?i>0&&(c=$a,d=i,p=a.length):t===Ma?u>0&&(c=Ma,d=u,p=s.length):(d=Math.max(i,u),c=d>0?i>u?$a:Ma:null,p=c?c===$a?a.length:s.length:0);return{type:c,timeout:d,propCount:p,hasTransform:c===$a&&/\b(transform|all)(,|$)/.test(o(`${$a}Property`).toString())}}function ja(e,t){for(;e.length{t.isLeaf||t.setChecked(!1,!1),e(t)}))};e(n)}}else n.checked&&!r[s]&&n.setChecked(!1,!1)}}setCheckedNodes(e,t=!1){const n=this.key,o={};e.forEach((e=>{o[(e||{})[n]]=!0})),this._setCheckedKeys(n,t,o)}setCheckedKeys(e,t=!1){this.defaultCheckedKeys=e;const n=this.key,o={};e.forEach((e=>{o[e]=!0})),this._setCheckedKeys(n,t,o)}setDefaultExpandedKeys(e){e=e||[],this.defaultExpandedKeys=e,e.forEach((e=>{const t=this.getNode(e);t&&t.expand(null,this.autoExpandParent)}))}setChecked(e,t,n){const o=this.getNode(e);o&&o.setChecked(!!t,n)}getCurrentNode(){return this.currentNode}setCurrentNode(e){const t=this.currentNode;t&&(t.isCurrent=!1),this.currentNode=e,this.currentNode.isCurrent=!0}setUserCurrentNode(e,t=!0){const n=e[this.key],o=this.nodesMap[n];this.setCurrentNode(o),t&&this.currentNode.level>1&&this.currentNode.parent.expand(null,!0)}setCurrentNodeKey(e,t=!0){if(this.currentNodeKey=e,null==e)return this.currentNode&&(this.currentNode.isCurrent=!1),void(this.currentNode=null);const n=this.getNode(e);n&&(this.setCurrentNode(n),t&&this.currentNode.level>1&&this.currentNode.parent.expand(null,!0))}}var nW=Eh(Nn({name:"ElTreeNodeContent",props:{node:{type:Object,required:!0},renderContent:Function},setup(e){const t=hl("tree"),n=jo("NodeInstance"),o=jo("RootTree");return()=>{const r=e.node,{data:a,store:i}=r;return e.renderContent?e.renderContent(ma,{_self:n,node:r,data:a,store:i}):go(o.ctx.slots,"default",{node:r,data:a},(()=>[ma("span",{class:t.be("node","label")},[r.label])]))}}}),[["__file","tree-node-content.vue"]]);function oW(e){const t=jo("TreeNodeMap",null),n={treeNodeExpand:t=>{e.node!==t&&e.node.collapse()},children:[]};return t&&t.children.push(n),Vo("TreeNodeMap",n),{broadcastExpanded:t=>{if(e.accordion)for(const e of n.children)e.treeNodeExpand(t)}}}const rW=Symbol("dragEvents");const aW=Nn({name:"ElTreeNode",components:{ElCollapseTransition:NT,ElCheckbox:LI,NodeContent:nW,ElIcon:nf,Loading:Db},props:{node:{type:eW,default:()=>({})},props:{type:Object,default:()=>({})},accordion:Boolean,renderContent:Function,renderAfterExpand:Boolean,showCheckbox:{type:Boolean,default:!1}},emits:["node-expand"],setup(e,t){const n=hl("tree"),{broadcastExpanded:o}=oW(e),r=jo("RootTree"),a=kt(!1),i=kt(!1),l=kt(),s=kt(),u=kt(),c=jo(rW),d=oa();Vo("NodeInstance",d),e.node.expanded&&(a.value=!0,i.value=!0);const p=r.props.props.children||"children";fr((()=>{var t;const n=null==(t=e.node.data)?void 0:t[p];return n&&[...n]}),(()=>{e.node.updateChildren()})),fr((()=>e.node.indeterminate),(t=>{f(e.node.checked,t)})),fr((()=>e.node.checked),(t=>{f(t,e.node.indeterminate)})),fr((()=>e.node.childNodes.length),(()=>e.node.reInitChecked())),fr((()=>e.node.expanded),(e=>{Jt((()=>a.value=e)),e&&(i.value=!0)}));const h=e=>Uj(r.props.nodeKey,e.data),f=(t,n)=>{l.value===t&&s.value===n||r.ctx.emit("check-change",e.node.data,t,n),l.value=t,s.value=n},m=()=>{e.node.isLeaf||(a.value?(r.ctx.emit("node-collapse",e.node.data,e.node,d),e.node.collapse()):e.node.expand((()=>{t.emit("node-expand",e.node.data,e.node,d)})))},y=t=>{e.node.setChecked(t,!(null==r?void 0:r.props.checkStrictly)),Jt((()=>{const t=r.store.value;r.ctx.emit("check",e.node.data,{checkedNodes:t.getCheckedNodes(),checkedKeys:t.getCheckedKeys(),halfCheckedNodes:t.getHalfCheckedNodes(),halfCheckedKeys:t.getHalfCheckedKeys()})}))};return{ns:n,node$:u,tree:r,expanded:a,childNodeRendered:i,oldChecked:l,oldIndeterminate:s,getNodeKey:h,getNodeClass:t=>{const n=e.props.class;if(!n)return{};let o;if(v(n)){const{data:e}=t;o=n(e,t)}else o=n;return g(o)?{[o]:!0}:o},handleSelectChange:f,handleClick:t=>{Yj(r.store,r.ctx.emit,(()=>{var t;if(null==(t=null==r?void 0:r.props)?void 0:t.nodeKey){const t=h(e.node);r.store.value.setCurrentNodeKey(t)}else r.store.value.setCurrentNode(e.node)})),r.currentNode.value=e.node,r.props.expandOnClickNode&&m(),(r.props.checkOnClickNode||e.node.isLeaf&&r.props.checkOnClickLeaf)&&!e.node.disabled&&y(!e.node.checked),r.ctx.emit("node-click",e.node.data,e.node,d,t)},handleContextMenu:t=>{var n;(null==(n=r.instance.vnode.props)?void 0:n.onNodeContextmenu)&&(t.stopPropagation(),t.preventDefault()),r.ctx.emit("node-contextmenu",t,e.node.data,e.node,d)},handleExpandIconClick:m,handleCheckChange:y,handleChildNodeExpand:(e,t,n)=>{o(t),r.ctx.emit("node-expand",e,t,n)},handleDragStart:t=>{r.props.draggable&&c.treeNodeDragStart({event:t,treeNode:e})},handleDragOver:t=>{t.preventDefault(),r.props.draggable&&c.treeNodeDragOver({event:t,treeNode:{$el:u.value,node:e.node}})},handleDrop:e=>{e.preventDefault()},handleDragEnd:e=>{r.props.draggable&&c.treeNodeDragEnd(e)},CaretRight:mv}}});const iW=Nn({name:"ElTree",components:{ElTreeNode:Eh(aW,[["render",function(e,t,n,o,r,a){const i=lo("el-icon"),l=lo("el-checkbox"),s=lo("loading"),u=lo("node-content"),c=lo("el-tree-node"),d=lo("el-collapse-transition");return dn((Dr(),zr("div",{ref:"node$",class:j([e.ns.b("node"),e.ns.is("expanded",e.expanded),e.ns.is("current",e.node.isCurrent),e.ns.is("hidden",!e.node.visible),e.ns.is("focusable",!e.node.disabled),e.ns.is("checked",!e.node.disabled&&e.node.checked),e.getNodeClass(e.node)]),role:"treeitem",tabindex:"-1","aria-expanded":e.expanded,"aria-disabled":e.node.disabled,"aria-checked":e.node.checked,draggable:e.tree.props.draggable,"data-key":e.getNodeKey(e.node),onClick:Li(e.handleClick,["stop"]),onContextmenu:e.handleContextMenu,onDragstart:Li(e.handleDragStart,["stop"]),onDragover:Li(e.handleDragOver,["stop"]),onDragend:Li(e.handleDragEnd,["stop"]),onDrop:Li(e.handleDrop,["stop"])},[jr("div",{class:j(e.ns.be("node","content")),style:B({paddingLeft:(e.node.level-1)*e.tree.props.indent+"px"})},[e.tree.props.icon||e.CaretRight?(Dr(),Br(i,{key:0,class:j([e.ns.be("node","expand-icon"),e.ns.is("leaf",e.node.isLeaf),{expanded:!e.node.isLeaf&&e.expanded}]),onClick:Li(e.handleExpandIconClick,["stop"])},{default:cn((()=>[(Dr(),Br(uo(e.tree.props.icon||e.CaretRight)))])),_:1},8,["class","onClick"])):Ur("v-if",!0),e.showCheckbox?(Dr(),Br(l,{key:1,"model-value":e.node.checked,indeterminate:e.node.indeterminate,disabled:!!e.node.disabled,onClick:Li((()=>{}),["stop"]),onChange:e.handleCheckChange},null,8,["model-value","indeterminate","disabled","onClick","onChange"])):Ur("v-if",!0),e.node.loading?(Dr(),Br(i,{key:2,class:j([e.ns.be("node","loading-icon"),e.ns.is("loading")])},{default:cn((()=>[Wr(s)])),_:1},8,["class"])):Ur("v-if",!0),Wr(u,{node:e.node,"render-content":e.renderContent},null,8,["node","render-content"])],6),Wr(d,null,{default:cn((()=>[!e.renderAfterExpand||e.childNodeRendered?dn((Dr(),zr("div",{key:0,class:j(e.ns.be("node","children")),role:"group","aria-expanded":e.expanded},[(Dr(!0),zr(Mr,null,fo(e.node.childNodes,(t=>(Dr(),Br(c,{key:e.getNodeKey(t),"render-content":e.renderContent,"render-after-expand":e.renderAfterExpand,"show-checkbox":e.showCheckbox,node:t,accordion:e.accordion,props:e.props,onNodeExpand:e.handleChildNodeExpand},null,8,["render-content","render-after-expand","show-checkbox","node","accordion","props","onNodeExpand"])))),128))],10,["aria-expanded"])),[[Ua,e.expanded]]):Ur("v-if",!0)])),_:1})],42,["aria-expanded","aria-disabled","aria-checked","draggable","data-key","onClick","onContextmenu","onDragstart","onDragover","onDragend","onDrop"])),[[Ua,e.node.visible]])}],["__file","tree-node.vue"]])},props:{data:{type:Array,default:()=>[]},emptyText:{type:String},renderAfterExpand:{type:Boolean,default:!0},nodeKey:String,checkStrictly:Boolean,defaultExpandAll:Boolean,expandOnClickNode:{type:Boolean,default:!0},checkOnClickNode:Boolean,checkOnClickLeaf:{type:Boolean,default:!0},checkDescendants:{type:Boolean,default:!1},autoExpandParent:{type:Boolean,default:!0},defaultCheckedKeys:Array,defaultExpandedKeys:Array,currentNodeKey:[String,Number],renderContent:Function,showCheckbox:{type:Boolean,default:!1},draggable:{type:Boolean,default:!1},allowDrag:Function,allowDrop:Function,props:{type:Object,default:()=>({children:"children",label:"label",disabled:"disabled"})},lazy:{type:Boolean,default:!1},highlightCurrent:Boolean,load:Function,filterNodeMethod:Function,accordion:Boolean,indent:{type:Number,default:18},icon:{type:iC}},emits:["check-change","current-change","node-click","node-contextmenu","node-collapse","node-expand","check","node-drag-start","node-drag-end","node-drop","node-drag-leave","node-drag-enter","node-drag-over"],setup(e,t){const{t:n}=lh(),o=hl("tree"),r=jo(EL,null),a=kt(new tW({key:e.nodeKey,data:e.data,lazy:e.lazy,props:e.props,load:e.load,currentNodeKey:e.currentNodeKey,checkStrictly:e.checkStrictly,checkDescendants:e.checkDescendants,defaultCheckedKeys:e.defaultCheckedKeys,defaultExpandedKeys:e.defaultExpandedKeys,autoExpandParent:e.autoExpandParent,defaultExpandAll:e.defaultExpandAll,filterNodeMethod:e.filterNodeMethod}));a.value.initialize();const i=kt(a.value.root),l=kt(null),s=kt(null),u=kt(null),{broadcastExpanded:c}=oW(e),{dragState:d}=function({props:e,ctx:t,el$:n,dropIndicator$:o,store:r}){const a=hl("tree"),i=kt({showDropIndicator:!1,draggingNode:null,dropNode:null,allowDrop:!0,dropType:null});return Vo(rW,{treeNodeDragStart:({event:n,treeNode:o})=>{if(v(e.allowDrag)&&!e.allowDrag(o.node))return n.preventDefault(),!1;n.dataTransfer.effectAllowed="move";try{n.dataTransfer.setData("text/plain","")}catch(HO){}i.value.draggingNode=o,t.emit("node-drag-start",o.node,n)},treeNodeDragOver:({event:r,treeNode:l})=>{const s=l,u=i.value.dropNode;u&&u.node.id!==s.node.id&&Bh(u.$el,a.is("drop-inner"));const c=i.value.draggingNode;if(!c||!s)return;let d=!0,p=!0,h=!0,f=!0;v(e.allowDrop)&&(d=e.allowDrop(c.node,s.node,"prev"),f=p=e.allowDrop(c.node,s.node,"inner"),h=e.allowDrop(c.node,s.node,"next")),r.dataTransfer.dropEffect=p||d||h?"move":"none",(d||p||h)&&(null==u?void 0:u.node.id)!==s.node.id&&(u&&t.emit("node-drag-leave",c.node,u.node,r),t.emit("node-drag-enter",c.node,s.node,r)),i.value.dropNode=d||p||h?s:null,s.node.nextSibling===c.node&&(h=!1),s.node.previousSibling===c.node&&(d=!1),s.node.contains(c.node,!1)&&(p=!1),(c.node===s.node||c.node.contains(s.node))&&(d=!1,p=!1,h=!1);const g=s.$el.querySelector(`.${a.be("node","content")}`).getBoundingClientRect(),m=n.value.getBoundingClientRect();let y;const b=d?p?.25:h?.45:1:-1,x=h?p?.75:d?.55:0:1;let w=-9999;const S=r.clientY-g.top;y=Si.message:null);return u.appContext=qd(t)?GG._context:t,u.props.onDestroy=()=>{Fi(null,s)},Fi(u,s),WG[n].push({vm:u}),l.appendChild(s.firstElementChild),{close:()=>{u.component.exposed.visible.value=!1}}};FG.forEach((e=>{GG[e]=(t={},n)=>((g(t)||Nr(t))&&(t={message:t}),GG({...t,type:e},n))})),GG.closeAll=function(){for(const e of Object.values(WG))e.forEach((({vm:e})=>{e.component.exposed.visible.value=!1}))},GG._context=null;const XG=Qh(GG,"$notify");var UG=((e=[])=>({version:"2.9.7",install:(t,n)=>{t[ll]||(t[ll]=!0,e.forEach((e=>t.use(e))),n&&_h(n,t,!0))}}))([...JK,...[lG,mG,IG,HG,XG,yR]]),YG={name:"zh-cn",el:{breadcrumb:{label:"面包屑"},colorpicker:{confirm:"确定",clear:"清空",defaultLabel:"颜色选择器",description:"当前颜色 {color},按 Enter 键选择新颜色",alphaLabel:"选择透明度的值"},datepicker:{now:"此刻",today:"今天",cancel:"取消",clear:"清空",confirm:"确定",dateTablePrompt:"使用方向键与 Enter 键可选择日期",monthTablePrompt:"使用方向键与 Enter 键可选择月份",yearTablePrompt:"使用方向键与 Enter 键可选择年份",selectedDate:"已选日期",selectDate:"选择日期",selectTime:"选择时间",startDate:"开始日期",startTime:"开始时间",endDate:"结束日期",endTime:"结束时间",prevYear:"前一年",nextYear:"后一年",prevMonth:"上个月",nextMonth:"下个月",year:"年",month1:"1 月",month2:"2 月",month3:"3 月",month4:"4 月",month5:"5 月",month6:"6 月",month7:"7 月",month8:"8 月",month9:"9 月",month10:"10 月",month11:"11 月",month12:"12 月",weeks:{sun:"日",mon:"一",tue:"二",wed:"三",thu:"四",fri:"五",sat:"六"},weeksFull:{sun:"星期日",mon:"星期一",tue:"星期二",wed:"星期三",thu:"星期四",fri:"星期五",sat:"星期六"},months:{jan:"一月",feb:"二月",mar:"三月",apr:"四月",may:"五月",jun:"六月",jul:"七月",aug:"八月",sep:"九月",oct:"十月",nov:"十一月",dec:"十二月"}},inputNumber:{decrease:"减少数值",increase:"增加数值"},select:{loading:"加载中",noMatch:"无匹配数据",noData:"无数据",placeholder:"请选择"},dropdown:{toggleDropdown:"切换下拉选项"},mention:{loading:"加载中"},cascader:{noMatch:"无匹配数据",loading:"加载中",placeholder:"请选择",noData:"暂无数据"},pagination:{goto:"前往",pagesize:"条/页",total:"共 {total} 条",pageClassifier:"页",page:"页",prev:"上一页",next:"下一页",currentPage:"第 {pager} 页",prevPages:"向前 {pager} 页",nextPages:"向后 {pager} 页",deprecationWarning:"你使用了一些已被废弃的用法,请参考 el-pagination 的官方文档"},dialog:{close:"关闭此对话框"},drawer:{close:"关闭此对话框"},messagebox:{title:"提示",confirm:"确定",cancel:"取消",error:"输入的数据不合法!",close:"关闭此对话框"},upload:{deleteTip:"按 delete 键可删除",delete:"删除",preview:"查看图片",continue:"继续上传"},slider:{defaultLabel:"滑块介于 {min} 至 {max}",defaultRangeStartLabel:"选择起始值",defaultRangeEndLabel:"选择结束值"},table:{emptyText:"暂无数据",confirmFilter:"筛选",resetFilter:"重置",clearFilter:"全部",sumText:"合计"},tour:{next:"下一步",previous:"上一步",finish:"结束导览"},tree:{emptyText:"暂无数据"},transfer:{noMatch:"无匹配数据",noData:"无数据",titles:["列表 1","列表 2"],filterPlaceholder:"请输入搜索内容",noCheckedFormat:"共 {total} 项",hasCheckedFormat:"已选 {checked}/{total} 项"},image:{error:"加载失败"},pageHeader:{title:"返回"},popconfirm:{confirmButtonText:"确定",cancelButtonText:"取消"},carousel:{leftArrow:"上一张幻灯片",rightArrow:"下一张幻灯片",indicator:"幻灯片切换至索引 {index}"}}};
+/*!
+ * vue-router v4.5.0
+ * (c) 2024 Eduardo San Martin Morote
+ * @license MIT
+ */
+const qG="undefined"!=typeof document;function ZG(e){return"object"==typeof e||"displayName"in e||"props"in e||"__vccOpts"in e}const QG=Object.assign;function JG(e,t){const n={};for(const o in t){const r=t[o];n[o]=tX(r)?r.map(e):e(r)}return n}const eX=()=>{},tX=Array.isArray,nX=/#/g,oX=/&/g,rX=/\//g,aX=/=/g,iX=/\?/g,lX=/\+/g,sX=/%5B/g,uX=/%5D/g,cX=/%5E/g,dX=/%60/g,pX=/%7B/g,hX=/%7C/g,fX=/%7D/g,vX=/%20/g;function gX(e){return encodeURI(""+e).replace(hX,"|").replace(sX,"[").replace(uX,"]")}function mX(e){return gX(e).replace(lX,"%2B").replace(vX,"+").replace(nX,"%23").replace(oX,"%26").replace(dX,"`").replace(pX,"{").replace(fX,"}").replace(cX,"^")}function yX(e){return null==e?"":function(e){return gX(e).replace(nX,"%23").replace(iX,"%3F")}(e).replace(rX,"%2F")}function bX(e){try{return decodeURIComponent(""+e)}catch(t){}return""+e}const xX=/\/$/;function wX(e,t,n="/"){let o,r={},a="",i="";const l=t.indexOf("#");let s=t.indexOf("?");return l=0&&(s=-1),s>-1&&(o=t.slice(0,s),a=t.slice(s+1,l>-1?l:t.length),r=e(a)),l>-1&&(o=o||t.slice(0,l),i=t.slice(l,t.length)),o=function(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),o=e.split("/"),r=o[o.length-1];".."!==r&&"."!==r||o.push("");let a,i,l=n.length-1;for(a=0;a