commit c882f0452942dfe83a5fcd5595f6234636f64b42 Author: likun <906102152@qq.com> Date: Fri Apr 18 17:17:23 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/admin/apps/game/boot.go b/admin/apps/game/boot.go new file mode 100644 index 0000000..1a92228 --- /dev/null +++ b/admin/apps/game/boot.go @@ -0,0 +1,26 @@ +package game + +import ( + "admin/apps/game/server" + "admin/apps/game/service" + "admin/internal/global" + "admin/lib/node" +) + +func initFun(app *node.Application) error { + svc := service.NewCmdServerSvc(global.GLOB_DB) // 初始化应用服务 + srv := server.New(svc) // 初始化http服务 + srv.Route(global.GLOB_API_ENGINE) // 初始化http服务路由 + return nil +} + +func New() *node.ApplicationDescInfo { + app := node.NewApplicationDescInfo("user", initFun) + return app +} + +func must(err error) { + if err != nil { + panic(err) + } +} diff --git a/admin/apps/game/domain/entity/project.go b/admin/apps/game/domain/entity/project.go new file mode 100644 index 0000000..cef1ad7 --- /dev/null +++ b/admin/apps/game/domain/entity/project.go @@ -0,0 +1,94 @@ +package entity + +import ( + "admin/apps/game/model" + "admin/apps/game/model/dto" + "reflect" +) + +var ProjectDtoFieldsDescInfo = DefaultProject().GetDtoFieldsDescInfo() + +type Project struct { + Id int + po *model.Project +} + +func DefaultProject() *Project { + return &Project{ + po: &model.Project{}, + } +} + +func FromProjectPo(po *model.Project) *Project { + return &Project{ + Id: po.ID, + po: po, + } +} + +func FromProjectDto(dto *dto.CommonDtoValues) *Project { + et := DefaultProject() + po := et.po + + //to := reflect.TypeOf(po) + vo := reflect.ValueOf(po) + + for _, f := range dto.Values { + fo := vo.FieldByName(f.FieldName) + fo.Set(reflect.ValueOf(f.Value)) + } + + return et +} + +func (et *Project) ToPo() *model.Project { + return et.po +} + +func (et *Project) ToCommonDto() *dto.CommonDtoValues { + obj := &dto.CommonDtoValues{} + + to := reflect.TypeOf(et.po).Elem() + vo := reflect.ValueOf(et.po).Elem() + for i := 0; i < vo.NumField(); i++ { + ft := to.Field(i) + fo := vo.Field(i) + + f1 := &dto.CommonDtoValue{ + FieldName: ft.Name, + Value: fo.Interface(), + } + + obj.Values = append(obj.Values, f1) + } + + return obj +} + +func (et *Project) GetDtoFieldsDescInfo() []*dto.CommonDtoFieldDesc { + + to := reflect.TypeOf(et.po).Elem() + vo := reflect.ValueOf(et.po).Elem() + + obj := make([]*dto.CommonDtoFieldDesc, 0, to.NumField()) + + for i := 0; i < vo.NumField(); i++ { + ft := to.Field(i) + //fo := vo.Field(i) + + f1 := &dto.CommonDtoFieldDesc{ + Name: ft.Name, + Key: ft.Name, + Type: ft.Type.Name(), + HelpText: ft.Tag.Get("desc"), + Editable: true, + Require: true, + Choices: make([]*dto.CommonDtoFieldChoice, 0), + MultiChoice: false, + } + + obj = append(obj, f1) + } + + return obj +} diff --git a/admin/apps/game/domain/entity/server.go b/admin/apps/game/domain/entity/server.go new file mode 100644 index 0000000..9a817a1 --- /dev/null +++ b/admin/apps/game/domain/entity/server.go @@ -0,0 +1,94 @@ +package entity + +import ( + "admin/apps/game/model" + "admin/apps/game/model/dto" + "reflect" +) + +var ServerDtoFieldsDescInfo = DefaultServer().GetDtoFieldsDescInfo() + +type Server struct { + Id int + po *model.Server +} + +func DefaultServer() *Server { + return &Server{ + po: &model.Server{}, + } +} + +func FromServerPo(po *model.Server) *Server { + return &Server{ + Id: po.ID, + po: po, + } +} + +func FromServerDto(dto *dto.CommonDtoValues) *Server { + et := DefaultServer() + po := et.po + + //to := reflect.TypeOf(po) + vo := reflect.ValueOf(po) + + for _, f := range dto.Values { + fo := vo.FieldByName(f.FieldName) + fo.Set(reflect.ValueOf(f.Value)) + } + + return et +} + +func (et *Server) ToPo() *model.Server { + return et.po +} + +func (et *Server) ToCommonDto() *dto.CommonDtoValues { + obj := &dto.CommonDtoValues{} + + to := reflect.TypeOf(et.po).Elem() + vo := reflect.ValueOf(et.po).Elem() + for i := 0; i < vo.NumField(); i++ { + ft := to.Field(i) + fo := vo.Field(i) + + f1 := &dto.CommonDtoValue{ + FieldName: ft.Name, + Value: fo.Interface(), + } + + obj.Values = append(obj.Values, f1) + } + + return obj +} + +func (et *Server) GetDtoFieldsDescInfo() []*dto.CommonDtoFieldDesc { + + to := reflect.TypeOf(et.po).Elem() + vo := reflect.ValueOf(et.po).Elem() + + obj := make([]*dto.CommonDtoFieldDesc, 0, to.NumField()) + + for i := 0; i < vo.NumField(); i++ { + ft := to.Field(i) + //fo := vo.Field(i) + + f1 := &dto.CommonDtoFieldDesc{ + Name: ft.Name, + Key: ft.Name, + Type: ft.Type.Name(), + HelpText: ft.Tag.Get("desc"), + Editable: true, + Require: true, + Choices: make([]*dto.CommonDtoFieldChoice, 0), + MultiChoice: false, + } + + obj = append(obj, f1) + } + + return obj +} diff --git a/admin/apps/game/domain/irestfull.go b/admin/apps/game/domain/irestfull.go new file mode 100644 index 0000000..c35e957 --- /dev/null +++ b/admin/apps/game/domain/irestfull.go @@ -0,0 +1,31 @@ +package domain + +import ( + "admin/apps/game/model/dto" + "admin/internal/errcode" +) + +type IRestfulEntity interface { + ToCommonDto() *dto.CommonDtoValues +} + +type IRestfulResourceSvc interface { + List(pageNo, pageLen int) ([]*dto.CommonDtoFieldDesc, []IRestfulEntity, error) + Post(obj *dto.CommonDtoValues) (IRestfulEntity, error) + Put(obj *dto.CommonDtoValues) (IRestfulEntity, error) + Delete(id int) error +} + +var restfulResourceSvcMgr = make(map[string]IRestfulResourceSvc) + +func registerRestfulSvc(name string, svc IRestfulResourceSvc) { + restfulResourceSvcMgr[name] = svc +} + +func FindRestfulResourceSvc(name string) (IRestfulResourceSvc, error) { + svc, find := restfulResourceSvcMgr[name] + if !find { + return nil, errcode.New(errcode.ServerError, "not found %v restful svc", name) + } + return svc, nil +} diff --git a/admin/apps/game/domain/project.go b/admin/apps/game/domain/project.go new file mode 100644 index 0000000..0cc64bf --- /dev/null +++ b/admin/apps/game/domain/project.go @@ -0,0 +1,48 @@ +package domain + +import ( + "admin/apps/game/domain/entity" + "admin/apps/game/domain/repo" + "admin/apps/game/model/dto" + "gorm.io/gorm" +) + +type ProjectSvc struct { + proRepo repo.IProjectRepo +} + +func NewProjectSvc(db *gorm.DB) *ProjectSvc { + svc := &ProjectSvc{ + proRepo: repo.NewProjectRepo(db), + } + registerRestfulSvc("project", svc) + return svc +} + +func (svc *ProjectSvc) List(pageNo, pageLen int) ([]*dto.CommonDtoFieldDesc, []IRestfulEntity, error) { + entityList, err := svc.proRepo.List(pageNo, pageLen) + if err != nil { + return nil, nil, err + } + iList := make([]IRestfulEntity, 0, len(entityList)) + for _, v := range entityList { + iList = append(iList, v) + } + return entity.ProjectDtoFieldsDescInfo, iList, nil +} + +func (svc *ProjectSvc) Post(obj *dto.CommonDtoValues) (IRestfulEntity, error) { + et := entity.FromProjectDto(obj) + err := svc.proRepo.Create(et) + return et, err +} + +func (svc *ProjectSvc) Put(obj *dto.CommonDtoValues) (IRestfulEntity, error) { + et := entity.FromProjectDto(obj) + err := svc.proRepo.Edit(et) + return et, err +} + +func (svc *ProjectSvc) Delete(id int) error { + return svc.proRepo.Delete(id) +} diff --git a/admin/apps/game/domain/repo/project.go b/admin/apps/game/domain/repo/project.go new file mode 100644 index 0000000..361a20c --- /dev/null +++ b/admin/apps/game/domain/repo/project.go @@ -0,0 +1,77 @@ +package repo + +import ( + "admin/apps/game/domain/entity" + "admin/apps/game/model" + "admin/internal/errcode" + "gorm.io/gorm" +) + +type IProjectRepo interface { + List(pageNo, pageLen int) ([]*entity.Project, error) + Create(et *entity.Project) error + Edit(et *entity.Project) error + Delete(id int) error +} + +func NewProjectRepo(db *gorm.DB) IProjectRepo { + return newProjectRepoImpl(db) +} + +type projectRepoImpl struct { + db *gorm.DB +} + +func newProjectRepoImpl(db *gorm.DB) *projectRepoImpl { + return &projectRepoImpl{db: db} +} + +func (repo *projectRepoImpl) List(pageNo, pageLen int) ([]*entity.Project, error) { + list := make([]*model.Project, 0) + err := repo.db.Find(&list).Error + if err != nil { + return nil, errcode.New(errcode.DBError, "find project error:%v", err) + } + + // debug + list = append(list, &model.Project{ + ID: 123, + Name: "神魔大陆", + Desc: "神魔大陆服务器", + ApiAddr: "http://192.168.1.1:8081", + }) + + entityList := make([]*entity.Project, 0, len(list)) + for _, Project := range list { + entityList = append(entityList, entity.FromProjectPo(Project)) + } + + return entityList, nil +} + +func (repo *projectRepoImpl) Create(et *entity.Project) error { + po := et.ToPo() + err := repo.db.Create(po).Error + if err != nil { + return errcode.New(errcode.DBError, "create obj:%+v error:%v", et, err) + } + et.Id = po.ID + return nil +} + +func (repo *projectRepoImpl) Edit(et *entity.Project) error { + po := et.ToPo() + err := repo.db.Where("id=?", et.Id).Updates(po).Error + if err != nil { + return errcode.New(errcode.DBError, "edit obj:%+v error:%v", et, err) + } + return nil +} + +func (repo *projectRepoImpl) Delete(id int) error { + err := repo.db.Where("id=?", id).Unscoped().Delete(&model.Project{}).Error + if err != nil { + return errcode.New(errcode.DBError, "delete obj:%+v error:%v", id, err) + } + return nil +} diff --git a/admin/apps/game/domain/repo/server.go b/admin/apps/game/domain/repo/server.go new file mode 100644 index 0000000..38327e7 --- /dev/null +++ b/admin/apps/game/domain/repo/server.go @@ -0,0 +1,69 @@ +package repo + +import ( + "admin/apps/game/domain/entity" + "admin/apps/game/model" + "admin/internal/errcode" + "gorm.io/gorm" +) + +type IServerRepo interface { + List(pageNo, pageLen int) ([]*entity.Server, error) + Create(et *entity.Server) error + Edit(et *entity.Server) error + Delete(id int) error +} + +func NewServerRepo(db *gorm.DB) IServerRepo { + return newServerRepoImpl(db) +} + +type ServerRepoImpl struct { + db *gorm.DB +} + +func newServerRepoImpl(db *gorm.DB) *ServerRepoImpl { + return &ServerRepoImpl{db: db} +} + +func (repo *ServerRepoImpl) List(pageNo, pageLen int) ([]*entity.Server, error) { + list := make([]*model.Server, 0) + err := repo.db.Find(&list).Error + if err != nil { + return nil, errcode.New(errcode.DBError, "find Server error:%v", err) + } + + entityList := make([]*entity.Server, 0, len(list)) + for _, Server := range list { + entityList = append(entityList, entity.FromServerPo(Server)) + } + + return entityList, nil +} + +func (repo *ServerRepoImpl) Create(et *entity.Server) error { + po := et.ToPo() + err := repo.db.Create(po).Error + if err != nil { + return errcode.New(errcode.DBError, "create obj:%+v error:%v", et, err) + } + et.Id = po.ID + return nil +} + +func (repo *ServerRepoImpl) Edit(et *entity.Server) error { + po := et.ToPo() + err := repo.db.Where("id=?", et.Id).Updates(po).Error + if err != nil { + return errcode.New(errcode.DBError, "edit obj:%+v error:%v", et, err) + } + return nil +} + +func (repo *ServerRepoImpl) Delete(id int) error { + err := repo.db.Where("id=?", id).Unscoped().Delete(&model.Server{}).Error + if err != nil { + return errcode.New(errcode.DBError, "delete obj:%+v error:%v", id, err) + } + return nil +} diff --git a/admin/apps/game/domain/server.go b/admin/apps/game/domain/server.go new file mode 100644 index 0000000..398c24b --- /dev/null +++ b/admin/apps/game/domain/server.go @@ -0,0 +1,48 @@ +package domain + +import ( + "admin/apps/game/domain/entity" + "admin/apps/game/domain/repo" + "admin/apps/game/model/dto" + "gorm.io/gorm" +) + +type ServerSvc struct { + serverRepo repo.IServerRepo +} + +func NewServerSvc(db *gorm.DB) *ServerSvc { + svc := &ServerSvc{ + serverRepo: repo.NewServerRepo(db), + } + registerRestfulSvc("server", svc) + return svc +} + +func (svc *ServerSvc) List(pageNo, pageLen int) ([]*dto.CommonDtoFieldDesc, []IRestfulEntity, error) { + entityList, err := svc.serverRepo.List(pageNo, pageLen) + if err != nil { + return nil, nil, err + } + iList := make([]IRestfulEntity, 0, len(entityList)) + for _, v := range entityList { + iList = append(iList, v) + } + return entity.ServerDtoFieldsDescInfo, iList, nil +} + +func (svc *ServerSvc) Post(obj *dto.CommonDtoValues) (IRestfulEntity, error) { + et := entity.FromServerDto(obj) + err := svc.serverRepo.Create(et) + return et, err +} + +func (svc *ServerSvc) Put(obj *dto.CommonDtoValues) (IRestfulEntity, error) { + et := entity.FromServerDto(obj) + err := svc.serverRepo.Edit(et) + return et, err +} + +func (svc *ServerSvc) Delete(id int) error { + return svc.serverRepo.Delete(id) +} diff --git a/admin/apps/game/model/dto/common.go b/admin/apps/game/model/dto/common.go new file mode 100644 index 0000000..5acad08 --- /dev/null +++ b/admin/apps/game/model/dto/common.go @@ -0,0 +1,35 @@ +package dto + +type CommonDtoFieldChoice struct { + Desc string `json:"desc"` + Value any `json:"value"` + // 描述选项的类型,例如添加物品时,可以添加道具、翅膀、宠物等,他们可能不一定都设计为道具 + Type int `json:"type"` +} + +type CommonDtoFieldDesc struct { + Name string `json:"name"` + Key string `json:"key"` + // 字段类型,基础类型支持int float string bool []<基础类行>, + // 支持自定义类型和自定义类型的数组 + Type string `json:"type"` + HelpText string `json:"help_text"` + Editable bool `json:"editable"` // 是否可编辑,例如id就不可编辑,新增时也不需要填写 + Require bool `json:"require"` // 是否必填,不能为空 + Choices []*CommonDtoFieldChoice `json:"choices"` // 可选项,用于字段做下拉框 + MultiChoice bool `json:"multi_choice"` // 是否多选 +} + +type CommonDtoValue struct { + FieldName string `json:"field_name"` + Value any `json:"value"` +} + +type CommonDtoValues struct { + Values []*CommonDtoValue `json:"values"` +} + +type CommonDtoList struct { + FieldsDesc []*CommonDtoFieldDesc `json:"fields_desc"` // 数据字段描述信息 + Rows []*CommonDtoValues `json:"rows"` // 数据行 +} diff --git a/admin/apps/game/model/dto/request.go b/admin/apps/game/model/dto/request.go new file mode 100644 index 0000000..9bf54ab --- /dev/null +++ b/admin/apps/game/model/dto/request.go @@ -0,0 +1,18 @@ +package dto + +type CommonListReq struct { + PageNo int `json:"page_no"` + PageLen int `json:"page_len"` +} + +type CommonPostReq struct { + Dto *CommonDtoValues `json:"dto"` +} + +type CommonPutReq struct { + Dto *CommonDtoValues `json:"dto"` +} + +type CommonDeleteReq struct { + Id int `json:"id"` +} diff --git a/admin/apps/game/model/dto/response.go b/admin/apps/game/model/dto/response.go new file mode 100644 index 0000000..95538ee --- /dev/null +++ b/admin/apps/game/model/dto/response.go @@ -0,0 +1,3 @@ +package dto + +type CommonListRsp = CommonDtoList diff --git a/admin/apps/game/model/project.go b/admin/apps/game/model/project.go new file mode 100644 index 0000000..a9644e5 --- /dev/null +++ b/admin/apps/game/model/project.go @@ -0,0 +1,28 @@ +package model + +import ( + "admin/internal/db" + "gorm.io/gorm" + "time" +) + +func init() { + db.RegisterTableModels(Project{}) +} + +// Project 游戏项目,例如神谕、神魔大陆 +type Project struct { + ID int `gorm:"primarykey"` + Name string `gorm:"primarykey"` + Desc string + // command_list接口服务器地址,为空代表由由项目下各个逻辑服提供command_list. + // 取决于每个项目改造难度: + // 不为空就代表项目要实现一个自己统一对外暴露的gm调用服务对内聚合、分发指令执行,本后台执行指令只调用一次; + // 为空就代表command_list实现在各个逻辑服,由本后台系统在执行指令时聚合、分发指令 + // 调用各个逻辑服执行,本后台执行指令需要根据逻辑服数量调用; + ApiAddr string // + + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} diff --git a/admin/apps/game/model/server.go b/admin/apps/game/model/server.go new file mode 100644 index 0000000..fe876c7 --- /dev/null +++ b/admin/apps/game/model/server.go @@ -0,0 +1,28 @@ +package model + +import ( + "admin/internal/db" + "gorm.io/gorm" + "time" +) + +func init() { + db.RegisterTableModels(Server{}) +} + +// Server 逻辑服 +type Server struct { + ID int `gorm:"primarykey"` + ServerConfID string `gorm:"primarykey"` + Desc string + // command_list接口服务器地址,为空代表由由项目统一提供command_list. + // 取决于每个项目改造难度: + // 为空就代表项目要实现一个自己统一对外暴露的gm调用服务对内聚合、分发指令执行,本后台执行指令只调用一次; + // 不为空就代表command_list实现在各个逻辑服,由本后台系统在执行指令时聚合、分发指令 + // 调用各个逻辑服执行,本后台执行指令需要根据逻辑服数量调用; + ApiAddr string + + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} diff --git a/admin/apps/game/server/ctl.go b/admin/apps/game/server/ctl.go new file mode 100644 index 0000000..9c799bd --- /dev/null +++ b/admin/apps/game/server/ctl.go @@ -0,0 +1,11 @@ +package server + +import "admin/apps/game/service" + +type controller struct { + svc *service.Service +} + +func newController(svc *service.Service) *controller { + return &controller{svc: svc} +} diff --git a/admin/apps/game/server/ctl_common_rest.go b/admin/apps/game/server/ctl_common_rest.go new file mode 100644 index 0000000..24b255e --- /dev/null +++ b/admin/apps/game/server/ctl_common_rest.go @@ -0,0 +1,42 @@ +package server + +import ( + "admin/apps/game/model/dto" + "admin/internal/context" +) + +func (ctl *controller) CommonList(ctx *context.WebContext, restfulResourceName string, params *dto.CommonListReq) { + list, err := ctl.svc.CommonList(ctx, restfulResourceName, params) + if err != nil { + ctx.Fail(err) + return + } + ctx.Ok(list) +} + +func (ctl *controller) CommonPost(ctx *context.WebContext, restfulResourceName string, params *dto.CommonDtoValues) { + newObj, err := ctl.svc.CommonPost(ctx, restfulResourceName, params) + if err != nil { + ctx.Fail(err) + return + } + ctx.Ok(newObj) +} + +func (ctl *controller) CommonPut(ctx *context.WebContext, restfulResourceName string, params *dto.CommonDtoValues) { + newObj, err := ctl.svc.CommonPut(ctx, restfulResourceName, params) + if err != nil { + ctx.Fail(err) + return + } + ctx.Ok(newObj) +} + +func (ctl *controller) CommonDelete(ctx *context.WebContext, restfulResourceName string, id int) { + err := ctl.svc.CommonDelete(ctx, restfulResourceName, id) + if err != nil { + ctx.Fail(err) + return + } + ctx.Ok(nil) +} diff --git a/admin/apps/game/server/route.go b/admin/apps/game/server/route.go new file mode 100644 index 0000000..46f92ee --- /dev/null +++ b/admin/apps/game/server/route.go @@ -0,0 +1,57 @@ +package server + +import ( + "admin/apps/game/model/dto" + "admin/internal/context" + "admin/lib/web" +) + +func (srv *Server) Route(engine *web.Engine) { + apiGroup := engine.Group("/api") + srv.proRoute(apiGroup) + srv.serverRoute(apiGroup) +} + +func (srv *Server) proRoute(engine *web.Group) { + resourceName := "project" + proGroup := engine.Group("/" + resourceName) + + proGroup.Get("", "获取项目列表", web.AccessMode_Read, dto.CommonListReq{}, commonHandlerList(srv.ctl, resourceName)) + proGroup.Post("", "新增项目", web.AccessMode_Read, dto.CommonPostReq{}, commonHandlerList(srv.ctl, resourceName)) + proGroup.Put("", "修改项目", web.AccessMode_Read, dto.CommonPutReq{}, commonHandlerList(srv.ctl, resourceName)) + proGroup.Delete("", "删除项目", web.AccessMode_Read, dto.CommonDeleteReq{}, commonHandlerList(srv.ctl, resourceName)) +} + +func (srv *Server) serverRoute(engine *web.Group) { + resourceName := "server" + proGroup := engine.Group("/" + resourceName) + + proGroup.Get("", "获取服务器列表", web.AccessMode_Read, dto.CommonListReq{}, commonHandlerList(srv.ctl, resourceName)) + proGroup.Post("", "新增服务器", web.AccessMode_Read, dto.CommonPostReq{}, commonHandlerList(srv.ctl, resourceName)) + proGroup.Put("", "修改服务器", web.AccessMode_Read, dto.CommonPutReq{}, commonHandlerList(srv.ctl, resourceName)) + proGroup.Delete("", "删除服务器", web.AccessMode_Read, dto.CommonDeleteReq{}, commonHandlerList(srv.ctl, resourceName)) +} + +func commonHandlerList(ctl *controller, resourceName string) func(ctx *context.WebContext, params *dto.CommonListReq) { + return func(ctx *context.WebContext, params *dto.CommonListReq) { + ctl.CommonList(ctx, resourceName, params) + } +} + +func commonHandlerPost(ctl *controller, resourceName string) func(ctx *context.WebContext, params *dto.CommonPostReq) { + return func(ctx *context.WebContext, params *dto.CommonPostReq) { + ctl.CommonPost(ctx, resourceName, params.Dto) + } +} + +func commonHandlerPut(ctl *controller, resourceName string) func(ctx *context.WebContext, params *dto.CommonPutReq) { + return func(ctx *context.WebContext, params *dto.CommonPutReq) { + ctl.CommonPut(ctx, resourceName, params.Dto) + } +} + +func commonHandlerDelete(ctl *controller, resourceName string) func(ctx *context.WebContext, params *dto.CommonDeleteReq) { + return func(ctx *context.WebContext, params *dto.CommonDeleteReq) { + ctl.CommonDelete(ctx, resourceName, params.Id) + } +} diff --git a/admin/apps/game/server/server.go b/admin/apps/game/server/server.go new file mode 100644 index 0000000..cb99acc --- /dev/null +++ b/admin/apps/game/server/server.go @@ -0,0 +1,15 @@ +package server + +import "admin/apps/game/service" + +type Server struct { + svc *service.Service + ctl *controller +} + +func New(svc *service.Service) *Server { + return &Server{ + svc: svc, + ctl: newController(svc), + } +} diff --git a/admin/apps/game/service/service.go b/admin/apps/game/service/service.go new file mode 100644 index 0000000..acc1cc0 --- /dev/null +++ b/admin/apps/game/service/service.go @@ -0,0 +1,68 @@ +package service + +import ( + "admin/apps/game/domain" + "admin/apps/game/model/dto" + "context" + "gorm.io/gorm" +) + +type Service struct { + db *gorm.DB + projectSvc *domain.ProjectSvc + serverSvc *domain.ServerSvc +} + +func NewCmdServerSvc(db *gorm.DB) *Service { + return &Service{ + db: db, + projectSvc: domain.NewProjectSvc(db), + serverSvc: domain.NewServerSvc(db), + } +} + +func (svc *Service) CommonList(ctx context.Context, resourceName string, params *dto.CommonListReq) (*dto.CommonDtoList, error) { + restfulDomainSvc, err := domain.FindRestfulResourceSvc(resourceName) + if err != nil { + return nil, err + } + dtoFieldsDescInfo, list, err := restfulDomainSvc.List(params.PageNo, params.PageLen) + if err != nil { + return nil, err + } + retList := make([]*dto.CommonDtoValues, 0, len(list)) + for _, v := range list { + retList = append(retList, v.ToCommonDto()) + } + return &dto.CommonDtoList{FieldsDesc: dtoFieldsDescInfo, Rows: retList}, nil +} + +func (svc *Service) CommonPost(ctx context.Context, resourceName string, params *dto.CommonDtoValues) (*dto.CommonDtoValues, error) { + restfulDomainSvc, err := domain.FindRestfulResourceSvc(resourceName) + if err != nil { + return nil, err + } + et, err := restfulDomainSvc.Post(params) + if err != nil { + return nil, err + } + return et.ToCommonDto(), nil +} +func (svc *Service) CommonPut(ctx context.Context, resourceName string, params *dto.CommonDtoValues) (*dto.CommonDtoValues, error) { + restfulDomainSvc, err := domain.FindRestfulResourceSvc(resourceName) + if err != nil { + return nil, err + } + et, err := restfulDomainSvc.Put(params) + if err != nil { + return nil, err + } + return et.ToCommonDto(), nil +} +func (svc *Service) CommonDelete(ctx context.Context, resourceName string, id int) error { + restfulDomainSvc, err := domain.FindRestfulResourceSvc(resourceName) + if err != nil { + return err + } + return restfulDomainSvc.Delete(id) +} diff --git a/admin/apps/user/boot.go b/admin/apps/user/boot.go new file mode 100644 index 0000000..53e3e8d --- /dev/null +++ b/admin/apps/user/boot.go @@ -0,0 +1,18 @@ +package user + +import "admin/lib/node" + +func initFun(app *node.Application) error { + return nil +} + +func New() *node.ApplicationDescInfo { + app := node.NewApplicationDescInfo("user", initFun) + return app +} + +func must(err error) { + if err != nil { + panic(err) + } +} diff --git a/admin/cmd/all_in_one/main.go b/admin/cmd/all_in_one/main.go new file mode 100644 index 0000000..c073490 --- /dev/null +++ b/admin/cmd/all_in_one/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "admin/apps/game" + "admin/internal/mynode" + "admin/lib/node" +) + +var appList []*node.ApplicationDescInfo + +func init() { + appList = []*node.ApplicationDescInfo{ + game.New(), + } +} + +func main() { + nd := mynode.New() + for _, app := range appList { + nd.WithApp(app) + } + err := nd.Run() + if err != nil { + panic(err) + } +} diff --git a/admin/go.mod b/admin/go.mod new file mode 100644 index 0000000..b7c9beb --- /dev/null +++ b/admin/go.mod @@ -0,0 +1,53 @@ +module admin + +go 1.24.2 + +require ( + github.com/gin-contrib/pprof v1.5.3 + github.com/gin-gonic/gin v1.10.0 + github.com/go-sql-driver/mysql v1.7.0 + github.com/prometheus/client_golang v1.22.0 + github.com/rs/zerolog v1.34.0 + gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/mysql v1.5.7 + gorm.io/driver/sqlite v1.5.7 + gorm.io/gorm v1.25.12 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.13.2 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.0.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.28 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.16.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/admin/go.sum b/admin/go.sum new file mode 100644 index 0000000..ee894ae --- /dev/null +++ b/admin/go.sum @@ -0,0 +1,139 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= +github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= +github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/pprof v1.5.3 h1:Bj5SxJ3kQDVez/s/+f9+meedJIqLS+xlkIVDe/lcvgM= +github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+YEYpfQQ6xY= +github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= +github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= +golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/admin/internal/config/flags.go b/admin/internal/config/flags.go new file mode 100644 index 0000000..a8b5796 --- /dev/null +++ b/admin/internal/config/flags.go @@ -0,0 +1,10 @@ +package config + +type CommonBootFlags struct { + ApiPort string `env:"api_port" default:"8080" desc:"api端口,客户端请求的地址端口"` + DBType string `env:"db_type" default:"sqlite3" 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:"数据库密码"` +} diff --git a/admin/internal/config/yaml.go b/admin/internal/config/yaml.go new file mode 100644 index 0000000..d912156 --- /dev/null +++ b/admin/internal/config/yaml.go @@ -0,0 +1 @@ +package config diff --git a/admin/internal/context/ctx_web.go b/admin/internal/context/ctx_web.go new file mode 100644 index 0000000..8ae820b --- /dev/null +++ b/admin/internal/context/ctx_web.go @@ -0,0 +1,43 @@ +package context + +import ( + "admin/internal/errcode" + "admin/lib/web" + "context" +) + +type WebContext struct { + context.Context + rawCtx web.RawContext +} + +func NewWebContext() web.Context { + return &WebContext{} +} + +func (ctx *WebContext) Ok(data any) { + ctx.rawCtx.Json(200, map[string]any{ + "code": 200, + "msg": "", + "data": data, + }) +} + +func (ctx *WebContext) Fail(err error) { + code, stack, msg := errcode.ParseError(err) + ctx.rawCtx.Json(200, map[string]any{ + "code": code, + "stack": stack, + "msg": msg, + "data": "", + }) +} + +func (ctx *WebContext) SetRawContext(rawCtx web.RawContext) { + ctx.Context = context.Background() + ctx.rawCtx = rawCtx +} + +func (ctx *WebContext) GetRawContext() web.RawContext { + return ctx.rawCtx +} diff --git a/admin/internal/db/db.go b/admin/internal/db/db.go new file mode 100644 index 0000000..4426b9b --- /dev/null +++ b/admin/internal/db/db.go @@ -0,0 +1,142 @@ +package db + +import ( + "admin/internal/errcode" + "admin/internal/global" + "admin/lib/xlog" + "fmt" + mysqlDriver "github.com/go-sql-driver/mysql" + "gorm.io/driver/mysql" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "sync" + "time" +) + +var ( + globalTables []any + locker sync.Mutex +) + +func RegisterTableModels(models ...any) { + locker.Lock() + defer locker.Unlock() + globalTables = append(globalTables, models...) +} + +func NewDB(dbType, dbAddr, dbName, dbUser, dbPass string) (db *gorm.DB, err error) { + switch dbType { + case "sqlite": + db, err = gorm.Open(sqlite.Open(dbName+".db"), &gorm.Config{}) + if err != nil { + return nil, err + } + case "mysql": + dsn := fmt.Sprintf("%v:%v@tcp(%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPass, dbAddr, dbName) + dsnWithoutDB := fmt.Sprintf("%v:%v@tcp(%v)/?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPass, dbAddr) + db, err = createDBAndGuaranteeMigrate(dsnWithoutDB, dsn, globalTables) + } + global.GLOB_DB = db + return db, nil +} + +func createDBAndGuaranteeMigrate(dsnWithoutDb, dsn string, tables []any) (*gorm.DB, error) { + mysqlDriverConf, err := mysqlDriver.ParseDSN(dsn) + if err != nil { + return nil, fmt.Errorf("parse dsn:%v error:%v", dsn, err) + } + dbName := mysqlDriverConf.DBName + _, err = tryCreateDB(dbName, dsnWithoutDb) + if err != nil { + xlog.Fatalf(err) + return nil, err + } + + driverConf := mysql.Config{ + DSN: dsn, + DontSupportRenameColumn: true, + //SkipInitializeWithVersion: false, // 根据数据库版本自动配置 + } + dialector := mysql.New(driverConf) + + //slowLogger := logger.New( + // syslog.New(xlog.GetGlobalWriter(), "\n", syslog.LstdFlags), + // logger.Config{ + // // 设定慢查询时间阈值为 默认值:200 * time.Millisecond + // SlowThreshold: 200 * time.Millisecond, + // // 设置日志级别 + // LogLevel: logger.Warn, + // Colorful: true, + // }, + //) + + db, err := gorm.Open(dialector, &gorm.Config{ + Logger: &gormLogger{}, + PrepareStmt: false, // 关闭缓存sql语句功能,因为后续use db会报错,这个缓存会无限存储可能导致内存泄露 + //SkipDefaultTransaction: true, // 跳过默认事务 + }) + if err != nil { + return nil, fmt.Errorf("failed to connect to mysql:%v", err) + } + + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(10) + sqlDB.SetMaxOpenConns(50) + sqlDB.SetConnMaxIdleTime(time.Minute * 5) + sqlDB.SetConnMaxLifetime(time.Minute * 10) + + if len(tables) > 0 { + err = autoMigrate(db, tables...) + if err != nil { + xlog.Fatalf(err) + return nil, fmt.Errorf("automigrate error:%v", err) + } + } + + //addMetricsCollection(db, strconv.Itoa(int(serverId)), dbName) + return db, nil +} + +func tryCreateDB(dbName, dsn string) (string, error) { + driverConf := mysql.Config{ + DSN: dsn, + DontSupportRenameColumn: true, + //SkipInitializeWithVersion: false, // 根据数据库版本自动配置 + } + dialector := mysql.New(driverConf) + + db, err := gorm.Open(dialector, &gorm.Config{ + PrepareStmt: false, // 关闭缓存sql语句功能,因为后续use db会报错,这个缓存会无限存储可能导致内存泄露 + //SkipDefaultTransaction: true, // 跳过默认事务 + }) + if err != nil { + return "", fmt.Errorf("failed to connect to mysql:%v", err) + } + + // 检查数据库是否存在 + var count int + db.Raw("SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = ?", dbName).Scan(&count) + if count == 0 { + // 数据库不存在,创建它 + sql := fmt.Sprintf(`create database if not exists %s default charset utf8mb4 collate utf8mb4_unicode_ci`, + dbName) + if e := db.Exec(sql).Error; e != nil { + return "", fmt.Errorf("failed to create database:%v", e) + } + } + + sqlDb, _ := db.DB() + sqlDb.Close() + + return dbName, nil +} + +func autoMigrate(db *gorm.DB, tables ...interface{}) error { + // 这个函数是在InitConn之后调用的 + + // 初始化表 + if err := db.AutoMigrate(tables...); err != nil { + return errcode.New(errcode.DBError, "failed to init tables", err) + } + return nil +} diff --git a/admin/internal/db/logger.go b/admin/internal/db/logger.go new file mode 100644 index 0000000..5cc77c8 --- /dev/null +++ b/admin/internal/db/logger.go @@ -0,0 +1,99 @@ +package db + +import ( + "admin/lib/xlog" + "context" + "fmt" + "gorm.io/gorm/logger" + "strings" + "time" +) + +type gormLogger struct { +} + +func (l *gormLogger) LogMode(logger.LogLevel) logger.Interface { + return l +} +func (l *gormLogger) Info(ctx context.Context, format string, args ...interface{}) { + xlog.Infof(fmt.Sprintf("[GORM LOGGER] "+format, args...)) +} +func (l *gormLogger) Warn(ctx context.Context, format string, args ...interface{}) { + xlog.Warnf(fmt.Sprintf("[GORM LOGGER] "+format, args...)) +} +func (l *gormLogger) Error(ctx context.Context, format string, args ...interface{}) { + xlog.Errorf(fmt.Sprintf("[GORM LOGGER] "+format, args...)) +} +func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) { + sqlStr, aff := fc() + if strings.Contains(sqlStr, "SHOW STATUS") { + return + } + if err != nil { + xlog.Errorf("[GORM LOGGER] "+"sql:%v affected:%v error:%v", sqlStr, aff, err) + cmd, table := getSqlCmdAndTable(sqlStr) + if cmd == "" || table == "" { + return + } + //metricsMysqlErrorCount.LabelValues(cmd, table).Add(1) + } else { + dura := time.Now().Sub(begin) + if dura.Milliseconds() > 2000 { + xlog.Warnf("[GORM LOGGER] [SLOW] "+"sql:%v affected:%v use %vms", sqlStr, aff, dura.Milliseconds()) + } else { + xlog.Tracef("[GORM LOGGER] "+"sql:%v affected:%v", sqlStr, aff) + } + } +} + +func getSqlCmdAndTable(sql string) (string, string) { + cmd := "" + table := "" + idx := strings.IndexAny(sql, " ") + if idx <= 0 { + cmd = sql + } else { + cmd = sql[:idx] + } + switch cmd { + case "SELECT": + if idx := strings.Index(sql, "FROM"); idx >= 0 { + for i := idx + 5; i < len(sql); i++ { + if sql[i] == ' ' { + break + } + table += string(sql[i]) + } + } + case "UPDATE": + if idx := strings.Index(sql, "UPDATE"); idx >= 0 { + for i := idx + 7; i < len(sql); i++ { + if sql[i] == ' ' { + break + } + table += string(sql[i]) + } + } + case "INSERT": + if idx := strings.Index(sql, "INTO"); idx >= 0 { + for i := idx + 5; i < len(sql); i++ { + if sql[i] == ' ' || sql[i] == '(' { + break + } + table += string(sql[i]) + } + } + case "DELETE": + if idx := strings.Index(sql, "FROM"); idx >= 0 { + for i := idx + 5; i < len(sql); i++ { + if sql[i] == ' ' { + break + } + table += string(sql[i]) + } + } + default: + } + + return cmd, table +} diff --git a/admin/internal/errcode/code.go b/admin/internal/errcode/code.go new file mode 100644 index 0000000..2b8afd2 --- /dev/null +++ b/admin/internal/errcode/code.go @@ -0,0 +1,7 @@ +package errcode + +const ( + Ok = 0 + ServerError = 1 // 服务器错误 + DBError = 2 // 数据库错误 +) diff --git a/admin/internal/errcode/error.go b/admin/internal/errcode/error.go new file mode 100644 index 0000000..7d5a3b0 --- /dev/null +++ b/admin/internal/errcode/error.go @@ -0,0 +1,53 @@ +package errcode + +import ( + "fmt" + "runtime" + "strconv" + "strings" +) + +type errorWithCode struct { + code int + stack string + msg string +} + +func newErrorWithCode(code int, stack, msg string) *errorWithCode { + return &errorWithCode{ + code: code, + stack: stack, + msg: msg, + } +} + +func (e *errorWithCode) Error() string { + return fmt.Sprintf("[stack ==> %v] code:%v msg:%v", e.stack, e.code, e.msg) +} + +func New(code int, format string, args ...interface{}) error { + return newError(code, 2, format, args...) +} + +func ParseError(err error) (int, string, string) { + if specErr, ok := err.(*errorWithCode); ok { + return specErr.code, specErr.stack, specErr.msg + } + return ParseError(newError(ServerError, 3, err.Error())) +} + +func newError(code int, callDeep int, format string, args ...interface{}) error { + _, caller, line, ok := runtime.Caller(callDeep) + if !ok { + panic(ok) + } + tokens := strings.Split(caller, "/") + if len(tokens) > 5 { + tokens = tokens[len(tokens)-5:] + } + fileInfo := strings.Join(tokens, "/") + + err := newErrorWithCode(code, fileInfo+":"+strconv.Itoa(int(line)), fmt.Sprintf(format, args...)) + + return err +} diff --git a/admin/internal/global/var.go b/admin/internal/global/var.go new file mode 100644 index 0000000..98f4790 --- /dev/null +++ b/admin/internal/global/var.go @@ -0,0 +1,13 @@ +package global + +import ( + "admin/internal/config" + "admin/lib/web" + "gorm.io/gorm" +) + +var ( + GLOB_BOOT_FLAGS = &config.CommonBootFlags{} // 启动命令行参数 + GLOB_DB *gorm.DB // 数据库 + GLOB_API_ENGINE *web.Engine // 全局api服务器 +) diff --git a/admin/internal/mynode/node.go b/admin/internal/mynode/node.go new file mode 100644 index 0000000..f239c3e --- /dev/null +++ b/admin/internal/mynode/node.go @@ -0,0 +1,38 @@ +package mynode + +import ( + "admin/internal/context" + "admin/internal/db" + "admin/internal/global" + "admin/lib/node" + "admin/lib/web" + "admin/lib/xlog" +) + +func New() *node.Node { + nd := node.NewNode() + nd.ApplyOptions(node.WithNodeExBootFlags(global.GLOB_BOOT_FLAGS)) + + nd.AddInitTask("初始化全局api监听服务", func() error { + global.GLOB_API_ENGINE = web.NewEngine("gin", context.NewWebContext) + return nil + }) + + nd.AddInitTask("初始化数据库", func() error { + flags := global.GLOB_BOOT_FLAGS + _, err := db.NewDB(flags.DBType, flags.DBAddr, flags.DBName, flags.DBUser, flags.DBPass) + return err + }) + + nd.AddPostTask("启动全局api监听服务", func() error { + go func() { + err := global.GLOB_API_ENGINE.Run(":" + global.GLOB_BOOT_FLAGS.ApiPort) + if err != nil { + xlog.Errorf("start api server on %v error: %v", global.GLOB_BOOT_FLAGS, err) + } + }() + return nil + }) + + return nd +} diff --git a/admin/lib/flags/flags.go b/admin/lib/flags/flags.go new file mode 100644 index 0000000..be110c9 --- /dev/null +++ b/admin/lib/flags/flags.go @@ -0,0 +1,76 @@ +package flags + +import ( + "flag" + "fmt" + "os" + "reflect" + "strconv" + "unsafe" +) + +// ParseWithStructPointers 启动参数解析,如果启动参数没有指定,会去env里查找同名参数 +// flagStructPointers为结构体指针数组 +func ParseWithStructPointers(flagStructPointers ...interface{}) { + + for _, st := range flagStructPointers { + flagParseStruct2Flags(st) + } + + flag.Parse() +} + +func flagParseStruct2Flags(st interface{}) { + if st == nil { + return + } + + var stTo = reflect.TypeOf(st) + var stVo = reflect.ValueOf(st) + switch stTo.Kind() { + case reflect.Ptr: + stTo = stTo.Elem() + stVo = stVo.Elem() + // case reflect.Struct: + // break + default: + panic(fmt.Errorf("invalid flags parse struct(%+v), must be pointer or struct", st)) + } + + for i := 0; i < stTo.NumField(); i++ { + field := stTo.Field(i) + + key, find := field.Tag.Lookup("env") + if !find { + continue + } + + desc := field.Tag.Get("desc") + + defaultValue, find := os.LookupEnv(key) + if !find { + defaultValue, find = field.Tag.Lookup("default") + if !find { + defaultValue = "" + } + } + + var fieldValuePointer = unsafe.Pointer(stVo.Field(i).Addr().Pointer()) + switch field.Type.Kind() { + case reflect.String: + flag.StringVar((*string)(fieldValuePointer), key, defaultValue, desc) + case reflect.Int: + defaultValue1, _ := strconv.Atoi(defaultValue) + flag.IntVar((*int)(fieldValuePointer), key, defaultValue1, desc) + case reflect.Int64: + defaultValue1, _ := strconv.ParseInt(defaultValue, 10, 64) + flag.Int64Var((*int64)(fieldValuePointer), key, defaultValue1, desc) + case reflect.Bool: + flag.BoolVar((*bool)(fieldValuePointer), key, defaultValue == "true", desc) + default: + panic(fmt.Errorf("parse flag kind invalid,must be string/int/int64/bool, not %+v", field.Type.Kind())) + } + } + + return +} diff --git a/admin/lib/node/app.go b/admin/lib/node/app.go new file mode 100644 index 0000000..33d0f54 --- /dev/null +++ b/admin/lib/node/app.go @@ -0,0 +1,216 @@ +package node + +import ( + "admin/lib/xlog" + "fmt" + "github.com/gin-gonic/gin" +) + +// Task 不会永久执行的任务,串行用于启动前初始化或者启动后初始化工作,返回error就停止application +type Task func() error + +// Worker 永久执行的工作协程,一旦停止就停止application +type Worker func() error + +// Job 不会永久执行的任务,且不关心执行结果,不关心执行顺序,例如内存预热等 +type Job func() + +type pair struct { + key any + value any +} + +// Application 受scheduler调度的最小逻辑单元,有独立的启动参数、各种串行、并行任务 +type Application struct { + Name string + bootFlag interface{} + initializeTasks []pair // 启动服务前串行执行初始化任务的job + services []pair // rpc服务 + servers []pair // web服务 + postRunTasks []pair // 启动后串行执行的job + postRunWorkers []pair // 启动后后台永久执行的工作协程,一旦推出就停止application + parallelJobs []pair // 启动services、servers后并行执行的任务,不关心结果,例如内存数据的预热等 + stopTasks []pair +} + +// newApp +func newApp(name string, options ...AppOption) *Application { + app := new(Application) + app.Name = name + app.applyOptions(options...) + return app +} + +// WithInitializeTask app完成init之后run之前执行的任务,可以用来初始化某些业务或者检查配置等 +func (app *Application) WithInitializeTask(desc string, task Task) *Application { + if task == nil { + return app + } + app.initializeTasks = append(app.initializeTasks, pair{desc, task}) + return app +} + +// WithServer 添加web服务器 +func (app *Application) WithServer(desc string, addr string) *gin.Engine { + server := gin.Default() + app.servers = append(app.servers, pair{desc, pair{addr, server}}) + return server +} + +// WithService 添加rpc服务 +//func (app *Application) WithService(desc string, service *joyservice.ServicesManager) *Application { +// if service == nil { +// return app +// } +// app.services = append(app.services, pair{desc, service}) +// return app +//} + +// WithPostTask app run之后执行的任务,一般做临时检查任务,可以用来服务启动后加载数据检查等 +func (app *Application) WithPostTask(desc string, task Task) *Application { + if task == nil { + return nil + } + app.postRunTasks = append(app.postRunTasks, pair{desc, task}) + return app +} + +// WithPostWorker 完成post task之后执行的后台任务,报错退出等app也会退出,一般做永久的关键后台逻辑 +func (app *Application) WithPostWorker(desc string, worker Worker) *Application { + if worker == nil { + return app + } + app.postRunWorkers = append(app.postRunWorkers, pair{desc, worker}) + return app +} + +// WithParallelJob 完成post task之后执行的并行后台任务,一般做永久的不关键后台逻辑,例如内存预热等 +func (app *Application) WithParallelJob(desc string, job Job) *Application { + if job == nil { + return app + } + app.parallelJobs = append(app.parallelJobs, pair{desc, job}) + return app +} + +// WithStopTask 注册停服逻辑(只能处理正常停服,异常例如panic、oom会监听不到) +func (app *Application) WithStopTask(desc string, task Task) *Application { + if task == nil { + return app + } + app.stopTasks = append(app.stopTasks, pair{desc, task}) + return app +} + +func (app *Application) GetBootConfig() any { + return app.bootFlag +} + +func (app *Application) applyOptions(options ...AppOption) *Application { + for _, option := range options { + option.Apply(app) + } + return app +} + +func (app *Application) run() (err error) { + waitChan := make(chan error, 1) + + // 启动前的初始化任务 + for _, j := range app.initializeTasks { + curErr := j.value.(Task)() + if curErr != nil { + err = fmt.Errorf("run initialize task(%s) return error:%v", j.key, curErr) + return + } + } + + // 启动rpc服务 + //for _, pair := range app.services { + // go func(desc string, s *joyservice.ServicesManager) { + // jlog.Noticef("app %v service %v will listen on %v", app.Name, desc, s.Addr) + // curErr := s.Run() + // if curErr != nil { + // waitChan <- fmt.Errorf("service %s run on %v error:%v", desc, s.Addr, curErr) + // } else { + // + // } + // }(pair.desc, pair.item.(*joyservice.ServicesManager)) + //} + + //defer app.stopServices() + + // 启动web服务 + for _, server := range app.servers { + go func(desc string, info pair) { + addr := info.key.(string) + engine := info.value.(*gin.Engine) + xlog.Noticef("app %v server %v will listen on %v", app.Name, desc, addr) + err := engine.Run(addr) + if err != nil { + waitChan <- fmt.Errorf("server %s error:%v", desc, err) + } else { + + } + }(server.key.(string), server.value.(pair)) + } + + //defer app.stopServers() + + // 启动后串行执行的job + for _, j := range app.postRunTasks { + curErr := j.value.(Task)() + if curErr != nil { + err = fmt.Errorf("run post task %s return error:%v", j.key, curErr) + return + } + } + + // 启动后串行执行的工作协程 + for _, worker := range app.postRunWorkers { + go func(desc string, g Worker) { + curErr := g() + if curErr != nil { + waitChan <- fmt.Errorf("run post worker %s return error:%v", desc, curErr) + } + }(worker.key.(string), worker.value.(Worker)) + } + + // 启动后的并行job + for _, j := range app.parallelJobs { + go j.value.(Job)() + } + + xlog.Noticef("application[%v] run ok.", app.Name) + + select { + case anyErr := <-waitChan: + xlog.Critif("scheduler stop with execute error:%v", anyErr) + return anyErr + } +} + +func (app *Application) stop() { + for _, task := range app.stopTasks { + err := task.value.(Task)() + if err != nil { + xlog.Errorf("app stop, execute %v error:%v", task.key, err) + } else { + xlog.Infof("app %v stop, execute task:%v", app.Name, task.key) + } + } + //app.stopServices() + //app.stopServers() +} + +//func (app *Application) stopServers() { +// for _, s := range app.servers { +// s.item.(*web.Engine).Stop() +// } +//} + +//func (app *Application) stopServices() { +// for _, s := range app.services { +// s.item.(*joyservice.ServicesManager).Stop() +// } +//} diff --git a/admin/lib/node/app_desc.go b/admin/lib/node/app_desc.go new file mode 100644 index 0000000..d22534f --- /dev/null +++ b/admin/lib/node/app_desc.go @@ -0,0 +1,22 @@ +package node + +type AppInitFunc func(app *Application) error + +// ApplicationDescInfo 调度器创建app时注入的app描述信息 +type ApplicationDescInfo struct { + name string + initFunc AppInitFunc + options []AppOption +} + +func NewApplicationDescInfo(name string, initFunc AppInitFunc) *ApplicationDescInfo { + adi := new(ApplicationDescInfo) + adi.name = name + adi.initFunc = initFunc + return adi +} + +func (adi *ApplicationDescInfo) WithOptions(options ...AppOption) *ApplicationDescInfo { + adi.options = append(adi.options, options...) + return adi +} diff --git a/admin/lib/node/app_options.go b/admin/lib/node/app_options.go new file mode 100644 index 0000000..b67c900 --- /dev/null +++ b/admin/lib/node/app_options.go @@ -0,0 +1,31 @@ +package node + +// WithAppBootFlag 设置app的起服参数,flags必须为结构体指针! +// 只支持string/int/int64/bool四种字段类型,例如: +// +// type Flags struct { +// F1 string `env:"id" desc:"boot id" default:"default value"` +// F2 int `env:"num" desc:"number" default:"3"` +// } +// WithAppBootFlag(&Flags{}) +func WithAppBootFlag(flag interface{}) AppOption { + return appOptionFunction(func(app *Application) { + app.bootFlag = flag + }) +} + +func WithAppStopTask(desc string, task Task) AppOption { + return appOptionFunction(func(app *Application) { + app.stopTasks = append(app.stopTasks, pair{desc, task}) + }) +} + +type AppOption interface { + Apply(scd *Application) +} + +type appOptionFunction func(app *Application) + +func (of appOptionFunction) Apply(app *Application) { + of(app) +} diff --git a/admin/lib/node/node.go b/admin/lib/node/node.go new file mode 100644 index 0000000..b0eb245 --- /dev/null +++ b/admin/lib/node/node.go @@ -0,0 +1,209 @@ +package node + +import ( + "admin/lib/xlog" + "admin/lib/xos" + "fmt" + "github.com/gin-gonic/gin" + "sync" + "time" +) + +type IBootConfigContent interface { + GetLogConfig() *LogBootConfig + OnReload(first bool, oldContent string) +} + +// Node 调度器,调度多个app +type Node struct { + // app全局启动参数 + bootFlags struct { + globalBootFlag *CommonBootFlags + exBootFlag interface{} + } + // app全局配置文件 + bootConfigFile struct { + globalBootConfigFileContent IBootConfigContent // app全局配置文件内容结构体指针,为空没有配置文件解析 + globalBootConfigParser func(in []byte, out interface{}) error // 配置文件解析函数,默认yaml + } + + preInitFuncs []pair // 节点node全局的调用方自定义初始化方法,早于节点初始化(初始化起服配置、日志后) + initFuncs []pair // 节点node全局的调用方自定义初始化方法,晚于节点初始化(初始化起服配置、日志后)、早于各个app初始化 + postFuncs []pair // 节点node全局的调用方自定义启动所有app之后执行的方法 + adis []*ApplicationDescInfo // app的描述信息列表,用来生成apps + apps []*Application // 可绑定多个app + tracer *gin.Engine // app全局监控服务,为prometheus、pprof共用 +} + +// NewNode +func NewNode(appOptions ...NodeOption) *Node { + node := new(Node) + node.bootFlags.globalBootFlag = GlobalBootFlags + node.applyOptions(appOptions...) + return node +} + +// AddPreInitTask Node自身初始化任务,在启动APP逻辑之前执行 +func (node *Node) AddPreInitTask(desc string, task Task) *Node { + node.preInitFuncs = append(node.preInitFuncs, pair{desc, task}) + return node +} + +// AddInitTask Node自身初始化任务,在启动APP逻辑之前执行 +func (node *Node) AddInitTask(desc string, task Task) *Node { + node.initFuncs = append(node.initFuncs, pair{desc, task}) + return node +} + +// AddInitTask Node自身初始化任务,在启动APP逻辑之前执行 +func (node *Node) AddPostTask(desc string, task Task) *Node { + node.postFuncs = append(node.postFuncs, pair{desc, task}) + return node +} + +// GetBootFlags 获取节点全局的启动参数信息,即./node -node_id 123 -service_name game的值 +func (node *Node) GetBootFlags() *CommonBootFlags { + return node.bootFlags.globalBootFlag +} + +// GetExBootFlags 获取调用方自定义的全局启动参数 +func (node *Node) GetExBootFlags() interface{} { + return node.bootFlags.exBootFlag +} + +// GetBootConfigContent 获取注册启动配置文件app.yaml内容 +func (node *Node) GetBootConfigContent() IBootConfigContent { + return node.bootConfigFile.globalBootConfigFileContent +} + +func (node *Node) applyOptions(options ...NodeOption) *Node { + for _, option := range options { + option.Apply(node) + } + return node +} + +// ApplyOptions 应用一些节点配置项 +func (node *Node) ApplyOptions(options ...NodeOption) *Node { + node.applyOptions(options...) + return node +} + +// WithApp 节点注入app +func (node *Node) WithApp(appDescInfoList ...*ApplicationDescInfo) *Node { + for _, adi := range appDescInfoList { + node.withAppDescInfo(adi) + } + return node +} + +func (node *Node) Run() error { + // 初始化node + node.initialize() + + defer xlog.CatchWithInfo("run node panic") + + // 启动调度器 + type waitInfo struct { + desc string + app *Application + err error + } + waitChan := make(chan waitInfo, 1) + + // 运行trace server + go func() { + tracerPort := node.bootFlags.globalBootFlag.TracePort + xlog.Noticef("trace server listen on %v", tracerPort) + err := node.tracer.Run(":" + tracerPort) + if err != nil { + waitChan <- waitInfo{"trace server", nil, err} + } + }() + + // 初始化各个app + wg := &sync.WaitGroup{} + wg.Add(len(node.apps)) + for i, app := range node.apps { + go func(app *Application, initFunc AppInitFunc) { + if initFunc != nil { + err := initFunc(app) + if err != nil { + errInfo := fmt.Errorf("application[%v] init return error[%v]", app.Name, err) + waitChan <- waitInfo{app.Name, app, errInfo} + } else { + xlog.Noticef("application[%v] initialize ok", app.Name) + } + } + wg.Done() + err := app.run() + if err != nil { + // 返回调度器的报错 + waitChan <- waitInfo{app.Name, app, err} + } + }(app, node.adis[i].initFunc) + } + + // for _, app := range node.apps { + // go func(app *Application) { + // err := app.run() + // if err != nil { + // // 返回调度器的报错 + // waitChan <- waitInfo{app.Name, app, err} + // } + // }(app) + // } + wg.Wait() + + // 初始化逻辑 + for _, j := range node.postFuncs { + curErr := j.value.(Task)() + if curErr != nil { + err := fmt.Errorf("node run post task(%s) return error:%v", j.key, curErr) + panic(err) + } + } + + watchSignChan := xos.WatchSignal1() + + defer node.Stop() + + select { + case signal := <-watchSignChan: + // 优雅停服,监听某些信号 + xlog.Noticef("Application receive signal(%v), will graceful stop", signal) + return nil + case errInfo := <-waitChan: + err := fmt.Errorf("Application receive Node(%v) stop with error:%v", errInfo.desc, errInfo.err) + xlog.Errorf(err.Error()) + return err + } +} + +func (node *Node) Stop() { + for _, app := range node.apps { + app.stop() + } + time.Sleep(3 * time.Second) +} + +// WithNode 添加调度器 +func (node *Node) withAppDescInfo(adi *ApplicationDescInfo) *Node { + node.adis = append(node.adis, adi) + return node +} + +func (node *Node) initApps() error { + for i, app := range node.apps { + if node.adis[i].initFunc != nil { + err := node.adis[i].initFunc(app) + if err != nil { + return fmt.Errorf("application[%v] init return error[%v]", app.Name, err) + } else { + xlog.Noticef("application[%v] initialize ok", app.Name) + } + } + } + + return nil +} diff --git a/admin/lib/node/node_flags.go b/admin/lib/node/node_flags.go new file mode 100644 index 0000000..bdfc60f --- /dev/null +++ b/admin/lib/node/node_flags.go @@ -0,0 +1,10 @@ +package node + +var GlobalBootFlags = &CommonBootFlags{} + +type CommonBootFlags struct { + NodeID string `env:"node_id" desc:"【必填】节点id(进程id),分布式环境下唯一标识,没指定就是随机、k8s环境下用pod名字作为id"` + ServiceName string `env:"service_name" desc:"【可选】节点服务名,标识节点服务种类,例如game、login,用于rpc服务名调用、日志服务种类输出等"` + TracePort string `env:"trace_port" default:"7788" desc:"监控服务端口,暴露prometheus指标、pprof采集等"` + BootConfig string `env:"boot_config" default:"config/boot.yaml" desc:"启动配置文件路径,默认\"config/boot.yaml\""` +} diff --git a/admin/lib/node/node_init.go b/admin/lib/node/node_init.go new file mode 100644 index 0000000..f3bcf38 --- /dev/null +++ b/admin/lib/node/node_init.go @@ -0,0 +1,201 @@ +/** + * @Author: likun + * @Title: todo + * @Description: todo + * @File: node_init + * @Date: 2024-11-08 14:02:42 + */ +package node + +import ( + "admin/lib/flags" + "admin/lib/prom" + "admin/lib/xlog" + "admin/lib/xlog/handler" + "encoding/json" + "fmt" + "github.com/rs/zerolog" + "gopkg.in/yaml.v3" + "io" + "os" + "time" +) + +// initialize 初始化app +func (node *Node) initialize() { + + // 初始化节点启动参数,例如 ./app -a a -b b -c 123 + must(node.initBootFlags()) + + // 初始化节点启动配置文件,例如config/app.yaml + must(node.initBootConfig()) + + // 初始化逻辑 + for _, j := range node.preInitFuncs { + curErr := j.value.(Task)() + if curErr != nil { + err := fmt.Errorf("node run pre_initialize task(%s) return error:%v", j.key, curErr) + panic(err) + } + } + + // 初始化程序日志系统 + must(node.initLog()) + + // 初始化prometheus metrics、go pprof + must(node.initTracer()) + + // 初始化逻辑 + for _, j := range node.initFuncs { + curErr := j.value.(Task)() + if curErr != nil { + err := fmt.Errorf("node run initialize task(%s) return error:%v", j.key, curErr) + panic(err) + } + } + return +} + +func (node *Node) initBootFlags() error { + var appBootFlags []interface{} + for _, v := range node.adis { + curApp := newApp(v.name, v.options...) + node.apps = append(node.apps, curApp) + if curApp.bootFlag == nil { + continue + } + appBootFlags = append(appBootFlags, curApp.bootFlag) + } + + // 解析启动参数 + flags.ParseWithStructPointers(append([]interface{}{node.bootFlags.globalBootFlag, node.bootFlags.exBootFlag}, appBootFlags...)...) + + flagBin, _ := json.Marshal(node.bootFlags.globalBootFlag) + xlog.Noticef("os args:%+v, parsed flags:%v", os.Args, string(flagBin)) + + // check + if node.bootFlags.globalBootFlag.ServiceName == "" { + if len(node.adis) > 1 { + node.bootFlags.globalBootFlag.ServiceName = "all_in_one" + } else { + node.bootFlags.globalBootFlag.ServiceName = node.adis[0].name + } + } + + return nil +} + +func (node *Node) initBootConfig() error { + // 解析配置文件 + if node.bootConfigFile.globalBootConfigFileContent != nil && node.bootFlags.globalBootFlag.BootConfig != "" { + err := node.parseWithConfigFileContent(true) + if err != nil { + newErr := fmt.Errorf("parse With Config File Content is error:%v", err) + return newErr + } + + // 定时解析配置文件 + ticker := time.NewTicker(time.Second * 3) + go func() { + for range ticker.C { + // 解析配置文件 + err = node.parseWithConfigFileContent(false) + if err != nil { + xlog.Errorf("parse With Config File Content is error:%v", err) + continue + } + } + }() + } + + return nil +} + +func (node *Node) initLog() error { + logConfig := &LogBootConfig{} + if node.bootConfigFile.globalBootConfigFileContent != nil { + logConfig = node.bootConfigFile.globalBootConfigFileContent.GetLogConfig().Check() + } + + var ( + enableLogFile = logConfig.EnableFile + enableLogStdout = logConfig.EnableStdout + logDir = logConfig.LogDir + logLevel = logConfig.LogLevel + nodeId = node.bootFlags.globalBootFlag.NodeID + serviceName = node.bootFlags.globalBootFlag.ServiceName + maxLogFileBytesSize = logConfig.LogFileSize * 1024 * 1024 * 1024 + maxLogRotateBackupCount = logConfig.LogFileRotateCount + ) + + // 初始化日志系统 + var logHandlers []io.Writer + if !enableLogFile { + // 没有指定日志输出目录,默认输出到控制台 + logHandlers = append(logHandlers, os.Stdout) + } else { + // 指定日志输出目录 + logHandler, err := handler.NewRotatingDayMaxFileHandler(logDir, serviceName, maxLogFileBytesSize, maxLogRotateBackupCount) + if err != nil { + newErr := fmt.Errorf("new xlog file handler with path [%v] name[%v] error:%v", logDir, serviceName, err) + return newErr + } + logHandlers = append(logHandlers, logHandler) + + // 也指定输出到控制台 + if enableLogStdout { + logHandlers = append(logHandlers, os.Stdout) + } + } + + // 创建logger + logLevelEnum := xlog.ParseLogLevelString(logLevel) + xlog.NewGlobalLogger(logHandlers, logLevelEnum, func(l zerolog.Logger) zerolog.Logger { + return l.With().Str("service", serviceName).Str("node_id", nodeId).Logger() + }) + + return nil +} + +func (node *Node) initTracer() error { + node.tracer = prom.NewEngine(true) + return nil +} + +// 解析配置文件 +func (node *Node) parseWithConfigFileContent(first bool) error { + + content, err := os.ReadFile(node.bootFlags.globalBootFlag.BootConfig) + if err != nil { + newErr := fmt.Errorf("load boot config file %v error:%v", node.bootFlags.globalBootFlag.BootConfig, err) + return newErr + } + + if node.bootConfigFile.globalBootConfigParser == nil { + node.bootConfigFile.globalBootConfigParser = yaml.Unmarshal + } + + oldContent, _ := json.Marshal(node.bootConfigFile.globalBootConfigFileContent) + + err = node.bootConfigFile.globalBootConfigParser(content, node.bootConfigFile.globalBootConfigFileContent) + if err != nil { + newErr := fmt.Errorf("load boot config file %v content %v ok, but parse content error:%v", + node.bootFlags.globalBootFlag.BootConfig, string(content), err) + return newErr + } + + // 读取配置后设置一次日志等级 + if !first { + xlog.SetLogLevel(xlog.ParseLogLevelString(node.bootConfigFile.globalBootConfigFileContent.GetLogConfig().LogLevel)) + } + + node.bootConfigFile.globalBootConfigFileContent.OnReload(first, string(oldContent)) + + return nil +} + +func must(err error) { + if err != nil { + panic(err) + } +} diff --git a/admin/lib/node/node_log.go b/admin/lib/node/node_log.go new file mode 100644 index 0000000..0acbc4d --- /dev/null +++ b/admin/lib/node/node_log.go @@ -0,0 +1,37 @@ +/** + * @Author: likun + * @Title: todo + * @Description: todo + * @File: node_log + * @Date: 2024-11-08 14:39:33 + */ +package node + +type LogBootConfig struct { + EnableStdout bool `yaml:"enable_stdout"` + EnableFile bool `yaml:"enable_file"` + LogDir string `yaml:"log_dir"` + LogFileSize int `yaml:"log_file_size"` // 单个日志文件最大容量(单位:G) + LogFileRotateCount int `yaml:"log_file_rotate_count"` // 日志文件最大滚动次数 + LogLevel string `yaml:"log_level"` // 日志等级: trace/debug/info/notice/warn/error/fatal +} + +func (lc *LogBootConfig) Check() *LogBootConfig { + if lc.LogDir == "" { + lc.LogDir = "./logs" + } + + if lc.LogFileSize == 0 { + lc.LogFileSize = 1024 * 1024 * 1024 * 5 + } + + if lc.LogFileRotateCount == 0 { + lc.LogFileRotateCount = 10 + } + + if lc.LogLevel == "" { + lc.LogLevel = "debug" + } + + return lc +} diff --git a/admin/lib/node/node_options.go b/admin/lib/node/node_options.go new file mode 100644 index 0000000..ba36466 --- /dev/null +++ b/admin/lib/node/node_options.go @@ -0,0 +1,46 @@ +package node + +// WithNodeBootFlags 设置启动参数解析结构 +//func WithNodeBootFlags(f *CommonBootFlags) NodeOption { +// return nodeOptionFun(func(node *Node) { +// node.globalBootFlag = f +// }) +//} + +// WithNodeBootConfigFileContent 设置启动配置文件的解析结构,不设置默认无起服配置,默认以yaml解析 +func WithNodeBootConfigFileContent(content IBootConfigContent) NodeOption { + return nodeOptionFun(func(node *Node) { + node.bootConfigFile.globalBootConfigFileContent = content + }) +} + +// WithNodeExBootFlags 设置额外的启动参数解析结构 +func WithNodeExBootFlags(content any) NodeOption { + return nodeOptionFun(func(node *Node) { + node.bootFlags.exBootFlag = content + }) +} + +// WithNodeBootConfigFileParser 设置起服文件解析函数,默认yaml格式 +func WithNodeBootConfigFileParser(f func(content []byte, out interface{}) error) NodeOption { + return nodeOptionFun(func(node *Node) { + node.bootConfigFile.globalBootConfigParser = f + }) +} + +// WithNodeLogFileTimestampFormat 设置日志文件默认时间戳格式,默认"20060102" +//func WithNodeLogFileTimestampFormat(format string) NodeOption { +// return appOptionFun(func(node *Node) { +// node.xlog.logFileTsFormat = format +// }) +//} + +type NodeOption interface { + Apply(app *Node) +} + +type nodeOptionFun func(node *Node) + +func (of nodeOptionFun) Apply(node *Node) { + of(node) +} diff --git a/admin/lib/prom/example_test.go b/admin/lib/prom/example_test.go new file mode 100644 index 0000000..f071e93 --- /dev/null +++ b/admin/lib/prom/example_test.go @@ -0,0 +1,50 @@ +package prom + +import ( + "math/rand" + "testing" + "time" +) + +func TestProm(t *testing.T) { + go func() { + NewEngine(true).Run(":9008") + }() + + // 模拟gate进程1 + ccuOnlineCounter := NewGauge("ccu").InitDefaultLabels(map[string]string{ + "app_id": "1", + "app": "gate", + }, []string{"nation"}) + + // 模拟gate进程2 + ccuOnlineCounter1 := NewGauge("ccu1").InitDefaultLabels(map[string]string{ + "app_id": "2", + "app": "gate", + }, []string{"nation"}) + + // 模拟比赛进程1 + ccuBattleCounter := NewGauge("ccu_battle").InitDefaultLabels(map[string]string{ + "app_id": "1", + "app": "battle", + }, []string{"nation"}) + + go func() { + for { + // 模拟统计gate1间隔收集在线人数 + num := float64(rand.Int31n(100)) + ccuOnlineCounter.LabelValues("cn").Add(num) + + // 模拟统计gate2间隔收集在线人数 + ccuOnlineCounter1.LabelValues("cn").Add(num * 2) + + // 模拟统计比赛1间隔收集比赛人数 + num = float64(rand.Int31n(100)) + ccuBattleCounter.LabelValues("uk").Add(num) + + time.Sleep(time.Second * 5) + } + }() + + select {} +} diff --git a/admin/lib/prom/pprof/prof.go b/admin/lib/prom/pprof/prof.go new file mode 100644 index 0000000..aadb83f --- /dev/null +++ b/admin/lib/prom/pprof/prof.go @@ -0,0 +1,65 @@ +package pprof + +import ( + "expvar" + _ "expvar" + "net/http" + _ "net/http/pprof" + "sync" +) + +var _expvars_ints = make(map[string]*expvar.Int) +var _expvars_floats = make(map[string]*expvar.Float) +var _expvars_strings = make(map[string]*expvar.String) + +var _expvars_ints_lock = &sync.RWMutex{} +var _expvars_floats_lock = &sync.RWMutex{} +var _expvars_strings_lock = &sync.RWMutex{} + +// StartCommonProfileMonitor 启动公共性能分析http服务器 +// 接口1:http://ip:port/debug/vars返回内存监控的json数据 +// 接口2:http://ip:port/debug/pprof/xxx +func StartCommonProfileMonitor(accessHttpAddr string) { + go func() { + http.ListenAndServe(accessHttpAddr, nil) + }() +} + +// AddCommonProfileExpVarInt 添加/debug/vars返回的json变量,保证全局名字唯一 +func AddCommonProfileExpVarInt(name string, delta int64) { + _expvars_ints_lock.Lock() + defer _expvars_ints_lock.Unlock() + if data, find := _expvars_ints[name]; find { + data.Add(delta) + } else { + v := expvar.NewInt(name) + v.Add(delta) + _expvars_ints[name] = v + } +} + +// AddCommonProfileExpVarFloat 添加/debug/vars返回的json变量,保证全局名字唯一 +func AddCommonProfileExpVarFloat(name string, delta float64) { + _expvars_floats_lock.Lock() + defer _expvars_floats_lock.Unlock() + if data, find := _expvars_floats[name]; find { + data.Add(delta) + } else { + v := expvar.NewFloat(name) + v.Add(delta) + _expvars_floats[name] = v + } +} + +// AddCommonProfileExpVarInt 添加/debug/vars返回的json变量,保证全局名字唯一 +func AddCommonProfileExpVarString(name string, cur string) { + _expvars_strings_lock.Lock() + defer _expvars_strings_lock.Unlock() + if data, find := _expvars_strings[name]; find { + data.Set(cur) + } else { + v := expvar.NewString(name) + v.Set(cur) + _expvars_strings[name] = v + } +} diff --git a/admin/lib/prom/prom_test.go b/admin/lib/prom/prom_test.go new file mode 100644 index 0000000..e6a6f6c --- /dev/null +++ b/admin/lib/prom/prom_test.go @@ -0,0 +1,37 @@ +package prom + +import ( + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "math/rand" + "testing" + "time" +) + +func TestRawProm(t *testing.T) { + vec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "test", + Help: "test help", + Buckets: []float64{1, 50, 100}, + }, + []string{"app"}, + ) + prometheus.MustRegister(vec) + + go func() { + for i := 0; i < 1000; i++ { + vec.WithLabelValues("battle").Observe(float64(rand.Intn(100))) + time.Sleep(time.Second) + } + }() + + gin.SetMode(gin.ReleaseMode) + engine := gin.Default() + engine.GET("/metrics", gin.WrapH(promhttp.Handler())) + err := engine.Run(":12345") + if err != nil { + panic(err) + } +} diff --git a/admin/lib/prom/promethus.go b/admin/lib/prom/promethus.go new file mode 100644 index 0000000..38ff74d --- /dev/null +++ b/admin/lib/prom/promethus.go @@ -0,0 +1,211 @@ +package prom + +import ( + "fmt" + "sync/atomic" + + ginPprof "github.com/gin-contrib/pprof" + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +func NewCounter(name string) *PromeCounterStatMgr { + mgr := new(PromeCounterStatMgr) + mgr.promeStatMgr = newPromeStatMgr(name, func(defaultLabels, labels []string) prometheus.Collector { + return newCounterVec(name, defaultLabels, labels) + }) + return mgr +} + +func NewGauge(name string) *PromeGaugeStatMgr { + mgr := new(PromeGaugeStatMgr) + mgr.promeStatMgr = newPromeStatMgr(name, func(defaultLabels, labels []string) prometheus.Collector { + return newGagueVec(name, defaultLabels, labels) + }) + return mgr +} + +func NewHistogram(name string, buckets []float64) *PromeHistogramStatMgr { + mgr := new(PromeHistogramStatMgr) + mgr.promeStatMgr = newPromeStatMgr(name, func(defaultLabels, labels []string) prometheus.Collector { + return newHistogramVec(name, defaultLabels, labels, buckets) + }) + return mgr +} + +type PromeCounterStatMgr struct { + *promeStatMgr +} + +// InitLabels 初始化指标的标签说明,以后每次收集指标都给上标签值 +func (c *PromeCounterStatMgr) InitLabels(labels []string) *PromeCounterStatMgr { + return c.InitDefaultLabels(nil, labels) +} + +// InitDefaultLabels 带上默认标签,例如某些服务启动就能知道标签值,比如app_name,app_id等, +// 又不想每次收集指标都传递标签值,可以用此方法一开始就设置好 +func (c *PromeCounterStatMgr) InitDefaultLabels(defaultLabels map[string]string, labels []string) *PromeCounterStatMgr { + c.promeStatMgr.withDefaultLabels(defaultLabels, labels) + return c +} + +// LabelValues 准备收集指标,调用这个传递标签值,后续再给上指标值 +// 即:c.LabelValues("xxx").Add(234) +func (c *PromeCounterStatMgr) LabelValues(labels ...string) prometheus.Counter { + return c.promeStatMgr.getCounterWithLabels(labels...) +} + +type PromeGaugeStatMgr struct { + *promeStatMgr +} + +// InitLabels 初始化指标的标签说明,以后每次收集指标都给上标签值 +func (c *PromeGaugeStatMgr) InitLabels(labels []string) *PromeGaugeStatMgr { + return c.InitDefaultLabels(nil, labels) +} + +// InitDefaultLabels 带上默认标签,例如某些服务启动就能知道标签值,比如app_name,app_id等, +// 又不想每次收集指标都传递标签值,可以用此方法一开始就设置好 +func (c *PromeGaugeStatMgr) InitDefaultLabels(defaultLabels map[string]string, labels []string) *PromeGaugeStatMgr { + c.promeStatMgr.withDefaultLabels(defaultLabels, labels) + return c +} + +// LabelValues 准备收集指标,调用这个传递标签值,后续再给上指标值 +// 即:c.LabelValues("xxx").Add(234) +func (c *PromeGaugeStatMgr) LabelValues(labels ...string) prometheus.Gauge { + return c.promeStatMgr.getGaugeWithLabels(labels...) +} + +type PromeHistogramStatMgr struct { + *promeStatMgr +} + +// InitLabels 初始化指标的标签说明,以后每次收集指标都给上标签值 +func (c *PromeHistogramStatMgr) InitLabels(labels []string) *PromeHistogramStatMgr { + return c.InitDefaultLabels(nil, labels) +} + +// InitDefaultLabels 带上默认标签,例如某些服务启动就能知道标签值,比如app_name,app_id等, +// 又不想每次收集指标都传递标签值,可以用此方法一开始就设置好 +func (c *PromeHistogramStatMgr) InitDefaultLabels(defaultLabels map[string]string, labels []string) *PromeHistogramStatMgr { + c.promeStatMgr.withDefaultLabels(defaultLabels, labels) + return c +} + +// LabelValues 准备收集指标,调用这个传递标签值,后续再给上指标值 +// 即:c.LabelValues("xxx").Add(234) +func (c *PromeHistogramStatMgr) LabelValues(labels ...string) prometheus.Observer { + return c.promeStatMgr.getHistogramWithLabels(labels...) +} + +type promeStatMgr struct { + collector prometheus.Collector + name string + newCollectorFun func(defaultLabels, labels []string) prometheus.Collector + defaultLabelsValue []string + initCounter int32 +} + +func (mgr *promeStatMgr) withDefaultLabels(defaultLabels map[string]string, labels []string) { + if atomic.AddInt32(&mgr.initCounter, 1) > 1 { + panic(fmt.Errorf("promethus vec labels must init once")) + } + + defaultLabelKeys := make([]string, 0) + defaultLabelValues := make([]string, 0) + for k, v := range defaultLabels { + defaultLabelKeys = append(defaultLabelKeys, k) + defaultLabelValues = append(defaultLabelValues, v) + } + + mgr.defaultLabelsValue = defaultLabelValues + mgr.collector = mgr.newCollectorFun(defaultLabelKeys, labels) + return +} + +func (mgr *promeStatMgr) joinLabels(labels ...string) []string { + newLabels := make([]string, 0, len(mgr.defaultLabelsValue)+len(labels)) + newLabels = append(newLabels, mgr.defaultLabelsValue...) + newLabels = append(newLabels, labels...) + return newLabels +} + +func (mgr *promeStatMgr) getCounterWithLabels(labels ...string) prometheus.Counter { + newLabels := mgr.joinLabels(labels...) + return mgr.collector.(*prometheus.CounterVec).WithLabelValues(newLabels...) +} + +func (mgr *promeStatMgr) getGaugeWithLabels(labels ...string) prometheus.Gauge { + newLabels := mgr.joinLabels(labels...) + return mgr.collector.(*prometheus.GaugeVec).WithLabelValues(newLabels...) +} + +func (mgr *promeStatMgr) getHistogramWithLabels(labels ...string) prometheus.Observer { + newLabels := mgr.joinLabels(labels...) + return mgr.collector.(*prometheus.HistogramVec).WithLabelValues(newLabels...) +} + +func newPromeStatMgr(name string, + newCollectorFun func(defaultLabelsKey []string, labels []string) prometheus.Collector) *promeStatMgr { + mgr := new(promeStatMgr) + mgr.name = name + mgr.newCollectorFun = newCollectorFun + return mgr +} + +func newCounterVec(name string, defaultLabels []string, dynamicLabels []string) prometheus.Collector { + vec := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: name, + Help: name, + }, + append(defaultLabels, dynamicLabels...), + ) + prometheus.MustRegister(vec) + return vec +} + +func newGagueVec(name string, defaultLabels []string, dynamicLabels []string) prometheus.Collector { + vec := prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: name, + Help: name, + }, + append(defaultLabels, dynamicLabels...), + ) + prometheus.MustRegister(vec) + return vec +} + +func newHistogramVec(name string, defaultLabels []string, dynamicLabels []string, buckets []float64) prometheus.Collector { + vec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: name, + Help: name, + Buckets: buckets, + }, + append(defaultLabels, dynamicLabels...), + ) + prometheus.MustRegister(vec) + return vec +} + +func NewEngine(enablePprof bool) *gin.Engine { + //gin.SetMode(gin.ReleaseMode) + engine := gin.New() + engine.Use(gin.Recovery()) + RouteEngine(engine, enablePprof) + return engine +} + +func RouteEngine(engine *gin.Engine, enablePprof bool) { + if enablePprof { + ginPprof.Register(engine) + } + engine.Use(gin.LoggerWithConfig(gin.LoggerConfig{ + SkipPaths: []string{"/metrics"}, + })) + engine.GET("/metrics", gin.WrapH(promhttp.Handler())) +} diff --git a/admin/lib/web/example/example.go b/admin/lib/web/example/example.go new file mode 100644 index 0000000..4d27ae7 --- /dev/null +++ b/admin/lib/web/example/example.go @@ -0,0 +1,212 @@ +package main + +import ( + "admin/lib/web" + "fmt" + "html/template" + "sort" +) + +type MyContext struct { + rawCtx web.RawContext +} + +func (ctx *MyContext) SetRawContext(rawCtx web.RawContext) { + ctx.rawCtx = rawCtx +} +func (ctx *MyContext) GetRawContext() web.RawContext { + return ctx.rawCtx +} + +func main() { + coreRouter := "iris" // gin|iris + engine := web.NewEngine(":8888", coreRouter, func() web.Context { + return new(MyContext) + }) + groupV1 := engine.Group("/v1") + { + type Test1 struct { + RoleId string `json:"role_id" desc:"角色id字段" required:"true"` + F1 int `json:"f1" desc:"这是字段1" default:"324"` + F2 string `json:"f2" desc:"这是字段2" default:"abcd"` + F3 bool `json:"f3" desc:"这是字段3" default:"false"` + } + groupV1.Get("/test1", "设置玩家背包数据", web.AccessMode_Write, Test1{}, func(ctx *MyContext, params *Test1) { + fmt.Printf("receive test1 path params:%+v\n", params) + ctx.GetRawContext().Json(200, map[string]any{ + "msg": "ok", + "code": 200, + }) + }) + groupV1.Post("/test1", "获取玩家数据", web.AccessMode_Read, Test1{}, func(ctx *MyContext, params *Test1) { + fmt.Printf("receive test1 path params:%+v\n", params) + ctx.GetRawContext().Json(200, map[string]any{ + "msg": "ok", + "code": 200, + }) + }) + // URI --> :8888/v1/test1?f1=123&f2=sdfsd&f3=true + // BODY --> :8888/v1/test1 {"f1":123,"f2":"sdfds","f3":"true"} + } + engine.Get("/test2", "获取玩家比赛", web.AccessMode_Read, nil, func(ctx *MyContext) { + fmt.Printf("receive test2 request\n") + ctx.GetRawContext().Json(200, map[string]any{ + "msg": "ok", + "code": 200, + }) + }) + engine.Post("/test2", "测试gm指令名字", web.AccessMode_Read, nil, func(ctx *MyContext) { + fmt.Printf("receive test2 request\n") + ctx.GetRawContext().Json(200, map[string]any{ + "msg": "ok", + "code": 200, + }) + }) + groupV2 := engine.Group("v2") + { + type Test2 struct { + F1 int `json:"f1" desc:"这是字段1"` + F2 string `json:"f2" desc:"这是字段2"` + F3 bool `json:"f3" desc:"这是字段3"` + } + groupV2.Post("test3", "测试gm指令名字123", web.AccessMode_Write, Test2{}, func(ctx *MyContext, params *Test2) { + fmt.Printf("receive test3\n") + ctx.GetRawContext().Json(200, map[string]any{ + "msg": "ok", + "code": 200, + }) + }) + } + + engine.Get("path_tree", "测试gm指令名字3424", web.AccessMode_Write, nil, func(ctx *MyContext) { + tree := engine.TravelPathTree() + ctx.GetRawContext().Json(200, tree) + }) + + engine.Get("/grid", "获取指令描述表", web.AccessMode_Read, nil, func(c *MyContext) { + tree := &Tree{tree: engine.TravelPathTree()} + + tmpl, err := template.New("html_test").Funcs(template.FuncMap(map[string]any{ + "incr": incr, + })).Parse(tplText) + if err != nil { + panic(err) + } + err = tmpl.Execute(c.GetRawContext().Writer(), tree) + if err != nil { + panic(err) + } + }) + + err := engine.Run() + if err != nil { + panic(err) + } +} + +type Path struct { + Path string + Route *web.RouteDescInfo +} + +type Tree struct { + tree map[string]*web.RouteDescInfo +} + +func (t *Tree) Paths() [][]*Path { + splitCol := 4 + list := make([]*Path, 0) + for k, v := range t.tree { + list = append(list, &Path{Path: k, Route: v}) + } + sort.SliceStable(list, func(i, j int) bool { + return list[i].Path < list[j].Path + }) + + if len(list) <= splitCol { + return [][]*Path{list} + } + + section := len(list) / splitCol + paths := make([][]*Path, splitCol) + + for i := 0; i < splitCol; i++ { + paths[i] = make([]*Path, 0) + start := i * section + for j := 0; j < section; j++ { + start += j + paths[i] = append(paths[i], list[start]) + } + } + + if len(list)%splitCol > 0 { + idx := 0 + for i := len(list) - len(list)%splitCol; i < len(list); i++ { + paths[idx] = append(paths[idx], list[i]) + idx++ + } + } + + return paths +} + +func incr(src int) int { + return src + 1 +} + +var tplText = ` + + + + + GM MASTER +
+ {{ $pathsList := .Paths }} + {{ range $pidex, $paths := $pathsList }} +
+

指令列表{{ incr $pidex }}

+
+ + {{ range $idx, $path := $paths }} + + + + + + + + + + + + {{ if $path.Route.HasRequest }} + {{ range $idx1, $field := $path.Route.Fields }} + + + + + + {{ if eq $field.Default "" }} + + {{ else }} + + {{ end }} + + {{ end }} + {{ else }} + + {{ end }} + + {{ end }} +
{{ $path.Route.Desc }}
请求路径  {{ $path.Path }}
方法  {{ $path.Route.MethodDesc "或" }}
{{ printf "参数%d" $idx1 }}{{printf "%s" $field.Name }}{{printf "%s" $field.Type }}{{printf "%s" $field.Desc }}{{printf "默认:无" }}{{printf "默认:%s" $field.Default }}
无需参数
+
+
+ {{ end }} +
+ + + + + + +` diff --git a/admin/lib/web/group.go b/admin/lib/web/group.go new file mode 100644 index 0000000..62b41b7 --- /dev/null +++ b/admin/lib/web/group.go @@ -0,0 +1,183 @@ +package web + +type routeGroupInterface interface { + Use(middlewares ...HandlerFunc) + Group(path string, handlers ...HandlerFunc) routeGroupInterface + Get(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface + Post(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface + Put(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface + Delete(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface +} + +type Group struct { + coreRouter routeGroupInterface + DirectRoutes map[string]*RouteDescInfo + GroupRoutes map[string]*Group +} + +func newGroup(coreRouter routeGroupInterface) *Group { + g := &Group{ + coreRouter: coreRouter, + DirectRoutes: make(map[string]*RouteDescInfo), + GroupRoutes: make(map[string]*Group), + } + return g +} + +func (e *Group) Use(middlewares ...HandlerFunc) { + e.coreRouter.Use(middlewares...) +} + +func (e *Group) Group(path string, handlers ...HandlerFunc) *Group { + path = pathBeTheSame(path) + routeGroup := e.coreRouter.Group(path, handlers...) + group := newGroup(routeGroup) + e.GroupRoutes[path] = group + return group +} + +// Get 注册get方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数 +// +// path:路径 +// desc:路由的一个简短描述 +// request:请求结构体 +// 格式: +// type struct { +// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"` +// } +// tag描述: +// json:字段名 +// desc:字段简短描述,没有可以不写 +// default:默认值,没有可以不写 +// required:是否必填字段,没有要求可以不写 +// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型, +// 例如: +// type Field struct { +// A int +// B bool +// } +// type Request struct { +// F1 *Field +// } +// F1字段就是非法的,无法解析,会报错 +// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request) +func (e *Group) Get(path string, desc string, access AccessMode, request any, handler HandlerFunc) { + path = pathBeTheSame(path) + old, find := e.DirectRoutes[path] + if !find { + e.DirectRoutes[path] = newRouteDescInfo(path, desc, "GET", access, request) + } else { + old.Method = append(old.Method, "GET") + e.DirectRoutes[path] = old + } + e.coreRouter.Get(path, desc, request, handler) +} + +// Post 注册post方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数 +// +// path:路径 +// desc:路由的一个简短描述 +// request:请求结构体 +// 格式: +// type struct { +// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"` +// } +// tag描述: +// json:字段名 +// desc:字段简短描述,没有可以不写 +// default:默认值,没有可以不写 +// required:是否必填字段,没有要求可以不写 +// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型, +// 例如: +// type Field struct { +// A int +// B bool +// } +// type Request struct { +// F1 *Field +// } +// F1字段就是非法的,无法解析,会报错 +// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request) +func (e *Group) Post(path, desc string, access AccessMode, request any, handler HandlerFunc) { + path = pathBeTheSame(path) + old, find := e.DirectRoutes[path] + if !find { + e.DirectRoutes[path] = newRouteDescInfo(path, desc, "POST", access, request) + } else { + old.Method = append(old.Method, "POST") + e.DirectRoutes[path] = old + } + e.coreRouter.Post(path, desc, request, handler) +} + +func (e *Group) Put(path, desc string, access AccessMode, request any, handler HandlerFunc) { + path = pathBeTheSame(path) + old, find := e.DirectRoutes[path] + if !find { + e.DirectRoutes[path] = newRouteDescInfo(path, desc, "PUT", access, request) + } else { + old.Method = append(old.Method, "PUT") + e.DirectRoutes[path] = old + } + e.coreRouter.Put(path, desc, request, handler) +} + +func (e *Group) Delete(path, desc string, access AccessMode, request any, handler HandlerFunc) { + path = pathBeTheSame(path) + old, find := e.DirectRoutes[path] + if !find { + e.DirectRoutes[path] = newRouteDescInfo(path, desc, "DELETE", access, request) + } else { + old.Method = append(old.Method, "DELETE") + e.DirectRoutes[path] = old + } + e.coreRouter.Delete(path, desc, request, handler) +} + +// GetAndPost 注册get&post方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数 +// +// path:路径 +// desc:路由的一个简短描述 +// request:请求结构体 +// 格式: +// type struct { +// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"` +// } +// tag描述: +// json:字段名 +// desc:字段简短描述,没有可以不写 +// default:默认值,没有可以不写 +// required:是否必填字段,没有要求可以不写 +// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型, +// 例如: +// type Field struct { +// A int +// B bool +// } +// type Request struct { +// F1 *Field +// } +// F1字段就是非法的,无法解析,会报错 +// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request) +func (e *Group) GetAndPost(path, desc string, access AccessMode, request any, handler HandlerFunc) { + path = pathBeTheSame(path) + record := newRouteDescInfo(path, desc, "GET", access, request) + record.Method = append(record.Method, "POST") + e.DirectRoutes[path] = record + e.coreRouter.Get(path, desc, request, handler) + e.coreRouter.Post(path, desc, request, handler) +} + +func (e *Group) TravelPathTree() map[string]*RouteDescInfo { + m := make(map[string]*RouteDescInfo) + for k, route := range e.DirectRoutes { + m[k] = route + } + for k, subG := range e.GroupRoutes { + gm := subG.TravelPathTree() + for k1, v1 := range gm { + m[k+k1] = v1 + } + } + return m +} diff --git a/admin/lib/web/model.go b/admin/lib/web/model.go new file mode 100644 index 0000000..59f9837 --- /dev/null +++ b/admin/lib/web/model.go @@ -0,0 +1,24 @@ +package web + +import ( + "fmt" + "io" +) + +type Context interface { + SetRawContext(ctx RawContext) + GetRawContext() RawContext +} + +type HandlerFunc any + +type RawContext interface { + Json(code int, v any) + Writer() io.Writer +} + +var ParseParamsErrorHandler = func(ctx Context, path string, err error) { + ctx.GetRawContext().Json(501, map[string]interface{}{ + "MSG": fmt.Sprintf("parse params error:%v", err), + }) +} diff --git a/admin/lib/web/readme.txt b/admin/lib/web/readme.txt new file mode 100644 index 0000000..3d01ed6 --- /dev/null +++ b/admin/lib/web/readme.txt @@ -0,0 +1,12 @@ + + +注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型, +例如: +type Field struct { + A int + B bool +} +type Request struct { + F1 *Field +} +F1字段就是非法的,无法解析,会报错 \ No newline at end of file diff --git a/admin/lib/web/router.go b/admin/lib/web/router.go new file mode 100644 index 0000000..16180a5 --- /dev/null +++ b/admin/lib/web/router.go @@ -0,0 +1,173 @@ +package web + +import "fmt" + +type routerInterface interface { + routeGroupInterface + Run(addr string) error +} + +type Engine struct { + Addr string + newContextFun func() Context + DirectRoutes map[string]*RouteDescInfo + GroupRoutes map[string]*Group + coreRouter routerInterface +} + +// NewEngine 使用gin或者iris创建web引擎,newContextFun为调用方需要实现的context初始化函数 +// coreRouter指定不同web引擎,目前只支持gin +func NewEngine(coreRouter string, newContextFun func() Context) *Engine { + e := &Engine{ + newContextFun: newContextFun, + DirectRoutes: make(map[string]*RouteDescInfo), + GroupRoutes: make(map[string]*Group), + } + switch coreRouter { + //case "iris": + // e.coreRouter = newRouterIris(newContextFun) + case "gin": + e.coreRouter = newRouterGin(newContextFun) + default: + panic(fmt.Errorf("NewEngine only support irir or gin, invalid type:%v", coreRouter)) + } + return e +} + +func (e *Engine) Use(middlewares ...HandlerFunc) { + e.coreRouter.Use(middlewares...) +} + +func (e *Engine) Group(path string, handlers ...HandlerFunc) *Group { + path = pathBeTheSame(path) + routeGroup := e.coreRouter.Group(path, handlers...) + group := newGroup(routeGroup) + e.GroupRoutes[path] = group + return group +} + +// Get 注册get方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数 +// +// path:路径 +// desc:路由的一个简短描述 +// request:请求结构体 +// 格式: +// type struct { +// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"` +// } +// tag描述: +// json:字段名 +// desc:字段简短描述,没有可以不写 +// default:默认值,没有可以不写 +// required:是否必填字段,没有要求可以不写 +// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型, +// 例如: +// type Field struct { +// A int +// B bool +// } +// type Request struct { +// F1 *Field +// } +// F1字段就是非法的,无法解析,会报错 +// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request) +func (e *Engine) Get(path string, desc string, access AccessMode, request any, handler HandlerFunc) { + path = pathBeTheSame(path) + old, find := e.DirectRoutes[path] + if !find { + e.DirectRoutes[path] = newRouteDescInfo(path, desc, "GET", access, request) + } else { + old.Method = append(old.Method, "GET") + e.DirectRoutes[path] = old + } + e.coreRouter.Get(path, desc, request, handler) +} + +// Post 注册post方法路由,根据request请求体优先从body中以json格式解析参数,如果没有body,则从uri参数中解析出请求参数 +// +// path:路径 +// desc:路由的一个简短描述 +// request:请求结构体 +// 格式: +// type struct { +// F1 int `json:"f1" desc:"字段描述" default:"234" required:"true"` +// } +// tag描述: +// json:字段名 +// desc:字段简短描述,没有可以不写 +// default:默认值,没有可以不写 +// required:是否必填字段,没有要求可以不写 +// 注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型, +// 例如: +// type Field struct { +// A int +// B bool +// } +// type Request struct { +// F1 *Field +// } +// F1字段就是非法的,无法解析,会报错 +// handlers:路由处理函数,如果没有请求体,就是func(ctx),否则就是func(ctx, request) +func (e *Engine) Post(path, desc string, access AccessMode, request any, handler HandlerFunc) { + path = pathBeTheSame(path) + old, find := e.DirectRoutes[path] + if !find { + e.DirectRoutes[path] = newRouteDescInfo(path, desc, "POST", access, request) + } else { + old.Method = append(old.Method, "POST") + e.DirectRoutes[path] = old + } + e.coreRouter.Post(path, desc, request, handler) +} + +func (e *Engine) Run(addr string) error { + e.Addr = addr + return e.coreRouter.Run(addr) +} + +// TravelPathTree 获取所有路径的描述表 +func (e *Engine) TravelPathTree() map[string]*RouteDescInfo { + m := make(map[string]*RouteDescInfo) + for k, route := range e.DirectRoutes { + m[k] = route + } + for k, subG := range e.GroupRoutes { + gm := subG.TravelPathTree() + for k1, v1 := range gm { + m[k+k1] = v1 + } + } + return m +} + +func pathBeTheSame(path string) string { + if path == "" { + return "" + } + if path == "/" { + return path + } + if path[0] != '/' { + path = "/" + path + } + if path[len(path)-1] == '/' { + path = path[:len(path)-1] + } + return path +} + +func pathBeTheSame1(path string) string { + if path == "" { + return "" + } + if path == "/" { + return path + } + if path[0] != '/' { + path = "/" + path + } + if path[len(path)-1] == '/' { + path = path[:len(path)-1] + } + return path +} diff --git a/admin/lib/web/router_desc.go b/admin/lib/web/router_desc.go new file mode 100644 index 0000000..da509c9 --- /dev/null +++ b/admin/lib/web/router_desc.go @@ -0,0 +1,85 @@ +package web + +import ( + "reflect" + "strings" +) + +type AccessMode int + +const ( + AccessMode_Read = 1 + AccessMode_Write = 2 +) + +type RouteDescInfo struct { + Path string + Desc string + Method []string + AccessMode + rawRequest any + Fields []*FieldDescInfo +} + +func newRouteDescInfo(path, desc, method string, access AccessMode, request any) *RouteDescInfo { + info := &RouteDescInfo{ + Path: path, + Desc: desc, + Method: []string{method}, + AccessMode: access, + rawRequest: request, + } + info.RequestFieldsDesc() + return info +} + +func (info *RouteDescInfo) MethodDesc(sep string) string { + return strings.Join(info.Method, sep) +} + +func (info *RouteDescInfo) RequestFieldsDesc() []*FieldDescInfo { + if !info.HasRequest() { + return []*FieldDescInfo{} + } + + list := make([]*FieldDescInfo, 0) + to := reflect.TypeOf(info.rawRequest) + if to.Kind() == reflect.Ptr { + to = to.Elem() + } + for i := 0; i < to.NumField(); i++ { + field := to.Field(i) + list = append(list, newFieldDescInfo(field)) + } + info.Fields = list + return list +} + +func (info *RouteDescInfo) HasRequest() bool { + return info.rawRequest != nil +} + +type FieldDescInfo struct { + Name string + FieldName string + Type string + Desc string + Required bool + Default string + rawTypeOfField reflect.StructField +} + +func newFieldDescInfo(field reflect.StructField) *FieldDescInfo { + desc := &FieldDescInfo{ + Name: field.Tag.Get("json"), + FieldName: field.Name, + Type: field.Type.String(), + Desc: field.Tag.Get("desc"), + rawTypeOfField: field, + } + if field.Tag.Get("required") == "true" { + desc.Required = true + } + desc.Default = desc.rawTypeOfField.Tag.Get("default") + return desc +} diff --git a/admin/lib/web/router_gin.go b/admin/lib/web/router_gin.go new file mode 100644 index 0000000..f0bfe1b --- /dev/null +++ b/admin/lib/web/router_gin.go @@ -0,0 +1,303 @@ +package web + +import ( + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "io" + "reflect" + "strconv" + "strings" +) + +type routerGroupGin struct { + group *gin.RouterGroup + newContextFun func() Context +} + +func (group *routerGroupGin) Use(middlewares ...HandlerFunc) { + group.group.Use(getGinHandlerFun(group.newContextFun, middlewares...)...) +} +func (group *routerGroupGin) Group(path string, handlers ...HandlerFunc) routeGroupInterface { + ginGroup := group.group.Group(path, getGinHandlerFun(group.newContextFun, handlers...)...) + group1 := &routerGroupGin{group: ginGroup, newContextFun: group.newContextFun} + return group1 +} +func (group *routerGroupGin) Get(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface { + if req == nil { + group.group.GET(path, getGinHandlerFun(group.newContextFun, handlers...)...) + } else { + group.group.GET(path, getGinHandlerFunWithRequest(group.newContextFun, req, handlers...)...) + } + return group +} +func (group *routerGroupGin) Post(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface { + if req == nil { + group.group.POST(path, getGinHandlerFun(group.newContextFun, handlers...)...) + } else { + group.group.POST(path, getGinHandlerFunWithRequest(group.newContextFun, req, handlers...)...) + } + return group +} + +func (group *routerGroupGin) Put(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface { + if req == nil { + group.group.PUT(path, getGinHandlerFun(group.newContextFun, handlers...)...) + } else { + group.group.PUT(path, getGinHandlerFunWithRequest(group.newContextFun, req, handlers...)...) + } + return group +} + +func (group *routerGroupGin) Delete(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface { + if req == nil { + group.group.DELETE(path, getGinHandlerFun(group.newContextFun, handlers...)...) + } else { + group.group.DELETE(path, getGinHandlerFunWithRequest(group.newContextFun, req, handlers...)...) + } + return group +} + +type routerGin struct { + engine *gin.Engine + newContextFun func() Context +} + +func newRouterGin(newContextFun func() Context) routerInterface { + engine := gin.Default() + router := &routerGin{ + engine: engine, + newContextFun: newContextFun, + } + return router +} + +func (router *routerGin) Use(middlewares ...HandlerFunc) { + router.engine.Use(getGinHandlerFun(router.newContextFun, middlewares...)...) +} +func (router *routerGin) Group(path string, handlers ...HandlerFunc) routeGroupInterface { + ginGroup := router.engine.Group(path, getGinHandlerFun(router.newContextFun, handlers...)...) + group := &routerGroupGin{ + group: ginGroup, + newContextFun: router.newContextFun, + } + return group +} +func (router *routerGin) Get(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface { + if req == nil { + router.engine.GET(path, getGinHandlerFun(router.newContextFun, handlers...)...) + } else { + router.engine.GET(path, getGinHandlerFunWithRequest(router.newContextFun, req, handlers...)...) + } + return router +} +func (router *routerGin) Post(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface { + if req == nil { + router.engine.POST(path, getGinHandlerFun(router.newContextFun, handlers...)...) + } else { + router.engine.POST(path, getGinHandlerFunWithRequest(router.newContextFun, req, handlers...)...) + } + return router +} + +func (router *routerGin) Put(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface { + if req == nil { + router.engine.PUT(path, getGinHandlerFun(router.newContextFun, handlers...)...) + } else { + router.engine.PUT(path, getGinHandlerFunWithRequest(router.newContextFun, req, handlers...)...) + } + return router +} + +func (router *routerGin) Delete(path string, desc string, req any, handlers ...HandlerFunc) routeGroupInterface { + if req == nil { + router.engine.DELETE(path, getGinHandlerFun(router.newContextFun, handlers...)...) + } else { + router.engine.DELETE(path, getGinHandlerFunWithRequest(router.newContextFun, req, handlers...)...) + } + return router +} + +func getGinHandlerFun(newContextFun func() Context, handlers ...HandlerFunc) []gin.HandlerFunc { + list := make([]gin.HandlerFunc, 0, len(handlers)) + for _, handler := range handlers { + list = append(list, func(rawCtx *gin.Context) { + rawCtx1 := &ginCtx{ctx: rawCtx} + ctx := newContextFun() + ctx.SetRawContext(rawCtx1) + reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(ctx)}) + if rawCtx.IsAborted() { + return + } + }) + } + return list +} + +func getGinHandlerFunWithRequest(newContextFun func() Context, requestTemplate any, handlers ...HandlerFunc) []gin.HandlerFunc { + list := make([]gin.HandlerFunc, 0, len(handlers)) + for _, handler := range handlers { + list = append(list, func(rawCtx *gin.Context) { + rawCtx1 := &ginCtx{ctx: rawCtx} + ctx := newContextFun() + ctx.SetRawContext(rawCtx1) + request, err := rawCtx1.parseRequest(requestTemplate) + if err != nil { + ParseParamsErrorHandler(ctx, rawCtx.Request.RequestURI, err) + return + } + reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(request)}) + }) + } + return list +} + +func (router *routerGin) Run(addr string) error { + return router.engine.Run(addr) +} + +type ginCtx struct { + ctx *gin.Context +} + +func (ctx *ginCtx) Json(code int, v any) { + ctx.ctx.JSON(code, v) +} + +func (ctx *ginCtx) Writer() io.Writer { + return ctx.ctx.Writer +} + +func (ctx *ginCtx) body() ([]byte, error) { + buf, err := io.ReadAll(ctx.ctx.Request.Body) + if err != nil { + return nil, fmt.Errorf("read body error:%v", err) + } + + return buf, nil +} + +func (ctx *ginCtx) parseRequest(request any) (any, error) { + if request == nil { + return nil, nil + } + + requestType := reflect.TypeOf(request) + newRequest := reflect.New(requestType).Interface() + + body, err := ctx.body() + if err != nil { + return nil, err + } + + if len(body) == 0 { + newRequestValue := reflect.ValueOf(newRequest).Elem() + newRequestValueType := newRequestValue.Type() + for i := 0; i < newRequestValue.NumField(); i++ { + f := newRequestValueType.Field(i) + fieldTagName := f.Tag.Get("json") + if fieldTagName == "" { + fieldTagName = f.Name + } + + field := newRequestValue.Field(i) + if !field.CanSet() { + continue + } + fieldStr := ctx.ctx.Query(fieldTagName) + err := setValue(field, fieldStr, f.Tag.Get("default"), f.Tag.Get("required")) + if err != nil { + return nil, fmt.Errorf("parse uri params field(%v) set value(%v) error:%v", f.Name, fieldStr, err) + } + } + } else { + err = json.Unmarshal(body, newRequest) + if err != nil { + return nil, fmt.Errorf("json unmarshal body error:%v", err) + } + } + return newRequest, nil +} + +// setValue 设置结构体一个字段的值 +func setValue(field reflect.Value, value string, defaultValue string, required string) error { + if value == "" { + value = defaultValue + } + if value == "" && required == "true" { + return fmt.Errorf("field is required, please give a valid value") + } + + if field.Kind() == reflect.Ptr { + if value == "" { + return nil + } + + if field.IsNil() { + field.Set(reflect.New(field.Type().Elem())) + } + field = field.Elem() + } + switch field.Kind() { + case reflect.String: + field.SetString(value) + case reflect.Bool: + if value == "" { + field.SetBool(false) + } else { + b, err := strconv.ParseBool(value) + if err != nil { + return err + } + field.SetBool(b) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if value == "" { + field.SetInt(0) + } else { + i, err := strconv.ParseInt(value, 0, field.Type().Bits()) + if err != nil { + return err + } + field.SetInt(i) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if value == "" { + field.SetUint(0) + break + } + ui, err := strconv.ParseUint(value, 0, field.Type().Bits()) + if err != nil { + return err + } + field.SetUint(ui) + case reflect.Float32, reflect.Float64: + if value == "" { + field.SetFloat(0) + break + } + f, err := strconv.ParseFloat(value, field.Type().Bits()) + if err != nil { + return err + } + field.SetFloat(f) + case reflect.Struct: + return fmt.Errorf("unsupport struct field:%v", field.Type()) + case reflect.Slice: + values := strings.Split(value, ",") + if len(values) == 1 && values[0] == "" { + values = []string{} + } + field.Set(reflect.MakeSlice(field.Type(), len(values), len(values))) + for i := 0; i < len(values); i++ { + err := setValue(field.Index(i), values[i], "", "") + if err != nil { + return err + } + } + + default: + return fmt.Errorf("no support type %s", field.Type()) + } + return nil +} diff --git a/admin/lib/xlog/catch.go b/admin/lib/xlog/catch.go new file mode 100644 index 0000000..7d736e2 --- /dev/null +++ b/admin/lib/xlog/catch.go @@ -0,0 +1,56 @@ +package xlog + +import ( + "runtime" + "time" +) + +func Catch() { + if v := recover(); v != nil { + backtrace(v) + } +} + +func CatchWithInfo(info string) { + if v := recover(); v != nil { + Critif("panic info: [%v]", info) + backtrace(v) + } +} + +func CatchWithInfoFun(info string, f func()) { + if v := recover(); v != nil { + Critif("panic info: [%v]", info) + backtrace(v) + f() + } +} + +func PrintCallerStack(info string) { + for i := 1; ; i++ { + pc, file, line, ok := runtime.Caller(i + 1) + if !ok { + break + } + //fmt.Fprintf(os.Stderr, "% 3d. %s() %s:%d\n", i, runtime.FuncForPC(pc).Name(), file, line) + Debugf("[%v] % 3d. %s() %s:%d\n", info, i, runtime.FuncForPC(pc).Name(), file, line) + } +} + +func backtrace(message interface{}) { + //fmt.Fprintf(os.Stderr, "Traceback[%s] (most recent call last):\n", time.Now()) + Critif("Traceback[%s] (most recent call last):\n", time.Now()) + for i := 0; ; i++ { + pc, file, line, ok := runtime.Caller(i + 1) + if !ok { + break + } + //fmt.Fprintf(os.Stderr, "% 3d. %s() %s:%d\n", i, runtime.FuncForPC(pc).Name(), file, line) + Critif("% 3d. %s() %s:%d\n", i, runtime.FuncForPC(pc).Name(), file, line) + } + //fmt.Fprintf(os.Stderr, "%v\n", message) + Critif("%v\n", message) + + // 等待写日志 + //time.Sleep(time.Second * 5) +} diff --git a/admin/lib/xlog/crosssyslog/syslog_darwin.go b/admin/lib/xlog/crosssyslog/syslog_darwin.go new file mode 100644 index 0000000..e0aecb2 --- /dev/null +++ b/admin/lib/xlog/crosssyslog/syslog_darwin.go @@ -0,0 +1,31 @@ +//go:build darwin +// +build darwin + +package crosssyslog + +import "log/syslog" + +type Syslog struct { + *syslog.Writer +} + +type Priority = syslog.Priority + +var ( + LOG_INFO = syslog.LOG_INFO + LOG_LOCAL0 = syslog.LOG_LOCAL0 +) + +func New(priority Priority, tag string) (*Syslog, error) { + SyslogLocal, syslogErr := syslog.New(syslog.LOG_NOTICE, tag) + if syslogErr != nil { + return nil, syslogErr + } + return &Syslog{ + Writer: SyslogLocal, + }, nil +} + +func (s *Syslog) Write(data []byte) (int, error) { + return s.Writer.Write(data) +} diff --git a/admin/lib/xlog/crosssyslog/syslog_linux.go b/admin/lib/xlog/crosssyslog/syslog_linux.go new file mode 100644 index 0000000..014deb1 --- /dev/null +++ b/admin/lib/xlog/crosssyslog/syslog_linux.go @@ -0,0 +1,31 @@ +//go:build linux +// +build linux + +package crosssyslog + +import "log/syslog" + +type Syslog struct { + *syslog.Writer +} + +type Priority = syslog.Priority + +var ( + LOG_INFO = syslog.LOG_INFO + LOG_LOCAL0 = syslog.LOG_LOCAL0 +) + +func New(priority Priority, tag string) (*Syslog, error) { + SyslogLocal, syslogErr := syslog.New(priority, tag) + if syslogErr != nil { + return nil, syslogErr + } + return &Syslog{ + Writer: SyslogLocal, + }, nil +} + +func (s *Syslog) Write(data []byte) (int, error) { + return s.Writer.Write(data) +} diff --git a/admin/lib/xlog/crosssyslog/syslog_windows.go b/admin/lib/xlog/crosssyslog/syslog_windows.go new file mode 100644 index 0000000..0553a37 --- /dev/null +++ b/admin/lib/xlog/crosssyslog/syslog_windows.go @@ -0,0 +1,21 @@ +// +build windows + +package crosssyslog + +type Syslog struct { +} + +type Priority int + +var ( + LOG_INFO Priority = 1 + LOG_LOCAL0 Priority = 2 +) + +func New(priority Priority, tag string) (*Syslog, error) { + return &Syslog{}, nil +} + +func (s *Syslog) Write(data []byte) (int, error) { + return len(data), nil +} diff --git a/admin/lib/xlog/handler.go b/admin/lib/xlog/handler.go new file mode 100644 index 0000000..4417f36 --- /dev/null +++ b/admin/lib/xlog/handler.go @@ -0,0 +1,8 @@ +package xlog + +// Handler writes logs to somewhere +type Handler interface { + //Rotate() + Write(p []byte) (n int, err error) + Close() error +} diff --git a/admin/lib/xlog/handler/handler_file.go b/admin/lib/xlog/handler/handler_file.go new file mode 100644 index 0000000..0efa43b --- /dev/null +++ b/admin/lib/xlog/handler/handler_file.go @@ -0,0 +1,58 @@ +package handler + +import ( + "os" + "path" + "sync" +) + +// FileHandler writes xlog to a file. +type FileHandler struct { + fileName string + flag int + fd *os.File + fdLock *sync.RWMutex +} + +func NewFileHandler(fileName string, flag int) (*FileHandler, error) { + dir := path.Dir(fileName) + os.Mkdir(dir, 0777) + + f, err := os.OpenFile(fileName, flag, 0666) + if err != nil { + return nil, err + } + + h := new(FileHandler) + + h.fileName = fileName + h.flag = flag + h.fd = f + h.fdLock = &sync.RWMutex{} + + return h, nil +} + +//func (h *FileHandler) Rotate() { +// fd, err := os.OpenFile(h.fileName, h.flag, 0666) +// if err != nil { +// h.fd.Write([]byte(fmt.Sprintf("interval check xlog file %v open error:%v", h.fileName, err))) +// return +// } +// +// h.fdLock.Lock() +// oldFd := h.fd +// h.fd = fd +// oldFd.Close() +// h.fdLock.Unlock() +//} + +func (h *FileHandler) Write(b []byte) (n int, err error) { + h.fdLock.RLock() + defer h.fdLock.RUnlock() + return h.fd.Write(b) +} + +func (h *FileHandler) Close() error { + return h.fd.Close() +} diff --git a/admin/lib/xlog/handler/handler_file_rotate.go b/admin/lib/xlog/handler/handler_file_rotate.go new file mode 100644 index 0000000..415f833 --- /dev/null +++ b/admin/lib/xlog/handler/handler_file_rotate.go @@ -0,0 +1,83 @@ +package handler + +import ( + "fmt" + "os" + "path" +) + +// RotatingFileHandler writes xlog a file, if file size exceeds maxBytes, +// it will backup current file and open a new one. +// +// max backup file number is set by backupCount, it will delete oldest if backups too many. +type RotatingFileHandler struct { + fd *os.File + + fileName string + maxBytes int + backupCount int +} + +func NewRotatingFileHandler(fileName string, maxBytes int, backupCount int) (*RotatingFileHandler, error) { + dir := path.Dir(fileName) + os.Mkdir(dir, 0777) + + h := new(RotatingFileHandler) + + if maxBytes <= 0 { + return nil, fmt.Errorf("invalid max bytes") + } + + h.fileName = fileName + h.maxBytes = maxBytes + h.backupCount = backupCount + + var err error + h.fd, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + return nil, err + } + + return h, nil +} + +func (h *RotatingFileHandler) Write(p []byte) (n int, err error) { + h.doRollover() + return h.fd.Write(p) +} + +func (h *RotatingFileHandler) Close() error { + if h.fd != nil { + return h.fd.Close() + } + return nil +} + +func (h *RotatingFileHandler) doRollover() { + f, err := h.fd.Stat() + if err != nil { + return + } + + if h.maxBytes <= 0 { + return + } else if f.Size() < int64(h.maxBytes) { + return + } + + if h.backupCount > 0 { + h.fd.Close() + + for i := h.backupCount - 1; i > 0; i-- { + sfn := fmt.Sprintf("%s.%d", h.fileName, i) + dfn := fmt.Sprintf("%s.%d", h.fileName, i+1) + + os.Rename(sfn, dfn) + } + + dfn := fmt.Sprintf("%s.1", h.fileName) + os.Rename(h.fileName, dfn) + + h.fd, _ = os.OpenFile(h.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + } +} diff --git a/admin/lib/xlog/handler/handler_file_rotate_atday.go b/admin/lib/xlog/handler/handler_file_rotate_atday.go new file mode 100644 index 0000000..d756750 --- /dev/null +++ b/admin/lib/xlog/handler/handler_file_rotate_atday.go @@ -0,0 +1,207 @@ +package handler + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + "time" +) + +const ( + kRollPerSeconds = 60 * 60 * 24 // one day +) + +var ( + pid = os.Getpid() + host = "unknowhost" +) + +func init() { + h, err := os.Hostname() + if err == nil { + host = shortHostname(h) + } +} + +func shortHostname(hostname string) string { + if i := strings.Index(hostname, "."); i >= 0 { + return hostname[:i] + } + return hostname +} + +func logFileName(basename string) string { + now := time.Now() + name := fmt.Sprintf("%s.%04d%02d%02d-%02d%02d%02d.%s.%d.xlog", + basename, + now.Year(), + now.Month(), + now.Day(), + now.Hour(), + now.Minute(), + now.Second(), + host, + pid, + ) + + return name +} + +type RotatingFileAtDayHandler struct { + baseName string + rollSize int + flushInterval int64 + checkEveryN int + syncFlush bool + count int + startofPeriod int64 + lastRoll int64 + lastFlush int64 + file *appendFile +} + +// baseName 日志文件的基本名包含全路径 如 "/tmp/test" +// rollSize 每写入rollSize字节日志滚动文件 +// flushInterval 刷新文件写入缓存的间隔 +// checkEveryN 每写入checkEveryN次 检查文件回滚和缓存刷新 +// syncFlush == true flushInterval和checkEveryN 失效 +func NewRotatingFileAtDayHandler(baseName string, rollSize int, + flushInterval int64, checkEveryN int, syncFlush bool) *RotatingFileAtDayHandler { + hander := &RotatingFileAtDayHandler{ + baseName: baseName, + rollSize: rollSize, + flushInterval: flushInterval, + checkEveryN: checkEveryN, + syncFlush: syncFlush, + count: 0, + startofPeriod: 0, + lastRoll: 0, + lastFlush: 0, + } + hander.rollFile() + return hander +} + +func NewDefaultRotatingFileAtDayHandler(baseName string, + rollSize int) *RotatingFileAtDayHandler { + //checkEveryN 开发期间默认设置为1 上线后调高已提高处理性能 + return NewRotatingFileAtDayHandler(baseName, rollSize, 3, 1, true) +} + +func (self *RotatingFileAtDayHandler) Write(b []byte) (int, error) { + n, err := self.file.append(b) + if err != nil { + return n, err + } + if self.file.writtenBytes() > self.rollSize { + self.rollFile() + } else { + self.count++ + if self.count >= self.checkEveryN || self.syncFlush { + self.count = 0 + now := time.Now().Unix() + thisPeriod := now / kRollPerSeconds * kRollPerSeconds + if thisPeriod != self.startofPeriod { + self.rollFile() + } else if now-self.lastFlush > self.flushInterval || self.syncFlush { + self.lastFlush = now + err = self.file.flush() + } + } + } + return n, err +} + +func (self *RotatingFileAtDayHandler) Rotate() { + +} + +func (self *RotatingFileAtDayHandler) flush() error { + return self.file.flush() +} + +func (self *RotatingFileAtDayHandler) rollFile() bool { + fileName := logFileName(self.baseName) + t := time.Now().Unix() + start := t / kRollPerSeconds * kRollPerSeconds + //滚动时间间隔最小为1秒 + if t > self.lastRoll { + self.lastRoll = t + self.lastFlush = t + self.startofPeriod = start + if self.file != nil { + self.file.close() + } + tmpFile, _ := newAppendFile(fileName) + self.file = tmpFile + return true + } + return false +} + +func (self *RotatingFileAtDayHandler) Close() error { + return self.file.close() +} + +type appendFile struct { + file *os.File + writer *bufio.Writer + writtenBytes_ int +} + +func newAppendFile(filename string) (*appendFile, error) { + file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) + if err != nil { + return nil, err + } + return &appendFile{ + file: file, + writer: bufio.NewWriterSize(file, 64*1024), + writtenBytes_: 0, + }, nil +} + +func (self *appendFile) append(b []byte) (int, error) { + length := len(b) + remain := length + offset := 0 + var err error + for remain > 0 { + x, err := self.writer.Write(b[offset:]) + if err != nil { + if err != io.ErrShortWrite { + break + } + } + + offset = offset + x + remain = length - offset + } + self.writtenBytes_ = self.writtenBytes_ + length + return offset, err +} + +func (self *appendFile) writtenBytes() int { + return self.writtenBytes_ +} +func (self *appendFile) flush() error { + return self.writer.Flush() +} + +func (self *appendFile) close() error { + err := self.writer.Flush() + for err != nil { + if err == io.ErrShortWrite { + err = self.writer.Flush() + } else { + break + } + } + if err != nil { + return err + } + + return self.file.Close() +} diff --git a/admin/lib/xlog/handler/handler_file_rotate_daymax.go b/admin/lib/xlog/handler/handler_file_rotate_daymax.go new file mode 100644 index 0000000..7249daa --- /dev/null +++ b/admin/lib/xlog/handler/handler_file_rotate_daymax.go @@ -0,0 +1,202 @@ +package handler + +import ( + "fmt" + "os" + "time" + + "admin/lib/xlog/handler/logsyscall" +) + +type RotatingDayMaxFileHandler struct { + baseName string + outPath string + fd *os.File + + rotateInfo struct { + maxBytes int // 单个日志文件最大长度 + maxBackupCount int // 最大归档文件数量 + day time.Time // 记录跨天 + } +} + +// NewRotatingDayMaxFileHandler 每天00点滚动,当超过大小也滚动 +func NewRotatingDayMaxFileHandler(outPath, baseName string, maxBytes int, backupCount int) (*RotatingDayMaxFileHandler, error) { + if outPath == "" { + outPath = "log/" + } + + h := new(RotatingDayMaxFileHandler) + h.baseName = baseName + h.outPath = outPath + h.rotateInfo.day = time.Now() + h.rotateInfo.maxBytes = maxBytes + h.rotateInfo.maxBackupCount = backupCount + fd, err := openFile(h.outPath, h.baseName, false) + if err != nil { + panic(err) + } else { + h.fd = fd + } + go h.rotateHandler() + return h, nil +} + +func (h *RotatingDayMaxFileHandler) Write(p []byte) (n int, err error) { + return h.fd.Write(p) +} + +func (h *RotatingDayMaxFileHandler) Close() error { + if h.fd != nil { + return h.fd.Close() + } + return nil +} + +func (h *RotatingDayMaxFileHandler) rotateHandler() { + for { + time.Sleep(time.Second) + h.tryRotate() + } +} + +func (h *RotatingDayMaxFileHandler) tryRotate() { + // 校验时间是否触发归档 + now := time.Now() + if now.Day() != h.rotateInfo.day.Day() { + h.rotateDay() + h.rotateInfo.day = now + return + } + + // 校验文件大小是否触发归档 + size, err := calcFileSize(h.fd) + if err != nil { + outErrorLog("stat xlog file(%v) error:%v/home/likun/work/idlerpg-server/internal/utils/xlog", h.baseName, err) + return + } + + if h.rotateInfo.maxBytes > 0 && h.rotateInfo.maxBytes <= size { + h.rotateSize() + return + } +} + +// rotateAt 到点触发归档 +func (h *RotatingDayMaxFileHandler) rotateDay() { + // 创建新的一天的日志文件 + newFd, err := openFile(h.outPath, h.baseName, false) + if err != nil { + outErrorLog("new day rotate file, but open new file(%v) error:%v", h.baseName, err) + return + } + + // 用新的一天的日志文件描述符接管当前使用的 + oldFd := h.fd + h.fd = newFd + oldFd.Close() +} + +// rotateSize 文件过大触发归档 +func (h *RotatingDayMaxFileHandler) rotateSize() { + // 锁定文件,使触发归档的别的进程也锁住 + lockFile(h.fd) + // 重新打开文件判断大小,防止文件被别的归档进程改名 + curSize := calcFileNameSize(h.fd.Name()) + if curSize < h.rotateInfo.maxBytes { + // 别的进程归档过了 + unlockFile(h.fd) + return + } + + // 滚动copy归档的文件,那么归档的1号文件空出来了 + baseFileName := h.fd.Name() + for i := h.rotateInfo.maxBackupCount; i > 0; i-- { + sfn := fmt.Sprintf("%s.%d", baseFileName, i) + dfn := fmt.Sprintf("%s.%d", baseFileName, i+1) + os.Rename(sfn, dfn) + } + + // 将当前文件内容拷贝到归档1号文件 + dfn := fmt.Sprintf("%s.1", baseFileName) + os.Rename(baseFileName, dfn) + + // 重新创建当前日志文件得到新的描述符 + newFd, err := os.OpenFile(baseFileName, os.O_CREATE|os.O_APPEND|os.O_RDWR|os.O_TRUNC, 0777) + if err != nil { + outErrorLog("rotate xlog file size, open file(%v) error:%v", baseFileName, err) + unlockFile(h.fd) + } else { + oldFd := h.fd + h.fd = newFd + unlockFile(h.fd) + oldFd.Close() + } +} + +func calcFileSize(fd *os.File) (int, error) { + st, err := fd.Stat() + return int(st.Size()), err +} + +func calcFileNameSize(fileName string) int { + fd, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0777) + if err != nil { + return 0 + } + defer fd.Close() + st, err := fd.Stat() + if err != nil { + return 0 + } + return int(st.Size()) +} + +var fileTimeStampFormat = "20060102" + +func openFile(path, baseName string, isTrunc bool) (*os.File, error) { + os.MkdirAll(path, 0777) + timeNow := time.Now() + t := timeNow.Format(fileTimeStampFormat) + fullFileName := baseName + "-" + t + ".log" + + if path != "" { + if path[len(path)-1] != '/' { + path += "/" + } + } + + fullPathFileName := path + fullFileName + + var fd *os.File + var err error + + flag := os.O_CREATE | os.O_APPEND | os.O_RDWR + if isTrunc { + flag |= os.O_TRUNC + } + + fd, err = os.OpenFile(fullPathFileName, flag, 0777) + if err != nil { + return fd, fmt.Errorf("open xlog file(%v) error:%v", fullPathFileName, err) + } + return fd, nil +} + +func lockFile(fd *os.File) { + err := logsyscall.Flock(int(fd.Fd()), logsyscall.LOCK_EX) + if err != nil { + outErrorLog("lock file(%v) error:%v", fd.Name(), err) + } +} + +func unlockFile(fd *os.File) { + err := logsyscall.Flock(int(fd.Fd()), logsyscall.LOCK_UN) + if err != nil { + outErrorLog("unlock file(%v) error:%v", fd.Name(), err) + } +} + +func outErrorLog(format string, values ...interface{}) { + fmt.Fprintf(os.Stderr, format+"\n", values...) +} diff --git a/admin/lib/xlog/handler/handler_file_rotate_timing.go b/admin/lib/xlog/handler/handler_file_rotate_timing.go new file mode 100644 index 0000000..9f13eff --- /dev/null +++ b/admin/lib/xlog/handler/handler_file_rotate_timing.go @@ -0,0 +1,96 @@ +package handler + +import ( + "fmt" + "os" + "path" + "time" +) + +// TimeRotatingFileHandler writes xlog to a file, +// it will backup current and open a new one, with a period time you sepecified. +// +// refer: http://docs.python.org/2/library/logging.handlers.html. +// same like python TimedRotatingFileHandler. +type TimeRotatingFileHandler struct { + fd *os.File + + baseName string + interval int64 + suffix string + rolloverAt int64 +} + +const ( + WhenSecond = iota + WhenMinute + WhenHour + WhenDay +) + +func NewTimeRotatingFileHandler(baseName string, when int8, interval int) (*TimeRotatingFileHandler, error) { + dir := path.Dir(baseName) + os.Mkdir(dir, 0777) + + h := new(TimeRotatingFileHandler) + + h.baseName = baseName + + switch when { + case WhenSecond: + h.interval = 1 + h.suffix = "2006-01-02_15-04-05" + case WhenMinute: + h.interval = 60 + h.suffix = "2006-01-02_15-04" + case WhenHour: + h.interval = 3600 + h.suffix = "2006-01-02_15" + case WhenDay: + h.interval = 3600 * 24 + h.suffix = "2006-01-02" + default: + return nil, fmt.Errorf("invalid when_rotate: %d", when) + } + + h.interval = h.interval * int64(interval) + + var err error + h.fd, err = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + return nil, err + } + + fInfo, _ := h.fd.Stat() + h.rolloverAt = fInfo.ModTime().Unix() + h.interval + + return h, nil +} + +func (h *TimeRotatingFileHandler) doRollover() { + //refer http://hg.python.org/cpython/file/2.7/Lib/logging/handlers.py + now := time.Now() + t := now.Unix() + + if h.rolloverAt <= t { + fName := h.baseName + now.Format(h.suffix) + h.fd.Close() + e := os.Rename(h.baseName, fName) + if e != nil { + panic(e) + } + + h.fd, _ = os.OpenFile(h.baseName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + + h.rolloverAt = t + h.interval + } +} + +func (h *TimeRotatingFileHandler) Write(b []byte) (n int, err error) { + h.doRollover() + return h.fd.Write(b) +} + +func (h *TimeRotatingFileHandler) Close() error { + return h.fd.Close() +} diff --git a/admin/lib/xlog/handler/handler_null.go b/admin/lib/xlog/handler/handler_null.go new file mode 100644 index 0000000..c6c60ea --- /dev/null +++ b/admin/lib/xlog/handler/handler_null.go @@ -0,0 +1,17 @@ +package handler + +// NullHandler does nothing, it discards anything. +type NullHandler struct { +} + +func NewNullHandler() (*NullHandler, error) { + return new(NullHandler), nil +} + +func (h *NullHandler) Write(b []byte) (n int, err error) { + return len(b), nil +} + +func (h *NullHandler) Close() { + +} diff --git a/admin/lib/xlog/handler/handler_socket.go b/admin/lib/xlog/handler/handler_socket.go new file mode 100644 index 0000000..6dcd7d3 --- /dev/null +++ b/admin/lib/xlog/handler/handler_socket.go @@ -0,0 +1,65 @@ +package handler + +import ( + "encoding/binary" + "net" + "time" +) + +// SocketHandler writes xlog to a connectionl. +// Network protocol is simple: xlog length + xlog | xlog length + xlog. xlog length is uint32, bigendian. +// you must implement your own xlog server, maybe you can use logd instead simply. +type SocketHandler struct { + c net.Conn + protocol string + addr string +} + +func NewSocketHandler(protocol string, addr string) (*SocketHandler, error) { + s := new(SocketHandler) + + s.protocol = protocol + s.addr = addr + + return s, nil +} + +func (h *SocketHandler) Write(p []byte) (n int, err error) { + if err = h.connect(); err != nil { + return + } + + buf := make([]byte, len(p)+4) + + binary.BigEndian.PutUint32(buf, uint32(len(p))) + + copy(buf[4:], p) + + n, err = h.c.Write(buf) + if err != nil { + h.c.Close() + h.c = nil + } + return +} + +func (h *SocketHandler) Close() error { + if h.c != nil { + h.c.Close() + } + return nil +} + +func (h *SocketHandler) connect() error { + if h.c != nil { + return nil + } + + var err error + h.c, err = net.DialTimeout(h.protocol, h.addr, 20*time.Second) + if err != nil { + return err + } + + return nil +} diff --git a/admin/lib/xlog/handler/handler_stream.go b/admin/lib/xlog/handler/handler_stream.go new file mode 100644 index 0000000..a44251a --- /dev/null +++ b/admin/lib/xlog/handler/handler_stream.go @@ -0,0 +1,28 @@ +package handler + +import "io" + +// StreamHandler writes logs to a specified io Writer, maybe stdout, stderr, etc... +type StreamHandler struct { + w io.Writer +} + +func NewStreamHandler(w io.Writer) (*StreamHandler, error) { + h := new(StreamHandler) + + h.w = w + + return h, nil +} + +func (h *StreamHandler) Rotate() { + +} + +func (h *StreamHandler) Write(b []byte) (n int, err error) { + return h.w.Write(b) +} + +func (h *StreamHandler) Close() error { + return nil +} diff --git a/admin/lib/xlog/handler/handler_test.go b/admin/lib/xlog/handler/handler_test.go new file mode 100644 index 0000000..5ee6c7c --- /dev/null +++ b/admin/lib/xlog/handler/handler_test.go @@ -0,0 +1,62 @@ +package handler + +import ( + "strconv" + "sync" + "testing" + "time" +) + +func testHandler(goroutineNo int) { + h, err := NewRotatingDayMaxFileHandler("test_log", "shop", 40960, 1000) + if err != nil { + panic(err) + } + + wg := new(sync.WaitGroup) + wg.Add(3) + go func(no int) { + for i := 0; i < 10000; i++ { + h.Write([]byte("test content:" + strconv.Itoa(goroutineNo) + ":" + strconv.Itoa(no) + ":" + strconv.Itoa(i) + "\n")) + time.Sleep(time.Millisecond * 2) + } + wg.Done() + }(1) + + go func(no int) { + for i := 0; i < 10000; i++ { + h.Write([]byte("test content:" + strconv.Itoa(goroutineNo) + ":" + strconv.Itoa(no) + ":" + strconv.Itoa(i) + "\n")) + time.Sleep(time.Millisecond * 2) + } + wg.Done() + }(2) + + go func(no int) { + for i := 0; i < 10000; i++ { + h.Write([]byte("test content:" + strconv.Itoa(goroutineNo) + ":" + strconv.Itoa(no) + ":" + strconv.Itoa(i) + "\n")) + time.Sleep(time.Millisecond * 2) + } + wg.Done() + }(3) + + wg.Wait() + h.Write([]byte("tail\n")) + h.Close() +} + +func TestHandler(t *testing.T) { + wg := new(sync.WaitGroup) + num := 5 + wg.Add(5) + + // 并发创建几个协程,模拟多进程并发写归档 + // grep "test content" test_log/*|sed 's/^.*test content:\(.*\):\(.*\):\(.*\)$/\3/g'|sort|uniq -c|awk -F' ' '{print $1}'|uniq -ct pull + for i := 0; i < num; i++ { + go func(no int) { + testHandler(no) + wg.Done() + }(i) + } + + wg.Wait() +} diff --git a/admin/lib/xlog/handler/logsyscall/jlog_syscall_darwin.go b/admin/lib/xlog/handler/logsyscall/jlog_syscall_darwin.go new file mode 100644 index 0000000..2dacb56 --- /dev/null +++ b/admin/lib/xlog/handler/logsyscall/jlog_syscall_darwin.go @@ -0,0 +1,14 @@ +//go:build darwin +// +build darwin + +package logsyscall + +const ( + LOCK_EX int = 2 + LOCK_UN int = 8 +) + +// Flock +func Flock(fd int, how int) (err error) { + return nil +} diff --git a/admin/lib/xlog/handler/logsyscall/jlog_syscall_linux.go b/admin/lib/xlog/handler/logsyscall/jlog_syscall_linux.go new file mode 100644 index 0000000..478f7fa --- /dev/null +++ b/admin/lib/xlog/handler/logsyscall/jlog_syscall_linux.go @@ -0,0 +1,16 @@ +//go:build linux +// +build linux + +package logsyscall + +import "syscall" + +const ( + LOCK_EX int = 2 + LOCK_UN int = 8 +) + +// Flock +func Flock(fd int, how int) (err error) { + return syscall.Flock(fd, how) +} diff --git a/admin/lib/xlog/handler/logsyscall/jlog_syscall_windows.go b/admin/lib/xlog/handler/logsyscall/jlog_syscall_windows.go new file mode 100644 index 0000000..5e7cf78 --- /dev/null +++ b/admin/lib/xlog/handler/logsyscall/jlog_syscall_windows.go @@ -0,0 +1,14 @@ +//go:build windows +// +build windows + +package logsyscall + +const ( + LOCK_EX int = 2 + LOCK_UN int = 8 +) + +// Flock +func Flock(fd int, how int) (err error) { + return nil +} diff --git a/admin/lib/xlog/log.go b/admin/lib/xlog/log.go new file mode 100644 index 0000000..3458a2e --- /dev/null +++ b/admin/lib/xlog/log.go @@ -0,0 +1,255 @@ +package xlog + +import ( + "fmt" + "io" + "os" + "strconv" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +type LogLevel = zerolog.Level +type LogEvent = zerolog.Event + +var ( + LogLevelTrace = zerolog.TraceLevel // 追踪日志,调试完毕需要删除 + LogLevelDebug = zerolog.DebugLevel // 调试日志,可以放在代码中,线上出问题调成这个级别 + LogLevelInfo = zerolog.InfoLevel // 正常关键逻辑记录信息,线上日常设置为这个级别 + LogLevelNotice = zerolog.Level(99) // 系统关键节点时输出的留意日志 + LogLevelWarn = zerolog.WarnLevel // 警告,某些逻辑出现意向不到的情况,输出告警,例如配置表错误、rpc错误 + LogLevelError = zerolog.ErrorLevel // 错误,服务器重要组件出现意向不到的情况,输出错误,例如数据库、redis错误 + LogLevelCriti = zerolog.Level(100) // 危急,用于需要开发注意的信息,例如崩溃但不影响服务器运行的栈日志,一般接上sms、im告警 + LogLevelFatal = zerolog.FatalLevel // 致命,核心组建出问题,无法运行,输出告警,并以1的错误码退出 + LogLevelPanic = zerolog.PanicLevel // 崩溃,核心组建出问题,无法运行,崩溃退出 +) + +var LogLevelStr2Enum = map[string]LogLevel{ + "trace": LogLevelTrace, + "debug": LogLevelDebug, + "info": LogLevelInfo, + "notice": LogLevelNotice, + "warn": LogLevelWarn, + "error": LogLevelError, + "criti": LogLevelCriti, + "fatal": LogLevelFatal, + "panic": LogLevelPanic, +} + +func init() { + // 设置时间格式 + zerolog.TimeFieldFormat = "06/01/02 15:04:05.000" + // 修改level字段key,防止跟调用方的key一样 + zerolog.LevelFieldName = "log_level" + // 修改时间key + zerolog.TimestampFieldName = "log_time" + // 设置调用文件名路径深度 + zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string { + depth := 1 + for i := len(file) - 1; i > 0; i-- { + if file[i] == '/' { + if depth == FileReversedDepth { + file = file[i+1:] + break + } + depth++ + } + } + return file + ":" + strconv.Itoa(line) + } + // 设置全局日志等级 + zerolog.SetGlobalLevel(LogLevelTrace) +} + +// 文件路径保留深度 +var FileReversedDepth = 3 + +func NewGlobalLogger(writers []io.Writer, level LogLevel, initFun func(logger zerolog.Logger) zerolog.Logger) { + // 设置全局日志等级 + zerolog.SetGlobalLevel(level) + var parentLogger zerolog.Logger + + if len(writers) == 0 { + fmt.Fprintf(os.Stderr, "NewGlobalLogger but write is nil, default give os.Stdout\n") + writers = append(writers, os.Stdout) + } + + multi := zerolog.MultiLevelWriter(writers...) + + // 创建全局日志 + parentLogger = zerolog.New(multi).With().Logger() + + if initFun != nil { + log.Logger = initFun(parentLogger) + } else { + log.Logger = parentLogger + } + + log.Logger = log.Hook(new(PrefixHook)) + globalWriter = multi +} + +func NewCustomLogger(writer Handler, initFun func(logger *zerolog.Logger) *zerolog.Logger) *zerolog.Logger { + if writer == nil { + fmt.Fprintf(os.Stderr, "NewGlobalLogger but write is nil, default give os.Stdout\n") + writer = os.Stdout + } + parentLogger := zerolog.New(writer).With().Logger() + initFun(&parentLogger) + return &parentLogger +} + +var globalWriter io.Writer + +func GetGlobalWriter() io.Writer { + if globalWriter == nil { + return os.Stdout + } + return globalWriter +} + +// GetSubLogger 获取全局logger的子logger,可以设置子logger的输出格式 +func GetSubLogger() zerolog.Context { + return log.Logger.With() +} + +func GetLogLevel() LogLevel { + return zerolog.GlobalLevel() +} + +func SetLogLevel(level LogLevel) { + zerolog.SetGlobalLevel(level) +} + +func ParseLogLevelString(level string) LogLevel { + lvEnum, find := LogLevelStr2Enum[level] + if !find { + return LogLevelDebug + } + return lvEnum +} + +// transFormat +func transFormat(v ...interface{}) (string, []interface{}) { + if len(v) == 0 { + return "empty content", v + } else { + formatStr, ok := v[0].(string) + if ok { + return formatStr, v[1:] + } + formatStr = fmt.Sprint(v...) + return formatStr, []interface{}{} + } +} + +func Tracef(v ...interface{}) { + format, v := transFormat(v...) + output(LogLevelTrace, format, v...) +} + +func Debugf(v ...interface{}) { + format, v := transFormat(v...) + output(LogLevelDebug, format, v...) +} + +func Infof(v ...interface{}) { + format, v := transFormat(v...) + output(LogLevelInfo, format, v...) +} + +func Noticef(v ...interface{}) { + format, v := transFormat(v...) + output(LogLevelNotice, format, v...) +} + +func Warnf(v ...interface{}) { + format, v := transFormat(v...) + output(LogLevelWarn, format, v...) +} + +func Errorf(v ...interface{}) { + format, v := transFormat(v...) + output(LogLevelError, format, v...) +} + +func Critif(v ...interface{}) { + format, v := transFormat(v...) + output(LogLevelCriti, format, v...) +} + +func Fatalf(v ...interface{}) { + format, v := transFormat(v...) + output(LogLevelFatal, format, v...) +} + +func WithLevelEvent(level LogLevel) *LogEvent { + e := log.WithLevel(level) + return e +} + +func WithEventMsgF(e *LogEvent, v ...interface{}) { + format, v := transFormat(v...) + e.Timestamp().Caller(2).Msgf(format, v...) +} + +func output(level LogLevel, format string, v ...interface{}) { + var e *zerolog.Event + switch level { + case LogLevelTrace: + e = log.Trace() + case LogLevelDebug: + e = log.Debug() + case LogLevelInfo: + e = log.Info() + case LogLevelNotice: + e = log.WithLevel(zerolog.NoLevel) + e.Str("log_level", "notice") + case LogLevelWarn: + e = log.Warn() + case LogLevelError: + e = log.Error() + case LogLevelCriti: + e = log.WithLevel(zerolog.NoLevel) + e.Str("log_level", "criti") + case LogLevelFatal: + e = log.Fatal() + case LogLevelPanic: + e = log.Panic() + default: + return + } + + e.Timestamp().Caller(2).Msgf(format, v...) +} + +func Output(level LogLevel) *zerolog.Event { + var e *zerolog.Event + switch level { + case LogLevelTrace: + e = log.Trace() + case LogLevelDebug: + e = log.Debug() + case LogLevelInfo: + e = log.Info() + case LogLevelNotice: + e = log.WithLevel(zerolog.NoLevel) + e.Str("log_level", "notice") + case LogLevelWarn: + e = log.Warn() + case LogLevelError: + e = log.Error() + case LogLevelCriti: + e = log.WithLevel(zerolog.NoLevel) + e.Str("log_level", "criti") + case LogLevelFatal: + e = log.Fatal() + case LogLevelPanic: + e = log.Panic() + default: + return nil + } + + return e +} diff --git a/admin/lib/xlog/log_hook.go b/admin/lib/xlog/log_hook.go new file mode 100644 index 0000000..8ce4501 --- /dev/null +++ b/admin/lib/xlog/log_hook.go @@ -0,0 +1,15 @@ +package xlog + +import ( + "github.com/rs/zerolog" +) + +type PrefixHook struct{} + +func (h *PrefixHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { + //e.Str("log_level", level.String()) + return + if level != zerolog.NoLevel { + e.Str("severity", level.String()) + } +} diff --git a/admin/lib/xos/signal.go b/admin/lib/xos/signal.go new file mode 100644 index 0000000..0fb05d9 --- /dev/null +++ b/admin/lib/xos/signal.go @@ -0,0 +1,30 @@ +package xos + +import ( + "os" + "os/signal" + "syscall" +) + +func WatchSignal(notify func(signal2 os.Signal)) { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) + for { + s := <-c + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL: + notify(s) + return + case syscall.SIGHUP: + // TODO reload + default: + return + } + } +} + +func WatchSignal1() chan os.Signal { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) + return c +} diff --git a/ui/.env.development b/ui/.env.development new file mode 100644 index 0000000..ff54252 --- /dev/null +++ b/ui/.env.development @@ -0,0 +1,2 @@ +VITE_APP_BASE_API = '/api' +VITE_APP_BASE_URL = 'http://192.168.78.128:8080/api' \ No newline at end of file diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/ui/.vscode/extensions.json b/ui/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/ui/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 0000000..82d7ccd --- /dev/null +++ b/ui/README.md @@ -0,0 +1,29 @@ +# ui + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Compile and Minify for Production + +```sh +npm run build +``` diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..b19040a --- /dev/null +++ b/ui/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/ui/jsconfig.json b/ui/jsconfig.json new file mode 100644 index 0000000..5a1f2d2 --- /dev/null +++ b/ui/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/ui/package-lock.json b/ui/package-lock.json new file mode 100644 index 0000000..2ade7bd --- /dev/null +++ b/ui/package-lock.json @@ -0,0 +1,3862 @@ +{ + "name": "ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ui", + "version": "0.0.0", + "dependencies": { + "axios": "^1.8.4", + "element-plus": "^2.9.7", + "pinia": "^3.0.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.3", + "unplugin-auto-import": "^19.1.2", + "unplugin-vue-components": "^28.5.0", + "vite": "^6.2.4", + "vite-plugin-vue-devtools": "^7.7.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.27.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", + "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-decorators": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", + "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", + "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.27.0", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.16", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", + "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz", + "integrity": "sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.4.0.tgz", + "integrity": "sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "@vue/babel-helper-vue-transform-on": "1.4.0", + "@vue/babel-plugin-resolve-type": "1.4.0", + "@vue/shared": "^3.5.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.4.0.tgz", + "integrity": "sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/parser": "^7.26.9", + "@vue/compiler-sfc": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.5.tgz", + "integrity": "sha512-HYV3tJGARROq5nlVMJh5KKHk7GU8Au3IrrmNNqr978m0edxgpHgYPDoNUGrvEgIbObz09SQezFR3A1EVmB5WZg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.5" + } + }, + "node_modules/@vue/devtools-core": { + "version": "7.7.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-core/-/devtools-core-7.7.5.tgz", + "integrity": "sha512-ElKr0NDor57gVaT+gMQ8kcVP4uFGqHcxuuQndW/rPwh6aHWvEcUL3sxL8cEk+e1Rdt28kS88erpsiIMO6hEENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.5", + "@vue/devtools-shared": "^7.7.5", + "mitt": "^3.0.1", + "nanoid": "^5.1.0", + "pathe": "^2.0.3", + "vite-hot-client": "^2.0.4" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.5.tgz", + "integrity": "sha512-S9VAVJYVAe4RPx2JZb9ZTEi0lqTySz2CBeF0wHT5D3dkTLnT9yMMGegKNl4b2EIELwLSkcI9bl2qp0/jW+upqA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.5", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.5.tgz", + "integrity": "sha512-QBjG72RfpM0DKtpns2RZOxBltO226kOAls9e4Lri6YxS2gWTgL0H+wj1R2K76lxxIeOrqo4+2Ty6RQnzv+WSTQ==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/birpc": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.3.0.tgz", + "integrity": "sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001714", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001714.tgz", + "integrity": "sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.137", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.137.tgz", + "integrity": "sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==", + "dev": true, + "license": "ISC" + }, + "node_modules/element-plus": { + "version": "2.9.7", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.7.tgz", + "integrity": "sha512-6vjZh5SXBncLhUwJGTVKS5oDljfgGMh6J4zVTeAZK3YdMUN76FgpvHkwwFXocpJpMbii6rDYU3sgie64FyPerQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.13", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "0.1.5", + "resolved": "https://registry.npmmirror.com/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", + "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/execa": { + "version": "9.5.2", + "resolved": "https://registry.npmmirror.com/execa/-/execa-9.5.2.tgz", + "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exsolve": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.4.tgz", + "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz", + "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.1", + "resolved": "https://registry.npmmirror.com/open/-/open-10.1.1.tgz", + "integrity": "sha512-zy1wx4+P3PfhXSEPJNtZmJXfhkkIaxU1VauWIrDZw1O7uJRDRJtKr9n3Ic4NgbA16KyOxOXO2ng9gYwCdXuSXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.2.tgz", + "integrity": "sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.1.0.tgz", + "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.1", + "exsolve": "^1.0.1", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmmirror.com/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/quansync": { + "version": "0.2.10", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz", + "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-4.2.0.tgz", + "integrity": "sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.1", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "pkg-types": "^2.1.0", + "scule": "^1.3.0", + "strip-literal": "^3.0.0", + "tinyglobby": "^0.2.12", + "unplugin": "^2.2.2", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.2.tgz", + "integrity": "sha512-3n7YA46rROb3zSj8fFxtxC/PqoyvYQ0llwz9wtUPUutr9ig09C8gGo5CWCwHrUzlqC1LLR43kxp5vEIyH1ac1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.1", + "picomatch": "^4.0.2", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "19.1.2", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-19.1.2.tgz", + "integrity": "sha512-EkxNIJm4ZPYtV7rRaPBKnsscgTaifIZNrJF5DkMffTxkUOJOlJuKVypA6YBSBOjzPJDTFPjfVmCQPoBuOO+YYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "picomatch": "^4.0.2", + "unimport": "^4.1.2", + "unplugin": "^2.2.2", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.2.4.tgz", + "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-components": { + "version": "28.5.0", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-28.5.0.tgz", + "integrity": "sha512-o7fMKU/uI8NiP+E0W62zoduuguWqB0obTfHFtbr1AP2uo2lhUPnPttWUE92yesdiYfo9/0hxIrj38FMc1eaySg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "debug": "^4.4.0", + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "tinyglobby": "^0.2.12", + "unplugin": "^2.3.2", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.0.tgz", + "integrity": "sha512-9aC0n4pr6hIbvi1YOpFjwQ+QOTGssvbJKoeYkuHHGWwlXfdxQlI8L2qNMo9awEEcCPSiS+5mJZk5jH1PAqoDeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.12" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-hot-client": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/vite-hot-client/-/vite-hot-client-2.0.4.tgz", + "integrity": "sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "0.8.9", + "resolved": "https://registry.npmmirror.com/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz", + "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "debug": "^4.3.7", + "error-stack-parser-es": "^0.1.5", + "fs-extra": "^11.2.0", + "open": "^10.1.0", + "perfect-debounce": "^1.0.0", + "picocolors": "^1.1.1", + "sirv": "^3.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "7.7.5", + "resolved": "https://registry.npmmirror.com/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.5.tgz", + "integrity": "sha512-cSlQYI1E+8d0qubBg70suTBbXMFbTHLn7vLPYUPK9GjNNJ0nw+Yks0ZLOAp7/+PjmqSpN5fK1taor6HeAjKb1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^7.7.5", + "@vue/devtools-kit": "^7.7.5", + "@vue/devtools-shared": "^7.7.5", + "execa": "^9.5.2", + "sirv": "^3.0.1", + "vite-plugin-inspect": "0.8.9", + "vite-plugin-vue-inspector": "^5.3.1" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz", + "integrity": "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.0.tgz", + "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..6e86fd3 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,25 @@ +{ + "name": "ui", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.8.4", + "element-plus": "^2.9.7", + "pinia": "^3.0.1", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.3", + "unplugin-auto-import": "^19.1.2", + "unplugin-vue-components": "^28.5.0", + "vite": "^6.2.4", + "vite-plugin-vue-devtools": "^7.7.2" + } +} diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/ui/public/favicon.ico differ diff --git a/ui/src/App.vue b/ui/src/App.vue new file mode 100644 index 0000000..194c9c9 --- /dev/null +++ b/ui/src/App.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/ui/src/api/project/project.js b/ui/src/api/project/project.js new file mode 100644 index 0000000..09061f1 --- /dev/null +++ b/ui/src/api/project/project.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getProjectList(params) { + return request({ + url: '/project', + method: 'get', + params: params + }) +} \ No newline at end of file diff --git a/ui/src/assets/base.css b/ui/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/ui/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/ui/src/assets/icon/header.png b/ui/src/assets/icon/header.png new file mode 100644 index 0000000..7d9e717 Binary files /dev/null and b/ui/src/assets/icon/header.png differ diff --git a/ui/src/assets/logo.svg b/ui/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/ui/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/ui/src/assets/main.css b/ui/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/ui/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/ui/src/components/restful/table.vue b/ui/src/components/restful/table.vue new file mode 100644 index 0000000..9d662bd --- /dev/null +++ b/ui/src/components/restful/table.vue @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/ui/src/main.js b/ui/src/main.js new file mode 100644 index 0000000..f38afea --- /dev/null +++ b/ui/src/main.js @@ -0,0 +1,16 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(ElementPlus) +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/ui/src/router/index.js b/ui/src/router/index.js new file mode 100644 index 0000000..6eac7d3 --- /dev/null +++ b/ui/src/router/index.js @@ -0,0 +1,27 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Home from '../views/Home.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: Home, + children: [ + { + path: '/user', + name: 'user', + component: import('@/views/user/user.vue') + }, + { + path: '/project', + name: 'project', + component: import('@/views/project/project.vue') + } + ] + }, + ], +}) + +export default router diff --git a/ui/src/stores/counter.js b/ui/src/stores/counter.js new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/ui/src/stores/counter.js @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/ui/src/utils/request.js b/ui/src/utils/request.js new file mode 100644 index 0000000..fc500bb --- /dev/null +++ b/ui/src/utils/request.js @@ -0,0 +1,68 @@ +import axios from 'axios' +import {ElMessageBox} from "element-plus"; +// import LocalCache from "@/stores/localCache"; +import {useRouter} from "vue-router"; +// import ExpireCache from "@/stores/expireCache"; + +// 创建axios实例 +const service=axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + timeout: 10000, + headers: { + "Content-type": "application/json;charset=utf-8", + "Cache-Control": 'no-cache', + } +}) + +// axios.post('192.168.1.60:8888/login', {username: "likun", password: "likun1"}).then((res)=>{ +// console.log(res) +// }).catch((err)=>{ +// console.log('login error') +// console.log(err) +// }) + +const reqInterceptor = (config)=>{ + config.headers=config.headers || {} + // const token = ExpireCache.getCache('token') + // if(token){ + // config.headers["x-token"]=token || "" + // } else { + // console.log("not found local storage token") + // } + return config +} + +const resInterceptor = (res)=>{ + console.log("res:", res.data) + const code=res.data.code + if(code!=200) { + console.log("interceptor err code", res) + ElMessageBox.alert("请求服务器成功,但是逻辑错误:" + res.data.message, "服务器错误码" + code, { + type: "warning", + confirmButtonText: '知道了', + }) + return Promise.reject(res.data) + } + + return res.data +} + +const resErrorInterceptor = (err)=>{ + console.log(err) + const code = err.response && err.response.status || -1 + const message = err.response && err.response.data.message || err + ElMessageBox.alert(message, "请求服务器返回http错误码-" + code, { + type: "error", + confirmButtonText: '知道了', + }) + return Promise.reject(err) +} + +// 请求拦截 +service.interceptors.request.use(reqInterceptor) +// 响应拦截 +service.interceptors.response.use(resInterceptor, resErrorInterceptor) + + +export default service + diff --git a/ui/src/views/Home.vue b/ui/src/views/Home.vue new file mode 100644 index 0000000..3adecc4 --- /dev/null +++ b/ui/src/views/Home.vue @@ -0,0 +1,43 @@ + + + diff --git a/ui/src/views/project/project.vue b/ui/src/views/project/project.vue new file mode 100644 index 0000000..b3317f8 --- /dev/null +++ b/ui/src/views/project/project.vue @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/ui/src/views/user/user.vue b/ui/src/views/user/user.vue new file mode 100644 index 0000000..9415308 --- /dev/null +++ b/ui/src/views/user/user.vue @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/ui/vite.config.js b/ui/vite.config.js new file mode 100644 index 0000000..db5dca4 --- /dev/null +++ b/ui/vite.config.js @@ -0,0 +1,58 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import { loadEnv } from 'vite' + +// https://vite.dev/config/ +export default defineConfig(({ mode, command }) => { + const env = loadEnv(mode, process.cwd()) + const { VITE_APP_ENV, VITE_APP_BASE_URL,VITE_APP_BASE_URL_WS} = env + return { + plugins: [ + vue(), + vueDevTools(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, + + // vite 相关配置 + server: { + port: 5173, + host: true, + open: false, + + + proxy: { + // https://cn.vitejs.dev/config/#server-proxy + '/api': { + target: VITE_APP_BASE_URL, + changeOrigin: true, + rewrite: (p) => p.replace(/^\/api/, ''), + }, + + // '/dev-ws': { + // target: VITE_APP_BASE_URL_WS, + // changeOrigin: true, + // rewrite: (p) => p.replace(/^\/dev-ws/, ''), + // ws: true + // } + + } + }, + } +})