添加用户执行历史展示

This commit is contained in:
likun 2025-05-19 17:51:09 +08:00
parent 89cd75a6e2
commit 44372acdef
30 changed files with 702 additions and 103 deletions

View File

@ -16,6 +16,7 @@ import (
"fmt"
"gorm.io/gorm"
"reflect"
"strconv"
"strings"
"time"
)
@ -153,13 +154,13 @@ func (svc *CommonResourceService) GetById(projectId int, resource string, id int
return fieldsDescInfo, et.ToCommonDto(), et, find, nil
}
func (svc *CommonResourceService) Create(projectId int, resource string, dtoObj dto2.CommonDtoValues) (dto2.CommonDtoValues, error) {
func (svc *CommonResourceService) Create(projectId int, resource string, dtoObj dto2.CommonDtoValues) (*entity.Project, dto2.CommonDtoValues, error) {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return nil, err
return projectEt, nil, err
}
if resource != consts.ResourcesName_Project && !find {
return nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
return projectEt, nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
createOne := func(obj dto2.CommonDtoValues) (dto2.CommonDtoValues, error) {
@ -217,53 +218,53 @@ func (svc *CommonResourceService) Create(projectId int, resource string, dtoObj
}
}
} else {
return nil, errcode.New(errcode.ParamsInvalid, "account empty:%+v", dtoObj)
return projectEt, nil, errcode.New(errcode.ParamsInvalid, "account empty:%+v", dtoObj)
}
} else {
newDtoObj, err = createOne(dtoObj)
}
return newDtoObj, err
return projectEt, newDtoObj, err
}
func (svc *CommonResourceService) Edit(projectId int, resource string, dtoObj dto2.CommonDtoValues) error {
func (svc *CommonResourceService) Edit(projectId int, resource string, dtoObj dto2.CommonDtoValues) (*entity.Project, error) {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return err
return projectEt, err
}
if resource != consts.ResourcesName_Project && !find {
return errcode.New(errcode.ServerError, "not found project %v db data", projectId)
return projectEt, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
err = findCommResourceRepo(resource).Repo.Edit(projectEt, dtoObj)
if err != nil {
return err
return projectEt, err
}
// 执行各个项目特定的钩子方法
if hook, ok := projects.GetProjectResourceHook(projectEt, resource).(projects.IPostResourceOpEditHook); ok {
err = hook.Edit(projectEt, resource, dtoObj)
if err != nil {
return err
return projectEt, err
}
return nil
return projectEt, nil
}
return nil
return projectEt, nil
}
func (svc *CommonResourceService) Delete(projectId int, resource string, id int) (*entity.CommonResource, error) {
func (svc *CommonResourceService) Delete(projectId int, resource string, id int) (*entity.Project, *entity.CommonResource, error) {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return nil, err
return projectEt, nil, err
}
if resource != consts.ResourcesName_Project && !find {
return nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
return projectEt, nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
oldEt, find, err := findCommResourceRepo(resource).Repo.Delete(projectEt, id)
if err != nil {
return nil, err
return projectEt, nil, err
}
// 执行各个项目特定的钩子方法
@ -271,27 +272,67 @@ func (svc *CommonResourceService) Delete(projectId int, resource string, id int)
if hook, ok := projects.GetProjectResourceHook(projectEt, resource).(projects.IPostResourceOpDeleteHook); ok {
err = hook.Delete(projectEt, resource, oldEt.ToCommonDto())
if err != nil {
return oldEt, err
return projectEt, oldEt, err
}
return oldEt, nil
return projectEt, oldEt, nil
}
}
return oldEt, nil
return projectEt, oldEt, nil
}
func (svc *CommonResourceService) RowsSelection(projectId int, resourceName string, params *dto2.CommonRowsSelectionReq) (*dto2.CommonRowsSelectionRsp, error) {
func (svc *CommonResourceService) RowsSelection(projectId int, resourceName string, params *dto2.CommonRowsSelectionReq) (*entity.Project, *dto2.CommonRowsSelectionRsp, error) {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return nil, err
return projectEt, nil, err
}
if resourceName != consts.ResourcesName_Project && !find {
return nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
return projectEt, nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
if hook, ok := projects.GetProjectResourceHook(projectEt, resourceName).(projects.IPostResourceOpRowsHook); ok {
return hook.RowsSelection(projectEt, resourceName, params.BtnKey, params.Rows)
rsp, err := hook.RowsSelection(projectEt, resourceName, params.BtnKey, params.Rows)
return projectEt, rsp, err
}
return nil, nil
return projectEt, nil, nil
}
func (svc *CommonResourceService) GetResourceKeyByDto(resource string, dtoObj []dto2.CommonDtoValues) (string, string) {
resourceInfo := findCommResourceRepo(resource)
desc := resourceInfo.Desc
etList := resourceInfo.Repo.MakeEntitiesByDtoList(dtoObj)
keyList := make([]string, 0, len(dtoObj))
for _, et := range etList {
po := et.Po.(model.IModel)
method := reflect.ValueOf(po).MethodByName("GetShowKey")
var key string
if !method.IsValid() {
key = strconv.Itoa(po.GetId())
} else {
rets := method.Call([]reflect.Value{})
key = rets[0].Interface().(string)
}
keyList = append(keyList, key)
}
if len(keyList) > 1 {
return desc, "[" + strings.Join(keyList, ",") + "]"
}
return desc, strings.Join(keyList, ",")
}
func (svc *CommonResourceService) GetResourceSpecBtnKey(resource string, btnKey string) *api.ResourceBtnInfo {
resourceInfo := findCommResourceRepo(resource)
for _, v := range resourceInfo.GlobalBtns {
if v.Key == btnKey {
return v
}
}
for _, v := range resourceInfo.RowBtns {
if v.Key == btnKey {
return v
}
}
return nil
}
func (svc *CommonResourceService) GetSupportResourcesList(permissions []string) []*api.ResourceInitInfo {

View File

@ -9,8 +9,6 @@ import (
"time"
)
var MaxKeyNum = 100000 // 每个批次直接搞10w个不然运营想补加码算法又要一开始定好数量
type CDKey struct {
Po *model.CDKey
}
@ -33,7 +31,12 @@ func (c *CDKey) GenerateKeys() []string {
if c.IsGlobalType() {
return []string{c.Po.Code}
}
return cdkey.GenerateAll(c.Po.ID, MaxKeyNum)[:c.Po.CodeNum]
codeList := make([]string, 0, c.Po.CodeNum)
for i := 0; i < c.Po.CodeNum; i++ {
codeList = append(codeList, cdkey.GenerateOne(c.Po.ID, consts.CDKeyBatchMaxKeyNum, i))
}
return codeList
//return cdkey.GenerateAll(c.Po.ID, consts.CDKeyBatchMaxKeyNum)[:c.Po.CodeNum]
}
func (c *CDKey) AddCount(delta int) {

View File

@ -10,6 +10,7 @@ import (
dto2 "admin/internal/model/dto"
"admin/lib/xlog"
"fmt"
"strings"
)
type ServerHook struct {
@ -71,7 +72,9 @@ func (hook *ServerHook) RowsSelection(projectInfo *entity.Project, resource stri
return nil, err
}
opServers := make([]string, 0)
opHandler := func(serverRunningInfo *internal.ServerInfo) error {
opServers = append(opServers, serverRunningInfo.ServerId)
switch btnKey {
case consts.BtnKeyGlobal_Server_UpAll, consts.BtnKeyRow_Server_Up:
err := serverRunningInfo.Up(apiAddr)
@ -137,6 +140,7 @@ func (hook *ServerHook) RowsSelection(projectInfo *entity.Project, resource stri
return &dto2.CommonRowsSelectionRsp{
Msg: fmt.Sprintf("执行%v操作成功!", msg),
NeedRefresh: true,
ShortDesc: "[" + strings.Join(opServers, ",") + "]",
}, nil
}

View File

@ -29,6 +29,7 @@ type ICommonResourceRepo interface {
Delete(projectEt *entity.Project, id int) (*entity.CommonResource, bool, error)
ListPagination(whereSql string, whereArgs []any, f func(po model.IModel)) error
UpdateClearDelayInvokeCreateHookFieldN(id int) error
MakeEntitiesByDtoList(dtoList []dto2.CommonDtoValues) []*entity.CommonResource
}
func NewCommonResourceRepo(db *gorm.DB, poTemplate model.IModel) ICommonResourceRepo {
@ -199,6 +200,15 @@ func (repo *commonResourceRepoImpl) UpdateClearDelayInvokeCreateHookFieldN(id in
return nil
}
func (repo *commonResourceRepoImpl) MakeEntitiesByDtoList(dtoList []dto2.CommonDtoValues) []*entity.CommonResource {
list := make([]*entity.CommonResource, 0, len(dtoList))
for _, v := range dtoList {
po := repo.makeEmptyPo()
list = append(list, (&entity.CommonResource{}).FromPo(po).FromDto(v))
}
return list
}
func (repo *commonResourceRepoImpl) makeEmptyPo() model.IModel {
return reflect.New(reflect.TypeOf(repo.poTemplate).Elem()).Interface().(model.IModel)
}

View File

@ -34,6 +34,10 @@ func (m *Ban) GetId() int {
return m.ID
}
func (m *Ban) GetShowKey() string {
return m.Value
}
func (m *Ban) GetServerConfIDChoices(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}

View File

@ -36,6 +36,10 @@ func (m *CDKey) GetId() int {
return m.ID
}
func (m *CDKey) GetShowKey() string {
return m.Name
}
func (m *CDKey) GetCodeTypeChoices(project *Project) []*dto.CommonDtoFieldChoice {
return []*dto.CommonDtoFieldChoice{
{Desc: "一码通用", Value: consts.CDKeyType_Global},

View File

@ -32,6 +32,10 @@ func (lm *GlobalMail) TableName() string {
return "mail_global"
}
func (m *GlobalMail) GetShowKey() string {
return m.Title
}
func (m *GlobalMail) GetId() int {
return m.ID
}

View File

@ -31,6 +31,10 @@ func (m *ItemBag) GetId() int {
return m.ID
}
func (m *ItemBag) GetShowKey() string {
return m.Name
}
func (m *ItemBag) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}

View File

@ -39,6 +39,10 @@ func (m *Project) GetId() int {
return m.ID
}
func (m *Project) GetShowKey() string {
return m.Name
}
func (m *Project) GetProjectChoices(_ *Project) []*dto.CommonDtoFieldChoice {
return []*dto.CommonDtoFieldChoice{
{Desc: "神魔大陆", Value: consts.RegisteredProjectId_shenmodalu, Type: 0},

View File

@ -38,6 +38,10 @@ func (m *RoleMail) GetId() int {
return m.ID
}
func (m *RoleMail) GetShowKey() string {
return m.Title
}
func (m *RoleMail) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}

View File

@ -40,6 +40,10 @@ func (m *Server) GetId() int {
return m.ID
}
func (m *Server) GetShowKey() string {
return m.ServerConfID
}
func (m *Server) ListByProjectId(projectId int) ([]*Server, error) {
list := make([]*Server, 0)
err := global.GLOB_DB.Where("project_id=?", projectId).Find(&list).Error

View File

@ -28,6 +28,10 @@ func (m *SupportAccount) GetId() int {
return m.ID
}
func (m *SupportAccount) GetShowKey() string {
return m.Account
}
func (m *SupportAccount) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}

View File

@ -29,6 +29,10 @@ func (m *WhiteList) GetId() int {
return m.ID
}
func (m *WhiteList) GetShowKey() string {
return m.Value
}
func (m *WhiteList) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}

View File

@ -9,7 +9,6 @@ import (
dto2 "admin/internal/model/dto"
"context"
"encoding/json"
"fmt"
"gorm.io/gorm"
)
@ -70,19 +69,31 @@ func (svc *Service) CommonPost(ctx context.Context, projectId int, resourceName
params["ProjectId"] = projectId
}
values, err := svc.resourceSvc.Create(projectId, resourceName, params)
project, values, err := svc.resourceSvc.Create(projectId, resourceName, params)
if err != nil {
return nil, err
}
userId := ctx.Value("user_id").(int)
userName := ctx.Value("user_name").(string)
//userName := ctx.Value("user_name").(string)
event.GetMgrInstance().Publish(event.EventTopic_UserExecute, &event.EventPayload_UserExecute{
UserId: userId,
UserName: userName,
ProjectId: projectId,
Resource: resourceName,
Method: "新增",
NewData: params,
})
resourceDesc, keyDesc := svc.resourceSvc.GetResourceKeyByDto(resourceName, []dto2.CommonDtoValues{params})
evPayload := &event.EventPayload_UserGameExecute{
UserId: userId,
ProjectId: projectId,
ProjectName: project.Po.Name,
OpResourceType: resourceDesc,
OpResourceGroup: "系统",
OpResourceKey: keyDesc,
Method: "新增",
SrcDataList: []any{params},
}
if resourceName != consts.ResourcesName_Project {
evPayload.OpResourceGroup = project.Po.Name
}
event.GetMgrInstance().Publish(event.EventTopic_UserGameExecute, evPayload)
return values, err
}
@ -91,61 +102,100 @@ func (svc *Service) CommonPut(ctx context.Context, projectId int, resourceName s
if resourceName != consts.ResourcesName_Project {
params["ProjectId"] = projectId
}
err := svc.resourceSvc.Edit(projectId, resourceName, params)
userId := ctx.Value("user_id").(int)
userName := ctx.Value("user_name").(string)
event.GetMgrInstance().Publish(event.EventTopic_UserExecute, &event.EventPayload_UserExecute{
UserId: userId,
UserName: userName,
ProjectId: projectId,
Resource: resourceName,
Method: "编辑",
NewData: params,
})
return err
}
func (svc *Service) CommonDelete(ctx context.Context, projectId int, resourceName string, id int) error {
deletedEt, err := svc.resourceSvc.Delete(projectId, resourceName, id)
project, err := svc.resourceSvc.Edit(projectId, resourceName, params)
if err != nil {
return err
}
userId := ctx.Value("user_id").(int)
userName := ctx.Value("user_name").(string)
//userName := ctx.Value("user_name").(string)
event.GetMgrInstance().Publish(event.EventTopic_UserExecute, &event.EventPayload_UserExecute{
UserId: userId,
UserName: userName,
ProjectId: projectId,
Resource: resourceName,
Method: "删除",
NewData: deletedEt.ToCommonDto(),
})
resourceDesc, keyDesc := svc.resourceSvc.GetResourceKeyByDto(resourceName, []dto2.CommonDtoValues{params})
evPayload := &event.EventPayload_UserGameExecute{
UserId: userId,
ProjectId: projectId,
ProjectName: project.Po.Name,
OpResourceType: resourceDesc,
OpResourceGroup: "系统",
OpResourceKey: keyDesc,
Method: "编辑",
SrcDataList: []any{params},
}
if resourceName != consts.ResourcesName_Project {
evPayload.OpResourceGroup = project.Po.Name
}
event.GetMgrInstance().Publish(event.EventTopic_UserGameExecute, evPayload)
return err
}
func (svc *Service) CommonDelete(ctx context.Context, projectId int, resourceName string, id int) error {
project, deletedEt, err := svc.resourceSvc.Delete(projectId, resourceName, id)
if err != nil {
return err
}
userId := ctx.Value("user_id").(int)
//userName := ctx.Value("user_name").(string)
delObj := deletedEt.ToCommonDto()
resourceDesc, keyDesc := svc.resourceSvc.GetResourceKeyByDto(resourceName, []dto2.CommonDtoValues{delObj})
evPayload := &event.EventPayload_UserGameExecute{
UserId: userId,
ProjectId: projectId,
ProjectName: project.Po.Name,
OpResourceType: resourceDesc,
OpResourceGroup: "系统",
OpResourceKey: keyDesc,
Method: "删除",
SrcDataList: []any{delObj},
}
if resourceName != consts.ResourcesName_Project {
evPayload.OpResourceGroup = project.Po.Name
}
event.GetMgrInstance().Publish(event.EventTopic_UserGameExecute, evPayload)
return err
}
func (svc *Service) CommonRowsSelection(ctx context.Context, projectId int, resourceName string, param *dto2.CommonRowsSelectionReq) (*dto2.CommonRowsSelectionRsp, error) {
rsp, err := svc.resourceSvc.RowsSelection(projectId, resourceName, param)
project, rsp, err := svc.resourceSvc.RowsSelection(projectId, resourceName, param)
if err != nil {
return rsp, err
}
userId := ctx.Value("user_id").(int)
userName := ctx.Value("user_name").(string)
//userName := ctx.Value("user_name").(string)
event.GetMgrInstance().Publish(event.EventTopic_UserExecute, &event.EventPayload_UserExecute{
UserId: userId,
UserName: userName,
ProjectId: projectId,
Resource: resourceName,
Method: fmt.Sprintf("选择行:%v", param.BtnKey),
Any: param,
})
srcDataList := make([]any, 0, len(param.Rows))
for _, v := range param.Rows {
srcDataList = append(srcDataList, v)
}
resourceDesc, keyDesc := svc.resourceSvc.GetResourceKeyByDto(resourceName, param.Rows)
btnInfo := svc.resourceSvc.GetResourceSpecBtnKey(resourceName, param.BtnKey)
evPayload := &event.EventPayload_UserGameExecute{
UserId: userId,
ProjectId: projectId,
ProjectName: project.Po.Name,
OpResourceType: resourceDesc,
OpResourceGroup: "系统",
OpResourceKey: keyDesc,
Method: param.BtnKey,
SrcDataList: srcDataList,
}
if btnInfo != nil {
evPayload.Method = btnInfo.Name
}
if resourceName != consts.ResourcesName_Project {
evPayload.OpResourceGroup = project.Po.Name
}
event.GetMgrInstance().Publish(event.EventTopic_UserGameExecute, evPayload)
return rsp, err
}

View File

@ -2,7 +2,9 @@ package model
import (
"admin/internal/db"
"admin/internal/errcode"
"admin/internal/global"
"strings"
"time"
)
@ -12,14 +14,15 @@ func init() {
// History 用户执行历史
type History struct {
ID int `gorm:"primarykey" readonly:"true"`
UserId int
UserName string
ProjectId int
Resource string
Method string
Data string `gorm:"type:longtext"`
CreatedAt time.Time
ID int `gorm:"primarykey" readonly:"true"`
UserId int `gorm:"index"`
UserName string `gorm:"index"`
OpResourceType string `gorm:"type:varchar(100);index"` // 操作资源类型:项目、角色、用户、封禁等
OpResourceGroup string `gorm:"type:varchar(100);index"` // 操作资源组名:系统、系统、系统、神魔大陆主播服等
OpResourceKey string `gorm:"type:varchar(100);index"` // 操作对象名神魔大陆主播服、qa、chenshun、account123
Method string `gorm:"type:varchar(100);index"` // 操作方法:新增、修改、删除、一键停服
DetailInfo string `gorm:"type:longtext"` // 详情,涉及到的对象列表,例如新增就给表行数据
CreatedAt time.Time
}
func (m *History) TableName() string {
@ -33,3 +36,54 @@ func (m *History) GetId() int {
func (m *History) Create() error {
return global.GLOB_DB.Create(m).Error
}
func (m *History) List(pageNo, pageLen int, userId int, opType, opGroup, opKey, method string) ([]*History, int, error) {
if pageNo <= 0 || pageLen <= 0 || pageLen > 1000 {
return nil, 0, errcode.New(errcode.ParamsInvalid, "pageNo or pageLen invalid:%v,%v", pageNo, pageLen)
}
list := make([]*History, 0)
whereSqls := make([]string, 0)
whereArgs := make([]any, 0)
if userId != 0 {
whereSqls = append(whereSqls, "user_id=?")
whereArgs = append(whereArgs, userId)
}
if opType != "" {
whereSqls = append(whereSqls, "op_resource_type=?")
whereArgs = append(whereArgs, opType)
}
if opGroup != "" {
whereSqls = append(whereSqls, "op_resource_group=?")
whereArgs = append(whereArgs, opGroup)
}
if opKey != "" {
whereSqls = append(whereSqls, "op_resource_key=?")
whereArgs = append(whereArgs, opKey)
}
if method != "" {
whereSqls = append(whereSqls, "method=?")
whereArgs = append(whereArgs, method)
}
tx := global.GLOB_DB
txCount := global.GLOB_DB.Model(new(History))
if len(whereSqls) != 0 {
tx = tx.Where(strings.Join(whereSqls, " and "), whereArgs...)
txCount = txCount.Where(strings.Join(whereSqls, " and "), whereArgs...)
}
limitStart := (pageNo - 1) * pageLen
err := tx.Offset(limitStart).Limit(pageLen).Order("created_at desc").Find(&list).Error
if err != nil {
return nil, 0, errcode.New(errcode.DBError, "list error:%v", err)
}
totalCount := int64(0)
err = txCount.Count(&totalCount).Error
if err != nil {
return nil, 0, errcode.New(errcode.DBError, "count error:%v", err)
}
return list, int(totalCount), nil
}

View File

@ -22,3 +22,12 @@ func (ctl *controller) GetUserInfo(ctx *context.WebContext, params *dto.NilReq,
*rsp = *svcRsp
return nil
}
func (ctl *controller) GetUserExecHistory(ctx *context.WebContext, params *dto.ListUserOpHistoryReq, rsp *dto.ListUserOpHistoryRsp) error {
rsp1, err := ctl.svc.ListUserExecHistory(params)
if err != nil {
return err
}
*rsp = *rsp1
return nil
}

View File

@ -15,6 +15,7 @@ func (srv *Server) Route(engine *web.Engine) {
userGroup := apiGroup.Group("/user", "用户操作组")
userGroup.Post("/login", "登录", consts.WebPathPermit_Write, srv.ctl.Login)
userGroup.Get("/info", "获取用户信息,里面包含用户权限信息,用于前端生成动态菜单", consts.WebPathPermit_Read, srv.ctl.GetUserInfo)
userGroup.Get("/history", "获取用户执行历史记录,按各种条件检索", consts.WebPathPermit_Read, srv.ctl.GetUserExecHistory)
}
{
@ -25,5 +26,4 @@ func (srv *Server) Route(engine *web.Engine) {
userResourceGroup.Put("", "编辑", consts.WebPathPermit_Read, srv.ctl.CommonPut)
userResourceGroup.Delete("", "删除", consts.WebPathPermit_Read, srv.ctl.CommonDelete)
}
}

View File

@ -6,6 +6,7 @@ import (
"admin/internal/event"
"admin/lib/xlog"
"encoding/json"
"strconv"
)
type Server struct {
@ -23,24 +24,41 @@ func New(svc *service.Service) *Server {
}
func (srv *Server) jobsSubscribe() {
event.GetMgrInstance().Subscribe("user", event.EventTopic_UserExecute, srv.subscriberHandlerUserExecute)
event.GetMgrInstance().Subscribe("user", event.EventTopic_UserGameExecute, srv.subscriberHandlerUserExecute)
}
func (srv *Server) subscriberHandlerUserExecute(msg *event.Msg) {
po := new(model.History)
msgHistory := &event.EventPayload_UserExecute{}
msgHistory := &event.EventPayload_UserGameExecute{}
err := json.Unmarshal(msg.Payload, msgHistory)
if err != nil {
xlog.Errorf("unmarshal msg(%+v) err:%v", string(msg.Payload), err)
return
}
userInfo, find, err := srv.ctl.svc.GetUserById(msgHistory.UserId)
if err != nil {
xlog.Errorf("find user %+v info error:%v", msgHistory, err)
} else if !find {
xlog.Errorf("not found user info:%+v", msgHistory)
}
po.UserId = msgHistory.UserId
po.UserName = msgHistory.UserName
po.ProjectId = msgHistory.ProjectId
po.Resource = msgHistory.Resource
po.UserName = strconv.Itoa(msgHistory.UserId)
if find {
po.UserName = userInfo.Po.UserName
}
po.OpResourceType = msgHistory.OpResourceType
po.OpResourceGroup = msgHistory.OpResourceGroup
po.OpResourceKey = msgHistory.OpResourceKey
po.Method = msgHistory.Method
po.Data = string(msg.Payload)
b, _ := json.Marshal(&map[string]any{
"project_id": msgHistory.ProjectId,
"project_name": msgHistory.ProjectName,
"src": msgHistory.SrcDataList,
"dst": msgHistory.DstDataList,
})
po.DetailInfo = string(b)
err = po.Create()
if err != nil {

View File

@ -10,6 +10,7 @@ import (
"admin/lib/tokenlib"
"admin/lib/xlog"
"context"
"time"
)
func (svc *Service) CheckToken(token string, userId int) error {
@ -110,3 +111,39 @@ func (svc *Service) GetUserInfo(userId int) (*dto2.GetUserInfoRsp, error) {
func (svc *Service) GetUserById(userId int) (*entity.User, bool, error) {
return svc.resourceSvc.GetUserById(userId)
}
func (svc *Service) ListUserExecHistory(params *dto2.ListUserOpHistoryReq) (*dto2.ListUserOpHistoryRsp, error) {
list, totalCount, err := new(model.History).List(params.PageNo, params.PageLen,
params.UserId, params.OpResourceType, params.OpResourceGroup, params.OpResourceKey, params.Method)
if err != nil {
return nil, err
}
rsp := new(dto2.ListUserOpHistoryRsp)
rsp.List = make([]*dto2.UserOpHistoryInfo, 0)
userMap := make(map[int]*entity.User, 0)
for _, v := range list {
var userName = v.UserName
userInfo, find := userMap[v.UserId]
if !find {
userInfo, find, err := svc.resourceSvc.GetUserById(v.UserId)
if err == nil && find {
userName = userInfo.Po.UserName
userMap[v.UserId] = userInfo
}
} else {
userName = userInfo.Po.UserName
}
rsp.List = append(rsp.List, &dto2.UserOpHistoryInfo{
UserId: v.UserId,
UserName: userName,
OpResourceType: v.OpResourceType,
OpResourceGroup: v.OpResourceGroup,
OpResourceKey: v.OpResourceKey,
Method: v.Method,
CreatedAt: v.CreatedAt.Format(time.DateTime),
DetailInfo: v.DetailInfo,
})
}
rsp.TotalCount = totalCount
return rsp, nil
}

View File

@ -67,3 +67,7 @@ const (
BtnKeyRow_Server_Down = "server:down"
BtnKeyRow_Server_Up = "server:up"
)
const (
CDKeyBatchMaxKeyNum = 1000000 // 奖励码一批最大数量,不然运营想补加码,算法又要一开始定好数量
)

View File

@ -3,19 +3,20 @@ package event
import "time"
const (
EventTopic_UserExecute = "user.execute"
EventTopic_UserGameExecute = "user.game.execute"
EventTopic_DelayInvokeCreateHook = "resource.create.delay"
)
type EventPayload_UserExecute struct {
UserId int `json:"user_id"`
UserName string `json:"user_name"`
ProjectId int `json:"project_id"`
Resource string `json:"resource"`
Method string `json:"method"`
OldData any `json:"old_data"`
NewData any `json:"new_data"`
Any any `json:"any"`
type EventPayload_UserGameExecute struct {
UserId int
ProjectId int
ProjectName string
OpResourceType string
OpResourceGroup string
OpResourceKey string
Method string
SrcDataList []any
DstDataList []any
}
type EventPayload_DelayInvokeCreateHook struct {

View File

@ -127,3 +127,14 @@ type TokenInfo struct {
Token string `json:"token"`
ExpireAt int64 `json:"expire_at"`
}
type UserOpHistoryInfo struct {
UserId int `json:"userId"`
UserName string `json:"userName"`
OpResourceType string `json:"opResourceType"`
OpResourceGroup string `json:"opResourceGroup"`
OpResourceKey string `json:"opResourceKey"`
Method string `json:"method"`
CreatedAt string `json:"createdAt"`
DetailInfo string `json:"detailInfo"`
}

View File

@ -51,6 +51,7 @@ type CommonRowsSelectionReq struct {
type CommonRowsSelectionRsp struct {
Msg string `json:"msg"`
NeedRefresh bool `json:"need_refresh"`
ShortDesc string `json:"short_desc"`
}
type CommandListReq struct {

View File

@ -20,3 +20,18 @@ type LoginRsp struct {
}
type GetUserInfoRsp = LoginRsp
type ListUserOpHistoryReq struct {
PageNo int `json:"pageNo"`
PageLen int `json:"pageLen"`
UserId int `json:"userId"`
OpResourceType string `json:"opResourceType"`
OpResourceGroup string `json:"opResourceGroup"`
OpResourceKey string `json:"opResourceKey"`
Method string `json:"method"`
}
type ListUserOpHistoryRsp struct {
List []*UserOpHistoryInfo `json:"list"`
TotalCount int `json:"totalCount"`
}

View File

@ -21,4 +21,22 @@ export function generateRoutes() {
url: "/routes",
method: "get"
})
}
export function getUserExecHistory(pageNo, pageLen, userId, opResourceType, opResourceGroup, opResourceKey, method) {
const p = {
pageNo,
pageLen,
userId,
opResourceType,
opResourceGroup,
opResourceKey,
method
}
console.log("params:", p)
return request({
url: "/user/history",
method: "get",
params: p
})
}

View File

@ -10,6 +10,17 @@ import {getProjects} from "@/stores/user.js";
const cachedResource = LocalCache.getCache("resource");
const props = defineProps({
rowClickDialogBtns: Array,
})
let rowClickDialogBtns = []
if (props.rowClickDialogBtns) {
rowClickDialogBtns = props.rowClickDialogBtns
}
const rowClickBtnVisibleList = reactive(rowClickDialogBtns.map(() => false))
const rowClickBtnSelectRow = ref(null)
const listRsp = ref({fields_desc: [], rows: []})
const listDataOK = ref(false)
const resource_raw_node = cachedResource;
@ -318,6 +329,12 @@ const handlePaginationCurChange = (val) => {
listData()
}
const tableSelectRow3 = (i, row) => {
rowClickBtnSelectRow.value = row
rowClickBtnVisibleList[i] = true
console.log("点击按钮:", rowClickBtnSelectRow)
}
</script>
<template>
@ -358,6 +375,30 @@ const handlePaginationCurChange = (val) => {
</el-icon>
<span>编辑</span>
</el-button>
<template v-for="(btn, index) in rowClickDialogBtns">
<template v-if="btn.btn_type === 0">
<el-button size="default" :type="btn.btn_color_type"
@click="tableSelectRows2(btn, scope.$index, scope.row)">
{{ btn.name }}
</el-button>
</template>
<template v-else-if="btn.btn_type === 1">
<el-button size="default" :type="btn.btn_color_type"
@click="tableSelectRow3(index, scope.row)">
{{ btn.name }}
</el-button>
</template>
<template v-else-if="btn.btn_type === 2">
<el-button size="default" :type="btn.btn_color_type"
@click="tableSelectRow4(btn, scope.row)">
{{ btn.name }}
</el-button>
</template>
</template>
<el-button size="default" type="danger" @click="handleDelete( scope.$index, scope.row)"
v-if="(resource_raw_node.meta.methods.delete === true)">
<el-icon style="vertical-align: middle">
@ -365,6 +406,7 @@ const handlePaginationCurChange = (val) => {
</el-icon>
<span>删除</span>
</el-button>
</template>
</el-table-column>
</el-table>
@ -582,6 +624,16 @@ const handlePaginationCurChange = (val) => {
</el-form>
</el-dialog>
<template v-for="(btn, index) in rowClickDialogBtns">
<el-dialog v-model="rowClickBtnVisibleList[index]" :title="btn.name"
@close="rowClickBtnVisibleList[index]=false"
destroy-on-close>
<div>
<component :is="btn.btn_callback_component" :rowInfo="rowClickBtnSelectRow"/>
</div>
</el-dialog>
</template>
</el-main>
</el-container>
</template>

View File

@ -0,0 +1,219 @@
<script setup>
import {getUserExecHistory} from "@/api/sys.js";
import empty from "@/components/restful/empty.vue";
import userStore from "@/stores/user.js";
const props = defineProps({
rowInfo: {},
disableConditionInput: true,
})
let disableConditionInput1 = true
if (props.disableConditionInput) {
disableConditionInput1 = props.disableConditionInput
}
console.log("禁止搜索框:", disableConditionInput1)
const isAdminCharacter = userStore().userInfo.character === 'admin'
const hasListPermit = ref(isAdminCharacter)
const current_page = ref(1)
const page_size = ref(20)
const whereCondUserId = ref(props.userId)
if (props.rowInfo && props.rowInfo.ID !== undefined) {
whereCondUserId.value = props.rowInfo.ID
}
const whereCondOpResourceType = ref('')
const whereCondOpResourceGroup = ref('')
const whereCondOpResourceKey = ref('')
const whereCondMethod = ref('')
const listDataOK = ref(false)
const pageSizes = [20, 50, 100]
const totalRowCount = ref(0)
const rows = ref([])
const listData = () => {
getUserExecHistory(current_page.value, page_size.value, whereCondUserId.value,
whereCondOpResourceType.value, whereCondOpResourceGroup.value, whereCondOpResourceKey.value, whereCondMethod.value).then((res) => {
rows.value = res.data.list
totalRowCount.value = res.data.totalCount
listDataOK.value = true
}, (err) => {
})
}
onMounted(() => {
listData()
})
const resetConditionSearch = () => {
whereCondUserId.value = ''
whereCondOpResourceType.value = ''
whereCondOpResourceGroup.value = ''
whereCondOpResourceKey.value = ''
whereCondMethod.value = ''
}
const handlePaginationSizeChange = (val) => {
// console.log(`${val} `)
if (totalRowCount.value <= 0) {
return
}
if (page_size.value * current_page.value > totalRowCount.value) {
//
if (rows.value.length >= totalRowCount.value) {
return
}
}
// console.log(`${page_size.value} `)
listData()
}
const handlePaginationCurChange = (val) => {
// console.log(`${val} `)
listData()
}
</script>
<template>
<div :class="disableConditionInput1 ? 'app-content1' : 'app-content'">
<template v-if="!hasListPermit">
<component :is="empty"></component>
</template>
<template v-else>
<el-container v-if="listDataOK">
<el-header style="margin-bottom: 10px">
<el-row>
<el-input v-model="whereCondUserId" placeholder="用户id" style="width: 150px;margin-left: 10px"
v-if="disableConditionInput1 === false"/>
<el-input v-model="whereCondOpResourceType" placeholder="操作资源类型"
style="width: 150px;margin-left: 10px" v-if="disableConditionInput1 === false"/>
<el-input v-model="whereCondOpResourceGroup" placeholder="操作资源组"
style="width: 150px;margin-left: 10px" v-if="disableConditionInput1 === false"/>
<el-input v-model="whereCondOpResourceKey" placeholder="操作对象" style="width: 150px;margin-left: 10px"
v-if="disableConditionInput1 === false"/>
<el-input v-model="whereCondMethod" placeholder="操作方法" style="width: 150px;margin-left: 10px"
v-if="disableConditionInput1 === false"/>
<el-button @click="listData" type="primary" style="margin-left: 10px"
v-if="disableConditionInput1 === false">条件搜索
</el-button>
<el-button @click="resetConditionSearch" v-if="disableConditionInput1 === false">清空条件</el-button>
</el-row>
</el-header>
<el-main>
<div class="table-content">
<div class="table">
<el-table :data="rows" style="width: 100%" table-layout="auto" stripe
tooltip-effect="light">
<el-table-column prop="userId" label="用户id"></el-table-column>
<el-table-column prop="userName" label="用户名"></el-table-column>
<el-table-column prop="opResourceType" label="操作资源类型"></el-table-column>
<el-table-column prop="opResourceGroup" label="操作资源组"></el-table-column>
<el-table-column prop="opResourceKey" label="操作对象"></el-table-column>
<el-table-column prop="method" label="操作方法"></el-table-column>
<el-table-column prop="createdAt" label="创建时间"></el-table-column>
<el-table-column prop="detailInfo" label="详情数据" show-overflow-tooltip></el-table-column>
</el-table>
</div>
<!-- 表格数据分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="current_page"
v-model:page-size="page_size"
:page-sizes="pageSizes"
layout="total, sizes, prev, pager, next, jumper"
:total="totalRowCount"
@size-change="handlePaginationSizeChange"
@current-change="handlePaginationCurChange"
/>
</div>
</div>
</el-main>
</el-container>
</template>
</div>
</template>
<style scoped lang="scss">
.app-content {
height: calc(100vh - 100px);
display: flex;
.table-content {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
overflow: auto;
.table {
flex: 1;
position: relative;
:deep(.el-table) {
flex: 1;
position: absolute;
}
}
}
}
.app-content1 {
height: calc(100vh - 500px);
display: flex;
.table-content {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
overflow: auto;
.table {
flex: 1;
position: relative;
:deep(.el-table) {
flex: 1;
position: absolute;
}
}
}
}
.pagination-container .el-pagination {
right: 0;
position: absolute;
height: 25px;
margin-bottom: 50px;
margin-top: 0px;
padding: 10px 30px !important;
z-index: 2;
}
.pagination-container.hidden {
display: none;
}
@media (max-width: 768px) {
.pagination-container .el-pagination > .el-pagination__jump {
display: none !important;
}
.pagination-container .el-pagination > .el-pagination__sizes {
display: none !important;
}
}
</style>

View File

@ -124,6 +124,9 @@ function logout() {
<!-- 头像 -->
<span style="font-size: 20px">
<el-icon>
<User/>
</el-icon>
{{ nickName }}
<el-icon color="black" size="10">
<arrow-down/>

View File

@ -1,9 +1,11 @@
<script setup>
import tableHistory from "@/components/user/history.vue";
</script>
<template>
用户执行历史列表待开发
<component :is="tableHistory" disableConditionInput="false"></component>
</template>
<style scoped>

View File

@ -2,6 +2,7 @@
import tableView from "@/components/restful/tableUser.vue";
import LocalCache from "@/stores/localCache.js";
import userStore from "@/stores/user.js";
import userHistory from "@/components/user/history.vue";
let resourceCache = {
meta: {
@ -22,11 +23,21 @@ if (userInfo.character !== "admin") {
}
LocalCache.setCache("resource", resourceCache)
const rowClickDialogBtns = []
rowClickDialogBtns.push({
key: "user:exec:history",
name: "执行记录",
btn_color_type: "info",
btn_type: 1,
btn_callback_component: userHistory,
})
</script>
<template>
<div>
<component :is="tableView"></component>
<component :is="tableView" :rowClickDialogBtns="rowClickDialogBtns"></component>
</div>
</template>