first commit
This commit is contained in:
commit
c882f04529
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.idea
|
26
admin/apps/game/boot.go
Normal file
26
admin/apps/game/boot.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
94
admin/apps/game/domain/entity/project.go
Normal file
94
admin/apps/game/domain/entity/project.go
Normal file
@ -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
|
||||
}
|
94
admin/apps/game/domain/entity/server.go
Normal file
94
admin/apps/game/domain/entity/server.go
Normal file
@ -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
|
||||
}
|
31
admin/apps/game/domain/irestfull.go
Normal file
31
admin/apps/game/domain/irestfull.go
Normal file
@ -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
|
||||
}
|
48
admin/apps/game/domain/project.go
Normal file
48
admin/apps/game/domain/project.go
Normal file
@ -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)
|
||||
}
|
77
admin/apps/game/domain/repo/project.go
Normal file
77
admin/apps/game/domain/repo/project.go
Normal file
@ -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
|
||||
}
|
69
admin/apps/game/domain/repo/server.go
Normal file
69
admin/apps/game/domain/repo/server.go
Normal file
@ -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
|
||||
}
|
48
admin/apps/game/domain/server.go
Normal file
48
admin/apps/game/domain/server.go
Normal file
@ -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)
|
||||
}
|
35
admin/apps/game/model/dto/common.go
Normal file
35
admin/apps/game/model/dto/common.go
Normal file
@ -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"` // 数据行
|
||||
}
|
18
admin/apps/game/model/dto/request.go
Normal file
18
admin/apps/game/model/dto/request.go
Normal file
@ -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"`
|
||||
}
|
3
admin/apps/game/model/dto/response.go
Normal file
3
admin/apps/game/model/dto/response.go
Normal file
@ -0,0 +1,3 @@
|
||||
package dto
|
||||
|
||||
type CommonListRsp = CommonDtoList
|
28
admin/apps/game/model/project.go
Normal file
28
admin/apps/game/model/project.go
Normal file
@ -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"`
|
||||
}
|
28
admin/apps/game/model/server.go
Normal file
28
admin/apps/game/model/server.go
Normal file
@ -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"`
|
||||
}
|
11
admin/apps/game/server/ctl.go
Normal file
11
admin/apps/game/server/ctl.go
Normal file
@ -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}
|
||||
}
|
42
admin/apps/game/server/ctl_common_rest.go
Normal file
42
admin/apps/game/server/ctl_common_rest.go
Normal file
@ -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)
|
||||
}
|
57
admin/apps/game/server/route.go
Normal file
57
admin/apps/game/server/route.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
15
admin/apps/game/server/server.go
Normal file
15
admin/apps/game/server/server.go
Normal file
@ -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),
|
||||
}
|
||||
}
|
68
admin/apps/game/service/service.go
Normal file
68
admin/apps/game/service/service.go
Normal file
@ -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)
|
||||
}
|
18
admin/apps/user/boot.go
Normal file
18
admin/apps/user/boot.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
26
admin/cmd/all_in_one/main.go
Normal file
26
admin/cmd/all_in_one/main.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
53
admin/go.mod
Normal file
53
admin/go.mod
Normal file
@ -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
|
||||
)
|
139
admin/go.sum
Normal file
139
admin/go.sum
Normal file
@ -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=
|
10
admin/internal/config/flags.go
Normal file
10
admin/internal/config/flags.go
Normal file
@ -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:"数据库密码"`
|
||||
}
|
1
admin/internal/config/yaml.go
Normal file
1
admin/internal/config/yaml.go
Normal file
@ -0,0 +1 @@
|
||||
package config
|
43
admin/internal/context/ctx_web.go
Normal file
43
admin/internal/context/ctx_web.go
Normal file
@ -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
|
||||
}
|
142
admin/internal/db/db.go
Normal file
142
admin/internal/db/db.go
Normal file
@ -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
|
||||
}
|
99
admin/internal/db/logger.go
Normal file
99
admin/internal/db/logger.go
Normal file
@ -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
|
||||
}
|
7
admin/internal/errcode/code.go
Normal file
7
admin/internal/errcode/code.go
Normal file
@ -0,0 +1,7 @@
|
||||
package errcode
|
||||
|
||||
const (
|
||||
Ok = 0
|
||||
ServerError = 1 // 服务器错误
|
||||
DBError = 2 // 数据库错误
|
||||
)
|
53
admin/internal/errcode/error.go
Normal file
53
admin/internal/errcode/error.go
Normal file
@ -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
|
||||
}
|
13
admin/internal/global/var.go
Normal file
13
admin/internal/global/var.go
Normal file
@ -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服务器
|
||||
)
|
38
admin/internal/mynode/node.go
Normal file
38
admin/internal/mynode/node.go
Normal file
@ -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
|
||||
}
|
76
admin/lib/flags/flags.go
Normal file
76
admin/lib/flags/flags.go
Normal file
@ -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
|
||||
}
|
216
admin/lib/node/app.go
Normal file
216
admin/lib/node/app.go
Normal file
@ -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()
|
||||
// }
|
||||
//}
|
22
admin/lib/node/app_desc.go
Normal file
22
admin/lib/node/app_desc.go
Normal file
@ -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
|
||||
}
|
31
admin/lib/node/app_options.go
Normal file
31
admin/lib/node/app_options.go
Normal file
@ -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)
|
||||
}
|
209
admin/lib/node/node.go
Normal file
209
admin/lib/node/node.go
Normal file
@ -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
|
||||
}
|
10
admin/lib/node/node_flags.go
Normal file
10
admin/lib/node/node_flags.go
Normal file
@ -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\""`
|
||||
}
|
201
admin/lib/node/node_init.go
Normal file
201
admin/lib/node/node_init.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
37
admin/lib/node/node_log.go
Normal file
37
admin/lib/node/node_log.go
Normal file
@ -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
|
||||
}
|
46
admin/lib/node/node_options.go
Normal file
46
admin/lib/node/node_options.go
Normal file
@ -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)
|
||||
}
|
50
admin/lib/prom/example_test.go
Normal file
50
admin/lib/prom/example_test.go
Normal file
@ -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 {}
|
||||
}
|
65
admin/lib/prom/pprof/prof.go
Normal file
65
admin/lib/prom/pprof/prof.go
Normal file
@ -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
|
||||
}
|
||||
}
|
37
admin/lib/prom/prom_test.go
Normal file
37
admin/lib/prom/prom_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
211
admin/lib/prom/promethus.go
Normal file
211
admin/lib/prom/promethus.go
Normal file
@ -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()))
|
||||
}
|
212
admin/lib/web/example/example.go
Normal file
212
admin/lib/web/example/example.go
Normal file
@ -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 = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>GM MASTER</title>
|
||||
<div style="padding-left: 50px;">
|
||||
{{ $pathsList := .Paths }}
|
||||
{{ range $pidex, $paths := $pathsList }}
|
||||
<div style="float:left">
|
||||
<h1 style="text-align: center">指令列表{{ incr $pidex }}</h1>
|
||||
<div style="padding-left: 20px;padding-right: 20px">
|
||||
<table border = "1" align="center">
|
||||
{{ range $idx, $path := $paths }}
|
||||
<tr>
|
||||
<th colspan="5" style="background-color: green; text-align: left">{{ $path.Route.Desc }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>请求路径</b></td>
|
||||
<th colspan="4" style="text-align: left"> {{ $path.Path }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>方法</b></td>
|
||||
<th colspan="4" style="text-align: left"> {{ $path.Route.MethodDesc "或" }}</th>
|
||||
</tr>
|
||||
{{ if $path.Route.HasRequest }}
|
||||
{{ range $idx1, $field := $path.Route.Fields }}
|
||||
<tr>
|
||||
<td><b>{{ printf "参数%d" $idx1 }}</b></td>
|
||||
<td style="text-align: center">{{printf "%s" $field.Name }}</td>
|
||||
<td style="text-align: center">{{printf "%s" $field.Type }}</td>
|
||||
<td>{{printf "%s" $field.Desc }}</td>
|
||||
{{ if eq $field.Default "" }}
|
||||
<td>{{printf "默认:无" }}</td>
|
||||
{{ else }}
|
||||
<td>{{printf "默认:%s" $field.Default }}</td>
|
||||
{{ end }}
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<tr><th colspan="5">无需参数</th>
|
||||
{{ end }}
|
||||
<!--<tr><th colspan="5"><hr></th></tr>-->
|
||||
{{ end }}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
`
|
183
admin/lib/web/group.go
Normal file
183
admin/lib/web/group.go
Normal file
@ -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
|
||||
}
|
24
admin/lib/web/model.go
Normal file
24
admin/lib/web/model.go
Normal file
@ -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),
|
||||
})
|
||||
}
|
12
admin/lib/web/readme.txt
Normal file
12
admin/lib/web/readme.txt
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
注意,get、post注册的request结构字段,如果是uri参数方式类型只支持golang基础类型以及基础类型的切片,不能是结构体类型,
|
||||
例如:
|
||||
type Field struct {
|
||||
A int
|
||||
B bool
|
||||
}
|
||||
type Request struct {
|
||||
F1 *Field
|
||||
}
|
||||
F1字段就是非法的,无法解析,会报错
|
173
admin/lib/web/router.go
Normal file
173
admin/lib/web/router.go
Normal file
@ -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
|
||||
}
|
85
admin/lib/web/router_desc.go
Normal file
85
admin/lib/web/router_desc.go
Normal file
@ -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
|
||||
}
|
303
admin/lib/web/router_gin.go
Normal file
303
admin/lib/web/router_gin.go
Normal file
@ -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
|
||||
}
|
56
admin/lib/xlog/catch.go
Normal file
56
admin/lib/xlog/catch.go
Normal file
@ -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)
|
||||
}
|
31
admin/lib/xlog/crosssyslog/syslog_darwin.go
Normal file
31
admin/lib/xlog/crosssyslog/syslog_darwin.go
Normal file
@ -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)
|
||||
}
|
31
admin/lib/xlog/crosssyslog/syslog_linux.go
Normal file
31
admin/lib/xlog/crosssyslog/syslog_linux.go
Normal file
@ -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)
|
||||
}
|
21
admin/lib/xlog/crosssyslog/syslog_windows.go
Normal file
21
admin/lib/xlog/crosssyslog/syslog_windows.go
Normal file
@ -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
|
||||
}
|
8
admin/lib/xlog/handler.go
Normal file
8
admin/lib/xlog/handler.go
Normal file
@ -0,0 +1,8 @@
|
||||
package xlog
|
||||
|
||||
// Handler writes logs to somewhere
|
||||
type Handler interface {
|
||||
//Rotate()
|
||||
Write(p []byte) (n int, err error)
|
||||
Close() error
|
||||
}
|
58
admin/lib/xlog/handler/handler_file.go
Normal file
58
admin/lib/xlog/handler/handler_file.go
Normal file
@ -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()
|
||||
}
|
83
admin/lib/xlog/handler/handler_file_rotate.go
Normal file
83
admin/lib/xlog/handler/handler_file_rotate.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
207
admin/lib/xlog/handler/handler_file_rotate_atday.go
Normal file
207
admin/lib/xlog/handler/handler_file_rotate_atday.go
Normal file
@ -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()
|
||||
}
|
202
admin/lib/xlog/handler/handler_file_rotate_daymax.go
Normal file
202
admin/lib/xlog/handler/handler_file_rotate_daymax.go
Normal file
@ -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...)
|
||||
}
|
96
admin/lib/xlog/handler/handler_file_rotate_timing.go
Normal file
96
admin/lib/xlog/handler/handler_file_rotate_timing.go
Normal file
@ -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()
|
||||
}
|
17
admin/lib/xlog/handler/handler_null.go
Normal file
17
admin/lib/xlog/handler/handler_null.go
Normal file
@ -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() {
|
||||
|
||||
}
|
65
admin/lib/xlog/handler/handler_socket.go
Normal file
65
admin/lib/xlog/handler/handler_socket.go
Normal file
@ -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
|
||||
}
|
28
admin/lib/xlog/handler/handler_stream.go
Normal file
28
admin/lib/xlog/handler/handler_stream.go
Normal file
@ -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
|
||||
}
|
62
admin/lib/xlog/handler/handler_test.go
Normal file
62
admin/lib/xlog/handler/handler_test.go
Normal file
@ -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()
|
||||
}
|
14
admin/lib/xlog/handler/logsyscall/jlog_syscall_darwin.go
Normal file
14
admin/lib/xlog/handler/logsyscall/jlog_syscall_darwin.go
Normal file
@ -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
|
||||
}
|
16
admin/lib/xlog/handler/logsyscall/jlog_syscall_linux.go
Normal file
16
admin/lib/xlog/handler/logsyscall/jlog_syscall_linux.go
Normal file
@ -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)
|
||||
}
|
14
admin/lib/xlog/handler/logsyscall/jlog_syscall_windows.go
Normal file
14
admin/lib/xlog/handler/logsyscall/jlog_syscall_windows.go
Normal file
@ -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
|
||||
}
|
255
admin/lib/xlog/log.go
Normal file
255
admin/lib/xlog/log.go
Normal file
@ -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
|
||||
}
|
15
admin/lib/xlog/log_hook.go
Normal file
15
admin/lib/xlog/log_hook.go
Normal file
@ -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())
|
||||
}
|
||||
}
|
30
admin/lib/xos/signal.go
Normal file
30
admin/lib/xos/signal.go
Normal file
@ -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
|
||||
}
|
2
ui/.env.development
Normal file
2
ui/.env.development
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_APP_BASE_API = '/api'
|
||||
VITE_APP_BASE_URL = 'http://192.168.78.128:8080/api'
|
30
ui/.gitignore
vendored
Normal file
30
ui/.gitignore
vendored
Normal file
@ -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
|
3
ui/.vscode/extensions.json
vendored
Normal file
3
ui/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
29
ui/README.md
Normal file
29
ui/README.md
Normal file
@ -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
|
||||
```
|
13
ui/index.html
Normal file
13
ui/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
8
ui/jsconfig.json
Normal file
8
ui/jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
3862
ui/package-lock.json
generated
Normal file
3862
ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
ui/package.json
Normal file
25
ui/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
BIN
ui/public/favicon.ico
Normal file
BIN
ui/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
11
ui/src/App.vue
Normal file
11
ui/src/App.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
9
ui/src/api/project/project.js
Normal file
9
ui/src/api/project/project.js
Normal file
@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getProjectList(params) {
|
||||
return request({
|
||||
url: '/project',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
86
ui/src/assets/base.css
Normal file
86
ui/src/assets/base.css
Normal file
@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
: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;
|
||||
}
|
BIN
ui/src/assets/icon/header.png
Normal file
BIN
ui/src/assets/icon/header.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 MiB |
1
ui/src/assets/logo.svg
Normal file
1
ui/src/assets/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
After Width: | Height: | Size: 276 B |
35
ui/src/assets/main.css
Normal file
35
ui/src/assets/main.css
Normal file
@ -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;
|
||||
}
|
||||
}
|
26
ui/src/components/restful/table.vue
Normal file
26
ui/src/components/restful/table.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
rows: {}
|
||||
})
|
||||
console.log("rows:", props.rows)
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-button size="large" type="primary">添加</el-button>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<el-table v-for="rows"
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
16
ui/src/main.js
Normal file
16
ui/src/main.js
Normal file
@ -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')
|
27
ui/src/router/index.js
Normal file
27
ui/src/router/index.js
Normal file
@ -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
|
12
ui/src/stores/counter.js
Normal file
12
ui/src/stores/counter.js
Normal file
@ -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 }
|
||||
})
|
68
ui/src/utils/request.js
Normal file
68
ui/src/utils/request.js
Normal file
@ -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
|
||||
|
43
ui/src/views/Home.vue
Normal file
43
ui/src/views/Home.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import avatarUrl from '@/assets/icon/header.png'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// 计算当前激活的菜单项
|
||||
const activeMenu = computed(() => route.path)
|
||||
|
||||
// 处理菜单点击事件
|
||||
const handleMenuSelect = (index) => {
|
||||
router.push(index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<div>
|
||||
<el-container>
|
||||
<el-aside width="200px">
|
||||
<el-avatar shape="square" :size="100" :src="avatarUrl"></el-avatar>
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
@select="handleMenuSelect"
|
||||
>
|
||||
<el-menu-item index="/user">
|
||||
<span>用户管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/project">
|
||||
<span>项目管理</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-header>Header</el-header>
|
||||
<el-header><router-view/></el-header>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
30
ui/src/views/project/project.vue
Normal file
30
ui/src/views/project/project.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {getProjectList} from '@/api/project/project.js'
|
||||
import tableComp from "@/components/restful/table.vue"
|
||||
|
||||
const rows = ref([])
|
||||
const getProjectListOk = ref(false)
|
||||
|
||||
getProjectList({page_no: 0, page_len: 100}).then((response) => {
|
||||
console.log(response)
|
||||
rows.value = response.data
|
||||
getProjectListOk.value = true
|
||||
}, (err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
项目
|
||||
<div>
|
||||
<template v-if="getProjectListOk">
|
||||
<component :is="tableComp" :rows="rows"></component>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
11
ui/src/views/user/user.vue
Normal file
11
ui/src/views/user/user.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
用户管理
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
58
ui/vite.config.js
Normal file
58
ui/vite.config.js
Normal file
@ -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
|
||||
// }
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user