This commit is contained in:
2026-08-13 21:51:20 +08:00
parent be5362913b
commit 21c264fcd7
60 changed files with 10174 additions and 69 deletions
+377
View File
@@ -0,0 +1,377 @@
package service
import (
"context"
"strings"
"kra/internal/biz"
)
type AccessService struct{ uc *biz.AccessUsecase }
func NewAccessService(uc *biz.AccessUsecase) *AccessService { return &AccessService{uc: uc} }
func (s *AccessService) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) {
return s.uc.Authorize(ctx, aid, path, method)
}
func authorityDTO(v *biz.Authority) map[string]any { return convertAuthority(*v) }
func apiDTO(v *biz.API) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "path": v.Path, "description": v.Description, "apiGroup": v.APIGroup, "method": v.Method}
}
func departmentDTO(v *biz.Department) map[string]any {
children := make([]map[string]any, 0, len(v.Children))
for _, c := range v.Children {
children = append(children, departmentDTO(c))
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "parentId": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leaderId": v.LeaderID, "leader": nil, "status": v.Status, "children": children, "namePath": ""}
}
func positionDTO(v *biz.Position) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}
}
func (s *AccessService) Authorities(ctx context.Context) ([]map[string]any, error) {
items, err := s.uc.Repo().ListAuthorities(ctx)
if err != nil {
return nil, err
}
byID := make(map[uint]map[string]any, len(items))
for _, v := range items {
byID[v.AuthorityID] = authorityDTO(v)
}
roots := make([]map[string]any, 0)
for _, v := range items {
item := byID[v.AuthorityID]
if v.ParentID != nil && *v.ParentID != 0 && byID[*v.ParentID] != nil {
parent := byID[*v.ParentID]
children, _ := parent["children"].([]map[string]any)
parent["children"] = append(children, item)
} else {
roots = append(roots, item)
}
}
return roots, nil
}
func (s *AccessService) CreateAuthority(ctx context.Context, v *biz.Authority) error {
return s.uc.Repo().CreateAuthority(ctx, v)
}
func (s *AccessService) CopyAuthority(ctx context.Context, sourceID uint, v *biz.Authority) error {
return s.uc.Repo().CopyAuthority(ctx, sourceID, v)
}
func (s *AccessService) UpdateAuthority(ctx context.Context, v *biz.Authority) error {
return s.uc.Repo().UpdateAuthority(ctx, v)
}
func (s *AccessService) DeleteAuthority(ctx context.Context, id uint) error {
return s.uc.Repo().DeleteAuthority(ctx, id)
}
func (s *AccessService) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetAuthorityMenus(ctx, id, ids)
}
func (s *AccessService) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().AuthorityMenuIDs(ctx, id)
}
func (s *AccessService) AuthorityMenus(ctx context.Context, id uint) ([]map[string]any, error) {
ids, err := s.uc.Repo().AuthorityMenuIDs(ctx, id)
if err != nil {
return nil, err
}
selected := make(map[uint]bool, len(ids))
for _, value := range ids {
selected[value] = true
}
menus, err := s.uc.Repo().ListMenus(ctx)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(ids))
for _, menu := range menus {
if selected[menu.ID] {
out = append(out, map[string]any{"menuId": menu.ID, "authorityId": id, "sysBaseMenu": convertMenu(menu), "parentId": menu.ParentID})
}
}
return out, nil
}
func (s *AccessService) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetAuthorityUsers(ctx, id, ids)
}
func (s *AccessService) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().AuthorityUserIDs(ctx, id)
}
func (s *AccessService) SetDataScope(ctx context.Context, id uint, scope int, ids []uint) error {
return s.uc.Repo().SetDataScope(ctx, id, scope, ids)
}
func (s *AccessService) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().DataScopeDepartmentIDs(ctx, id)
}
func (s *AccessService) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {
return s.uc.Repo().ResolveDataScope(ctx, authorityID, userID)
}
func (s *AccessService) Menus(ctx context.Context) ([]map[string]any, error) {
items, err := s.uc.Repo().ListMenus(ctx)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
v.Buttons, err = s.uc.Repo().Buttons(ctx, v.ID)
if err != nil {
return nil, err
}
out = append(out, convertMenu(v))
}
return out, nil
}
func (s *AccessService) MenuTree(ctx context.Context) ([]map[string]any, error) {
items, err := s.uc.Repo().ListMenus(ctx)
if err != nil {
return nil, err
}
byID := make(map[uint]*biz.Menu, len(items))
for _, item := range items {
item.Buttons, err = s.uc.Repo().Buttons(ctx, item.ID)
if err != nil {
return nil, err
}
item.Children = []*biz.Menu{}
byID[item.ID] = item
}
roots := make([]*biz.Menu, 0)
for _, item := range items {
if parent := byID[item.ParentID]; parent != nil {
parent.Children = append(parent.Children, item)
} else {
roots = append(roots, item)
}
}
out := make([]map[string]any, 0, len(roots))
for _, item := range roots {
out = append(out, convertMenu(item))
}
return out, nil
}
func (s *AccessService) CreateMenu(ctx context.Context, v *biz.Menu) error {
return s.uc.Repo().CreateMenu(ctx, v)
}
func (s *AccessService) UpdateMenu(ctx context.Context, v *biz.Menu) error {
return s.uc.Repo().UpdateMenu(ctx, v)
}
func (s *AccessService) DeleteMenu(ctx context.Context, id uint) error {
return s.uc.Repo().DeleteMenu(ctx, id)
}
func (s *AccessService) Menu(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindMenu(ctx, id)
if err != nil {
return nil, err
}
v.Buttons, err = s.uc.Repo().Buttons(ctx, id)
if err != nil {
return nil, err
}
return convertMenu(v), nil
}
func (s *AccessService) MenuRoleIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().MenuRoleIDs(ctx, id)
}
func (s *AccessService) SetMenuRoles(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetMenuRoles(ctx, id, ids)
}
func (s *AccessService) ReplaceMenuButtons(ctx context.Context, menuID uint, buttons []*biz.MenuButton) error {
return s.uc.Repo().ReplaceMenuButtons(ctx, menuID, buttons)
}
func (s *AccessService) APIs(ctx context.Context, page, size int, q *biz.API) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListAPIs(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, apiDTO(v))
}
return out, total, nil
}
func (s *AccessService) CreateAPI(ctx context.Context, v *biz.API) error {
return s.uc.Repo().CreateAPI(ctx, v)
}
func (s *AccessService) UpdateAPI(ctx context.Context, v *biz.API) error {
return s.uc.Repo().UpdateAPI(ctx, v)
}
func (s *AccessService) DeleteAPIs(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteAPIs(ctx, ids)
}
func (s *AccessService) API(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindAPI(ctx, id)
if err != nil {
return nil, err
}
return apiDTO(v), nil
}
func (s *AccessService) APIRoleIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().APIRoleIDs(ctx, id)
}
func (s *AccessService) SetAPIRoles(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetAPIRoles(ctx, id, ids)
}
func (s *AccessService) PolicyPaths(ctx context.Context, id uint) ([]map[string]any, error) {
items, err := s.uc.Repo().PolicyPaths(ctx, id)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"path": v.Path, "method": v.Method})
}
return out, nil
}
func (s *AccessService) SetPolicyPaths(ctx context.Context, id uint, paths []*biz.API) error {
return s.uc.Repo().SetPolicyPaths(ctx, id, paths)
}
func (s *AccessService) SyncAPIs(ctx context.Context, routes []*biz.API) (map[string]any, error) {
stored, _, err := s.uc.Repo().ListAPIs(ctx, 1, 100000, nil)
if err != nil {
return nil, err
}
ignored, err := s.uc.Repo().IgnoredAPIs(ctx)
if err != nil {
return nil, err
}
key := func(v *biz.API) string { return strings.ToUpper(v.Method) + " " + v.Path }
ignoreSet := make(map[string]bool, len(ignored))
for _, item := range ignored {
ignoreSet[key(item)] = true
}
routeSet := make(map[string]*biz.API, len(routes))
for _, item := range routes {
if !ignoreSet[key(item)] {
routeSet[key(item)] = item
}
}
storedSet := make(map[string]*biz.API, len(stored))
for _, item := range stored {
storedSet[key(item)] = item
}
added := make([]map[string]any, 0)
deleted := make([]map[string]any, 0)
ignoredDTO := make([]map[string]any, 0, len(ignored))
for k, item := range routeSet {
if storedSet[k] == nil {
added = append(added, apiDTO(item))
}
}
for k, item := range storedSet {
if routeSet[k] == nil && !ignoreSet[k] {
deleted = append(deleted, apiDTO(item))
}
}
for _, item := range ignored {
ignoredDTO = append(ignoredDTO, apiDTO(item))
}
return map[string]any{"newApis": added, "deleteApis": deleted, "ignoreApis": ignoredDTO}, nil
}
func (s *AccessService) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error {
return s.uc.Repo().SetAPIIgnored(ctx, path, method, ignored)
}
func (s *AccessService) ApplyAPISync(ctx context.Context, added, deleted []*biz.API) error {
return s.uc.Repo().ApplyAPISync(ctx, added, deleted)
}
func (s *AccessService) Buttons(ctx context.Context, id uint) ([]map[string]any, error) {
items, err := s.uc.Repo().Buttons(ctx, id)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"ID": v.ID, "name": v.Name, "desc": v.Description, "sysBaseMenuID": v.MenuID})
}
return out, nil
}
func (s *AccessService) SetAuthorityButtons(ctx context.Context, id uint, v map[uint][]uint) error {
return s.uc.Repo().SetAuthorityButtons(ctx, id, v)
}
func (s *AccessService) AuthorityButtonIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().AuthorityButtonIDs(ctx, id)
}
func (s *AccessService) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
return s.uc.Repo().SelectedButtons(ctx, aid, menuID)
}
func (s *AccessService) SetSelectedButtons(ctx context.Context, aid, menuID uint, ids []uint) error {
return s.uc.Repo().SetSelectedButtons(ctx, aid, menuID, ids)
}
func (s *AccessService) CanRemoveButton(ctx context.Context, id uint) (bool, error) {
return s.uc.Repo().CanRemoveButton(ctx, id)
}
func (s *AccessService) Departments(ctx context.Context, name string) ([]map[string]any, error) {
items, err := s.uc.Repo().ListDepartments(ctx, name)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, departmentDTO(v))
}
return out, nil
}
func (s *AccessService) CreateDepartment(ctx context.Context, v *biz.Department) error {
return s.uc.Repo().CreateDepartment(ctx, v)
}
func (s *AccessService) UpdateDepartment(ctx context.Context, v *biz.Department) error {
return s.uc.Repo().UpdateDepartment(ctx, v)
}
func (s *AccessService) DeleteDepartment(ctx context.Context, id uint) error {
return s.uc.Repo().DeleteDepartment(ctx, id)
}
func (s *AccessService) Department(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindDepartment(ctx, id)
if err != nil {
return nil, err
}
return departmentDTO(v), nil
}
func (s *AccessService) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().DepartmentUserIDs(ctx, id)
}
func (s *AccessService) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetDepartmentUsers(ctx, id, ids)
}
func (s *AccessService) SetUserDepartments(ctx context.Context, id uint, ids []uint, primary uint) error {
return s.uc.Repo().SetUserDepartments(ctx, id, ids, primary)
}
func (s *AccessService) Positions(ctx context.Context, page, size int, q *biz.Position) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListPositions(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, positionDTO(v))
}
return out, total, nil
}
func (s *AccessService) CreatePosition(ctx context.Context, v *biz.Position) error {
return s.uc.Repo().CreatePosition(ctx, v)
}
func (s *AccessService) UpdatePosition(ctx context.Context, v *biz.Position) error {
return s.uc.Repo().UpdatePosition(ctx, v)
}
func (s *AccessService) DeletePosition(ctx context.Context, id uint) error {
return s.uc.Repo().DeletePosition(ctx, id)
}
func (s *AccessService) Position(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindPosition(ctx, id)
if err != nil {
return nil, err
}
return positionDTO(v), nil
}
func (s *AccessService) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) {
return s.uc.Repo().PositionUserIDs(ctx, id)
}
func (s *AccessService) SetPositionUsers(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetPositionUsers(ctx, id, ids)
}
func (s *AccessService) SetUserPositions(ctx context.Context, id uint, ids []uint) error {
return s.uc.Repo().SetUserPositions(ctx, id, ids)
}
+143
View File
@@ -0,0 +1,143 @@
package service
import (
"context"
"kra/internal/biz"
)
type AuditService struct{ uc *biz.AuditUsecase }
func NewAuditService(uc *biz.AuditUsecase) *AuditService { return &AuditService{uc: uc} }
func (s *AuditService) RecordOperation(ctx context.Context, v *biz.OperationRecord) error {
return s.uc.Repo().RecordOperation(ctx, v)
}
func (s *AuditService) RecordLogin(ctx context.Context, v *biz.LoginLog) error {
return s.uc.Repo().RecordLogin(ctx, v)
}
func (s *AuditService) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error {
return s.uc.Repo().RecordDataAccess(ctx, v)
}
func opDTO(v *biz.OperationRecord) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "ip": v.IP, "method": v.Method, "path": v.Path, "status": v.Status, "latency_ms": v.LatencyMS, "agent": v.Agent, "error_message": v.ErrorMessage, "body": v.Body, "resp": v.Response, "user_id": v.UserID, "request_id": v.RequestID, "trace_id": v.TraceID, "device_id": v.DeviceID, "user": nil}
}
func loginDTO(v *biz.LoginLog) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "username": v.Username, "ip": v.IP, "status": v.Status, "errorMessage": v.ErrorMessage, "agent": v.Agent, "userId": v.UserID, "user": nil}
}
func dataAccessDTO(v *biz.DataAccessLog) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "eventType": v.EventType, "targetTable": v.TargetTable, "operation": v.Operation, "userId": v.UserID, "authorityId": v.AuthorityID, "scope": v.Scope, "requestId": v.RequestID, "method": v.Method, "path": v.Path, "detail": v.Detail}
}
func (s *AuditService) Operations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListOperations(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, opDTO(v))
}
return out, total, nil
}
func (s *AuditService) Operation(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindOperation(ctx, id)
if err != nil {
return nil, err
}
return opDTO(v), nil
}
func (s *AuditService) DeleteOperations(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteOperations(ctx, ids)
}
func (s *AuditService) Logins(ctx context.Context, page, size int, q *biz.LoginLog) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListLogins(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, loginDTO(v))
}
return out, total, nil
}
func (s *AuditService) Login(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindLogin(ctx, id)
if err != nil {
return nil, err
}
return loginDTO(v), nil
}
func (s *AuditService) DeleteLogins(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteLogins(ctx, ids)
}
func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListDataAccess(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, dataAccessDTO(v))
}
return out, total, nil
}
func (s *AuditService) DeleteDataAccess(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteDataAccess(ctx, ids)
}
func (s *AuditService) LogDates(ctx context.Context, month string) (map[string]any, error) {
items, err := s.uc.Repo().LogDates(ctx, month)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"date": v.Date, "fileCount": v.FileCount})
}
return map[string]any{"month": month, "dates": out}, nil
}
func (s *AuditService) LogFiles(ctx context.Context, date string) (map[string]any, error) {
items, err := s.uc.Repo().LogFiles(ctx, date)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"path": v.Path, "name": v.Name, "size": v.Size, "modifiedAt": v.ModifiedAt})
}
return map[string]any{"date": date, "files": out}, nil
}
func (s *AuditService) LogContent(ctx context.Context, date, path string, cursor *int64) (map[string]any, error) {
v, err := s.uc.Repo().LogContent(ctx, date, path, cursor)
if err != nil {
return nil, err
}
return map[string]any{"date": v.Date, "path": v.Path, "content": v.Content, "lineCount": v.LineCount, "nextCursor": v.NextCursor, "hasMore": v.HasMore, "limitedByBytes": v.LimitedByBytes, "size": v.Size, "modifiedAt": v.ModifiedAt}, nil
}
func errorDTO(v *biz.ErrorRecord) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "form": v.Form, "info": v.Info, "level": v.Level, "request_id": v.RequestID, "trace_id": v.TraceID, "solution": v.Solution, "status": v.Status}
}
func (s *AuditService) CreateError(ctx context.Context, v *biz.ErrorRecord) error {
return s.uc.Repo().CreateError(ctx, v)
}
func (s *AuditService) UpdateError(ctx context.Context, v *biz.ErrorRecord) error {
return s.uc.Repo().UpdateError(ctx, v)
}
func (s *AuditService) DeleteErrors(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteErrors(ctx, ids)
}
func (s *AuditService) Error(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindError(ctx, id)
if err != nil {
return nil, err
}
return errorDTO(v), nil
}
func (s *AuditService) Errors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListErrors(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, errorDTO(v))
}
return out, total, nil
}
+184
View File
@@ -0,0 +1,184 @@
package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/xuri/excelize/v2"
"io"
"kra/internal/biz"
"sort"
)
type ExportService struct{ uc *biz.ExportUsecase }
func NewExportService(uc *biz.ExportUsecase) *ExportService { return &ExportService{uc: uc} }
func exportDTO(v *biz.ExportTemplate) map[string]any {
conditions := make([]map[string]any, 0, len(v.Conditions))
for _, x := range v.Conditions {
conditions = append(conditions, map[string]any{"from": x.From, "column": x.Column, "operator": x.Operator})
}
joins := make([]map[string]any, 0, len(v.Joins))
for _, x := range v.Joins {
joins = append(joins, map[string]any{"joins": x.Join, "table": x.Table, "on": x.On})
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "dbName": v.DBName, "name": v.Name, "tableName": v.TableName, "templateID": v.TemplateID, "templateInfo": v.TemplateInfo, "sql": v.SQL, "importSql": v.ImportSQL, "limit": v.Limit, "order": v.Order, "conditions": conditions, "joinTemplate": joins}
}
func (s *ExportService) Create(ctx context.Context, v *biz.ExportTemplate) error {
return s.uc.Repo().CreateExportTemplate(ctx, v)
}
func (s *ExportService) Update(ctx context.Context, v *biz.ExportTemplate) error {
return s.uc.Repo().UpdateExportTemplate(ctx, v)
}
func (s *ExportService) Delete(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteExportTemplates(ctx, ids)
}
func (s *ExportService) Template(ctx context.Context, id uint, tid string) (map[string]any, error) {
v, err := s.uc.Repo().FindExportTemplate(ctx, id, tid)
if err != nil {
return nil, err
}
return exportDTO(v), nil
}
func (s *ExportService) Templates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListExportTemplates(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, exportDTO(v))
}
return out, total, nil
}
func templateColumns(raw string) ([]string, map[string]string, error) {
labels := map[string]string{}
if err := json.Unmarshal([]byte(raw), &labels); err != nil {
return nil, nil, err
}
keys := make([]string, 0, len(labels))
for key := range labels {
keys = append(keys, key)
}
sort.Strings(keys)
return keys, labels, nil
}
func (s *ExportService) Export(ctx context.Context, tid string, params map[string]string) ([]byte, string, error) {
template, err := s.uc.Repo().FindExportTemplate(ctx, 0, tid)
if err != nil {
return nil, "", err
}
rows, _, err := s.uc.Repo().QueryExport(ctx, template, params)
if err != nil {
return nil, "", err
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return nil, "", err
}
file := excelize.NewFile()
sheet := file.GetSheetName(0)
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, 1)
_ = file.SetCellValue(sheet, cell, labels[key])
}
for rowIndex, row := range rows {
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, rowIndex+2)
_ = file.SetCellValue(sheet, cell, row[key])
}
}
buffer, err := file.WriteToBuffer()
_ = file.Close()
if err != nil {
return nil, "", err
}
return buffer.Bytes(), template.Name + ".xlsx", nil
}
func (s *ExportService) ExportBlankTemplate(ctx context.Context, tid string) ([]byte, string, error) {
template, err := s.uc.Repo().FindExportTemplate(ctx, 0, tid)
if err != nil {
return nil, "", err
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return nil, "", err
}
file := excelize.NewFile()
sheet := file.GetSheetName(0)
for column, key := range keys {
cell, _ := excelize.CoordinatesToCellName(column+1, 1)
_ = file.SetCellValue(sheet, cell, labels[key])
}
buffer, err := file.WriteToBuffer()
_ = file.Close()
if err != nil {
return nil, "", err
}
return buffer.Bytes(), template.Name + "_template.xlsx", nil
}
func (s *ExportService) Preview(ctx context.Context, tid string, params map[string]string) (string, error) {
template, err := s.uc.Repo().FindExportTemplate(ctx, 0, tid)
if err != nil {
return "", err
}
_, sql, err := s.uc.Repo().QueryExport(ctx, template, params)
return sql, err
}
func (s *ExportService) Import(ctx context.Context, tid string, reader io.Reader) error {
template, err := s.uc.Repo().FindExportTemplate(ctx, 0, tid)
if err != nil {
return err
}
file, err := excelize.OpenReader(reader)
if err != nil {
return err
}
defer file.Close()
sheets := file.GetSheetList()
if len(sheets) == 0 {
return errors.New("工作簿没有工作表")
}
rows, err := file.GetRows(sheets[0])
if err != nil {
return err
}
if len(rows) < 2 {
return nil
}
keys, labels, err := templateColumns(template.TemplateInfo)
if err != nil {
return err
}
reverse := map[string]string{}
for _, key := range keys {
reverse[labels[key]] = key
}
headers := rows[0]
items := make([]map[string]any, 0, len(rows)-1)
for rowIndex, row := range rows[1:] {
item := map[string]any{}
empty := true
for column, value := range row {
if column >= len(headers) {
continue
}
key := reverse[headers[column]]
if key != "" {
item[key] = value
if value != "" {
empty = false
}
}
}
if !empty {
items = append(items, item)
}
_ = rowIndex
}
if err = s.uc.Repo().ImportExportRows(ctx, template, items); err != nil {
return fmt.Errorf("导入失败: %w", err)
}
return nil
}
+104
View File
@@ -0,0 +1,104 @@
package service
import (
"context"
"io"
"kra/internal/biz"
)
type MediaService struct{ uc *biz.MediaUsecase }
func NewMediaService(uc *biz.MediaUsecase) *MediaService { return &MediaService{uc: uc} }
func mediaDTO(v *biz.MediaFile) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "classId": v.CategoryID, "url": v.URL, "tag": v.Tag, "key": v.Key, "size": v.Size, "mime": v.Mime, "md5": v.MD5, "userId": v.UserID}
}
func categoryDTO(v *biz.AttachmentCategory) map[string]any {
children := make([]map[string]any, 0, len(v.Children))
for _, x := range v.Children {
children = append(children, categoryDTO(x))
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "pid": v.ParentID, "children": children}
}
func (s *MediaService) Upload(ctx context.Context, userID uint, name, mime string, categoryID int, reader io.Reader, save bool) (map[string]any, error) {
v, err := s.uc.Upload(ctx, userID, name, mime, categoryID, reader, save)
if err != nil {
return nil, err
}
return mediaDTO(v), nil
}
func (s *MediaService) Media(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindMedia(ctx, id)
if err != nil {
return nil, err
}
return mediaDTO(v), nil
}
func (s *MediaService) MediaList(ctx context.Context, page, size int, keyword string, categoryID int, tag string, userID uint) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListMedia(ctx, page, size, keyword, categoryID, tag, userID)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, mediaDTO(v))
}
return out, total, nil
}
func (s *MediaService) Delete(ctx context.Context, id uint) error { return s.uc.Delete(ctx, id) }
func (s *MediaService) Rename(ctx context.Context, id uint, name string) error {
return s.uc.Repo().UpdateMediaName(ctx, id, name)
}
func (s *MediaService) ImportURLs(ctx context.Context, items []*biz.MediaFile) error {
return s.uc.Repo().CreateMediaBatch(ctx, items)
}
func (s *MediaService) Storage(ctx context.Context, prefix, cursor string, limit int) ([]map[string]any, string, bool, error) {
items, next, more, err := s.uc.ListStorage(ctx, prefix, cursor, limit)
if err != nil {
return nil, "", false, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"name": v.Name, "key": v.Path, "url": v.URL, "size": v.Size})
}
return out, next, more, nil
}
func (s *MediaService) Categories(ctx context.Context) ([]map[string]any, error) {
items, err := s.uc.Repo().ListCategories(ctx)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, categoryDTO(v))
}
return out, nil
}
func (s *MediaService) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error {
return s.uc.Repo().SaveCategory(ctx, v)
}
func (s *MediaService) DeleteCategory(ctx context.Context, id uint) error {
return s.uc.Repo().DeleteCategory(ctx, id)
}
func (s *MediaService) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (map[string]any, error) {
session, media, chunks, err := s.uc.InitUpload(ctx, userID, name, hash, size, chunkSize, total)
if err != nil {
return nil, err
}
if media != nil {
return map[string]any{"instant": true, "media": mediaDTO(media), "uploadedChunks": []int{}}, nil
}
return map[string]any{"instant": false, "uploadId": session.ID, "uploadedChunks": chunks, "media": nil}, nil
}
func (s *MediaService) SaveChunk(ctx context.Context, userID, uploadID uint, index int, hash string, reader io.Reader) error {
return s.uc.SaveChunk(ctx, userID, uploadID, index, hash, reader)
}
func (s *MediaService) CompleteUpload(ctx context.Context, userID, uploadID uint) (map[string]any, error) {
v, err := s.uc.CompleteUpload(ctx, userID, uploadID, "")
if err != nil {
return nil, err
}
return mediaDTO(v), nil
}
func (s *MediaService) CancelUpload(ctx context.Context, userID, uploadID uint) error {
return s.uc.CancelUpload(ctx, userID, uploadID)
}
+1 -1
View File
@@ -3,4 +3,4 @@ package service
import "github.com/google/wire"
// ProviderSet is service providers.
var ProviderSet = wire.NewSet(NewAdminService)
var ProviderSet = wire.NewSet(NewAdminService, NewSystemService, NewAccessService, NewSettingsService, NewVersionService, NewExportService, NewAuditService, NewTaskService, NewMediaService)
+274
View File
@@ -0,0 +1,274 @@
package service
import (
"context"
"errors"
"sync"
"time"
"kra/internal/biz"
"kra/internal/conf"
"kra/pkg/adminauth"
)
type SettingsService struct {
uc *biz.SettingsUsecase
config *conf.AdminBackend
securityMu sync.RWMutex
securityCache *biz.SecurityConfig
}
func NewSettingsService(uc *biz.SettingsUsecase, config *conf.AdminBackend) *SettingsService {
return &SettingsService{uc: uc, config: config}
}
func (s *SettingsService) Repo() biz.SettingsRepo { return s.uc.Repo() }
func dictionaryDTO(v *biz.Dictionary) map[string]any {
children := make([]map[string]any, 0, len(v.Children))
for _, x := range v.Children {
children = append(children, dictionaryDTO(x))
}
details := make([]map[string]any, 0, len(v.Details))
for _, x := range v.Details {
details = append(details, detailDTO(x))
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parentID": v.ParentID, "children": children, "sysDictionaryDetails": details}
}
func detailDTO(v *biz.DictionaryDetail) map[string]any {
children := make([]map[string]any, 0, len(v.Children))
for _, x := range v.Children {
children = append(children, detailDTO(x))
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "label": v.Label, "value": v.Value, "extend": v.Extend, "status": v.Status, "sort": v.Sort, "sysDictionaryID": v.DictionaryID, "parentID": v.ParentID, "level": v.Level, "path": v.Path, "disabled": !v.Status, "children": children}
}
func parameterDTO(v *biz.SystemParameter) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "key": v.Key, "value": v.Value, "desc": v.Desc}
}
func tokenDTO(v *biz.APIToken) map[string]any {
var user any = nil
if v.User != nil {
user = convertUser(v.User)
}
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "userId": v.UserID, "authorityId": v.AuthorityID, "token": v.Token, "status": v.Status, "expiresAt": v.ExpiresAt, "remark": v.Remark, "user": user}
}
func (s *SettingsService) Dictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]map[string]any, int64, error) {
items, total, err := s.Repo().ListDictionaries(ctx, page, size, name, typ, details)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, dictionaryDTO(v))
}
return out, total, nil
}
func (s *SettingsService) Dictionary(ctx context.Context, id uint, typ string, details bool) (map[string]any, error) {
v, err := s.Repo().FindDictionary(ctx, id, typ, details)
if err != nil {
return nil, err
}
return dictionaryDTO(v), nil
}
func (s *SettingsService) CreateDictionary(ctx context.Context, v *biz.Dictionary) error {
return s.Repo().CreateDictionary(ctx, v)
}
func (s *SettingsService) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error {
return s.Repo().UpdateDictionary(ctx, v)
}
func (s *SettingsService) DeleteDictionary(ctx context.Context, id uint) error {
return s.Repo().DeleteDictionary(ctx, id)
}
func (s *SettingsService) DictionaryDetails(ctx context.Context, page, size int, dictionaryID uint, label, value string) ([]map[string]any, int64, error) {
items, total, err := s.Repo().ListDictionaryDetails(ctx, page, size, dictionaryID, label, value)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, detailDTO(v))
}
return out, total, nil
}
func (s *SettingsService) DictionaryDetail(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.Repo().FindDictionaryDetail(ctx, id)
if err != nil {
return nil, err
}
return detailDTO(v), nil
}
func (s *SettingsService) DictionaryTree(ctx context.Context, id uint, typ string) ([]map[string]any, error) {
items, err := s.Repo().DictionaryDetailTree(ctx, id, typ)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, detailDTO(v))
}
return out, nil
}
func (s *SettingsService) CreateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
return s.Repo().CreateDictionaryDetail(ctx, v)
}
func (s *SettingsService) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
return s.Repo().UpdateDictionaryDetail(ctx, v)
}
func (s *SettingsService) DeleteDictionaryDetail(ctx context.Context, id uint) error {
return s.Repo().DeleteDictionaryDetail(ctx, id)
}
func (s *SettingsService) Parameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]map[string]any, int64, error) {
items, total, err := s.Repo().ListParameters(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, parameterDTO(v))
}
return out, total, nil
}
func (s *SettingsService) Parameter(ctx context.Context, id uint, key string) (map[string]any, error) {
v, err := s.Repo().FindParameter(ctx, id, key)
if err != nil {
return nil, err
}
return parameterDTO(v), nil
}
func (s *SettingsService) CreateParameter(ctx context.Context, v *biz.SystemParameter) error {
return s.Repo().CreateParameter(ctx, v)
}
func (s *SettingsService) UpdateParameter(ctx context.Context, v *biz.SystemParameter) error {
return s.Repo().UpdateParameter(ctx, v)
}
func (s *SettingsService) DeleteParameters(ctx context.Context, ids []uint) error {
return s.Repo().DeleteParameters(ctx, ids)
}
func (s *SettingsService) CreateAPIToken(ctx context.Context, userID, authorityID uint, days int, remark string) (string, error) {
user, allowed, err := s.Repo().UserHasAuthority(ctx, userID, authorityID)
if err != nil {
return "", err
}
if !allowed {
return "", errors.New("用户不具备该角色权限")
}
duration := time.Duration(days) * 24 * time.Hour
if days == -1 {
duration = 100 * 365 * 24 * time.Hour
}
if duration <= 0 {
return "", errors.New("有效天数必须大于0或为-1")
}
secret, issuer := "", "kra"
if s.config != nil && s.config.Jwt != nil {
secret = s.config.Jwt.SigningKey
if s.config.Jwt.Issuer != "" {
issuer = s.config.Jwt.Issuer
}
}
token, _, err := adminauth.Generate(secret, issuer, duration, user.ID, authorityID, user.UUID, user.Username, user.NickName, false)
if err != nil {
return "", err
}
record := &biz.APIToken{UserID: userID, AuthorityID: authorityID, Token: token, Status: true, ExpiresAt: time.Now().Add(duration), Remark: remark}
if err = s.Repo().CreateAPIToken(ctx, record); err != nil {
return "", err
}
return token, nil
}
func (s *SettingsService) APITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]map[string]any, int64, error) {
items, total, err := s.Repo().ListAPITokens(ctx, page, size, userID, status)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, tokenDTO(v))
}
return out, total, nil
}
func (s *SettingsService) DisableAPIToken(ctx context.Context, id uint) error {
_, err := s.Repo().DisableAPIToken(ctx, id)
return err
}
func (s *SettingsService) IsTokenDisabled(ctx context.Context, token string) (bool, error) {
return s.Repo().IsTokenDisabled(ctx, token)
}
func securityDTO(v *biz.SecurityConfig) map[string]any {
return map[string]any{"ID": v.ID, "captchaOpen": v.CaptchaOpen, "captchaTimeout": v.CaptchaTimeout, "keyLong": v.KeyLong, "imgWidth": v.ImgWidth, "imgHeight": v.ImgHeight, "pwdMinLength": v.PwdMinLength, "pwdRequireUpper": v.PwdRequireUpper, "pwdRequireLower": v.PwdRequireLower, "pwdRequireDigit": v.PwdRequireDigit, "pwdRequireSpecial": v.PwdRequireSpecial, "limitEnable": v.LimitEnable, "limitWindow": v.LimitWindow, "limitCount": v.LimitCount, "lockEnable": v.LockEnable, "lockThreshold": v.LockThreshold, "lockDuration": v.LockDuration, "pwdExpireEnable": v.PwdExpireEnable, "pwdExpireDays": v.PwdExpireDays, "forceNewUserChangePassword": v.ForceNewUserChangePassword}
}
func (s *SettingsService) CurrentSecurity(ctx context.Context) (*biz.SecurityConfig, error) {
s.securityMu.RLock()
if s.securityCache != nil {
copy := *s.securityCache
s.securityMu.RUnlock()
return &copy, nil
}
s.securityMu.RUnlock()
value, err := s.Repo().SecurityConfig(ctx)
if err != nil {
return nil, err
}
s.securityMu.Lock()
s.securityCache = value
copy := *value
s.securityMu.Unlock()
return &copy, nil
}
func (s *SettingsService) Security(ctx context.Context) (map[string]any, error) {
value, err := s.CurrentSecurity(ctx)
if err != nil {
return nil, err
}
return securityDTO(value), nil
}
func (s *SettingsService) SaveSecurity(ctx context.Context, value *biz.SecurityConfig) (map[string]any, error) {
if value.KeyLong < 1 || value.ImgWidth < 1 || value.ImgHeight < 1 || value.PwdMinLength < 1 || value.LimitWindow < 1 || value.LimitCount < 1 || value.LockThreshold < 1 || value.LockDuration < 1 || value.PwdExpireDays < 1 {
return nil, errors.New("安全配置数值必须大于0")
}
if err := s.Repo().SaveSecurityConfig(ctx, value); err != nil {
return nil, err
}
s.securityMu.Lock()
s.securityCache = value
s.securityMu.Unlock()
return securityDTO(value), nil
}
func (s *SettingsService) ValidatePassword(ctx context.Context, password string) error {
cfg, err := s.CurrentSecurity(ctx)
if err != nil {
return err
}
if len([]rune(password)) < cfg.PwdMinLength {
return errors.New("密码长度不足")
}
hasUpper, hasLower, hasDigit, hasSpecial := false, false, false, false
for _, ch := range password {
switch {
case ch >= 'A' && ch <= 'Z':
hasUpper = true
case ch >= 'a' && ch <= 'z':
hasLower = true
case ch >= '0' && ch <= '9':
hasDigit = true
default:
hasSpecial = true
}
}
if cfg.PwdRequireUpper && !hasUpper {
return errors.New("密码必须包含大写字母")
}
if cfg.PwdRequireLower && !hasLower {
return errors.New("密码必须包含小写字母")
}
if cfg.PwdRequireDigit && !hasDigit {
return errors.New("密码必须包含数字")
}
if cfg.PwdRequireSpecial && !hasSpecial {
return errors.New("密码必须包含特殊字符")
}
return nil
}
+244
View File
@@ -0,0 +1,244 @@
package service
import (
"context"
"time"
"kra/internal/biz"
"kra/internal/conf"
"kra/pkg/adminauth"
)
type SystemService struct {
uc *biz.SystemUsecase
config *conf.AdminBackend
settings *SettingsService
}
type LoginResult struct {
User map[string]any `json:"user"`
Token string `json:"token"`
ExpiresAt int64 `json:"expiresAt"`
NeedChangePassword bool `json:"needChangePassword"`
}
func NewSystemService(uc *biz.SystemUsecase, config *conf.AdminBackend, settings *SettingsService) *SystemService {
return &SystemService{uc: uc, config: config, settings: settings}
}
func (s *SystemService) IsInitialized(ctx context.Context) (bool, error) {
return s.uc.IsInitialized(ctx)
}
func (s *SystemService) Initialize(ctx context.Context) error { return s.uc.Initialize(ctx) }
func (s *SystemService) CacheGet(ctx context.Context, key string) (string, bool, error) {
return s.uc.CacheGet(ctx, key)
}
func (s *SystemService) CacheSet(ctx context.Context, key, value string, expiration time.Duration) error {
return s.uc.CacheSet(ctx, key, value, expiration)
}
func (s *SystemService) CacheDelete(ctx context.Context, key string) error {
return s.uc.CacheDelete(ctx, key)
}
func (s *SystemService) Login(ctx context.Context, username, password string) (*LoginResult, error) {
u, err := s.uc.Login(ctx, username, password)
if err != nil {
return nil, err
}
if u.Enable != 1 {
return nil, biz.ErrInvalidCredentials
}
if security, securityErr := s.settings.CurrentSecurity(ctx); securityErr == nil && security.PwdExpireEnable && u.PasswordUpdatedAt != nil && time.Since(*u.PasswordUpdatedAt) > time.Duration(security.PwdExpireDays)*24*time.Hour {
u.MustChangePassword = true
}
expires := 7 * 24 * time.Hour
secret, issuer := "", "kra"
if s.config != nil && s.config.Jwt != nil {
secret, issuer = s.config.Jwt.SigningKey, s.config.Jwt.Issuer
if s.config.Jwt.ExpiresTime != nil {
expires = s.config.Jwt.ExpiresTime.AsDuration()
}
}
token, claims, err := adminauth.Generate(secret, issuer, expires, u.ID, u.AuthorityID, u.UUID, u.Username, u.NickName, u.MustChangePassword)
if err != nil {
return nil, err
}
return &LoginResult{User: convertUser(u), Token: token, ExpiresAt: claims.ExpiresAt.UnixMilli(), NeedChangePassword: u.MustChangePassword}, nil
}
func (s *SystemService) User(ctx context.Context, id uint) (map[string]any, error) {
u, err := s.uc.User(ctx, id)
if err != nil {
return nil, err
}
return convertUser(u), nil
}
func (s *SystemService) Menus(ctx context.Context, authorityID uint) ([]map[string]any, error) {
menus, err := s.uc.Menus(ctx, authorityID)
if err != nil {
return nil, err
}
result := make([]map[string]any, 0, len(menus))
for _, menu := range menus {
result = append(result, convertMenu(menu))
}
return result, nil
}
type UserInput struct {
ID uint
Username, Password, NickName, HeaderImg, Phone, Email string
AuthorityID uint
Enable int
AuthorityIDs []uint
}
func (s *SystemService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]map[string]any, int64, error) {
users, total, err := s.uc.ListUsers(ctx, page, pageSize, filter)
if err != nil {
return nil, 0, err
}
result := make([]map[string]any, 0, len(users))
for _, u := range users {
result = append(result, convertUser(u))
}
return result, total, nil
}
func (s *SystemService) CreateUser(ctx context.Context, in UserInput) (map[string]any, error) {
if err := s.settings.ValidatePassword(ctx, in.Password); err != nil {
return nil, err
}
security, _ := s.settings.CurrentSecurity(ctx)
mustChange := security != nil && security.ForceNewUserChangePassword
if in.AuthorityID == 0 && len(in.AuthorityIDs) > 0 {
in.AuthorityID = in.AuthorityIDs[0]
}
in.AuthorityIDs = includeAuthority(in.AuthorityIDs, in.AuthorityID)
u, err := s.uc.CreateUser(ctx, &biz.User{Username: in.Username, Password: in.Password, NickName: in.NickName, HeaderImg: in.HeaderImg, AuthorityID: in.AuthorityID, Phone: in.Phone, Email: in.Email, Enable: in.Enable, MustChangePassword: mustChange})
if err != nil {
return nil, err
}
if len(in.AuthorityIDs) > 0 {
if err = s.uc.SetUserAuthorities(ctx, u.ID, in.AuthorityIDs); err != nil {
return nil, err
}
u, err = s.uc.User(ctx, u.ID)
if err != nil {
return nil, err
}
}
return convertUser(u), nil
}
func (s *SystemService) UpdateUser(ctx context.Context, in UserInput) error {
in.AuthorityIDs = includeAuthority(in.AuthorityIDs, in.AuthorityID)
if err := s.uc.UpdateUser(ctx, &biz.User{ID: in.ID, NickName: in.NickName, HeaderImg: in.HeaderImg, AuthorityID: in.AuthorityID, Phone: in.Phone, Email: in.Email, Enable: in.Enable}); err != nil {
return err
}
if len(in.AuthorityIDs) > 0 {
return s.uc.SetUserAuthorities(ctx, in.ID, in.AuthorityIDs)
}
return nil
}
func includeAuthority(ids []uint, primary uint) []uint {
if primary == 0 {
return ids
}
for _, id := range ids {
if id == primary {
return ids
}
}
return append(ids, primary)
}
func (s *SystemService) DeleteUser(ctx context.Context, id uint) error {
return s.uc.DeleteUser(ctx, id)
}
func (s *SystemService) ResetPassword(ctx context.Context, id uint, password string) error {
if err := s.settings.ValidatePassword(ctx, password); err != nil {
return err
}
return s.uc.ResetPassword(ctx, id, password)
}
func (s *SystemService) ChangePassword(ctx context.Context, id uint, oldPassword, newPassword string) error {
if err := s.settings.ValidatePassword(ctx, newPassword); err != nil {
return err
}
return s.uc.ChangePassword(ctx, id, oldPassword, newPassword)
}
func (s *SystemService) Authorities(ctx context.Context) ([]map[string]any, error) {
authorities, err := s.uc.Authorities(ctx)
if err != nil {
return nil, err
}
result := make([]map[string]any, 0, len(authorities))
for _, a := range authorities {
result = append(result, convertAuthority(*a))
}
return result, nil
}
func (s *SystemService) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error {
return s.uc.SetUserAuthorities(ctx, id, authorityIDs)
}
func (s *SystemService) SwitchAuthority(ctx context.Context, id, authorityID uint) (*LoginResult, error) {
if err := s.uc.SetUserAuthority(ctx, id, authorityID); err != nil {
return nil, err
}
u, err := s.uc.User(ctx, id)
if err != nil {
return nil, err
}
expires := 7 * 24 * time.Hour
secret, issuer := "", "kra"
if s.config != nil && s.config.Jwt != nil {
secret, issuer = s.config.Jwt.SigningKey, s.config.Jwt.Issuer
if s.config.Jwt.ExpiresTime != nil {
expires = s.config.Jwt.ExpiresTime.AsDuration()
}
}
token, claims, err := adminauth.Generate(secret, issuer, expires, u.ID, authorityID, u.UUID, u.Username, u.NickName, u.MustChangePassword)
if err != nil {
return nil, err
}
return &LoginResult{User: convertUser(u), Token: token, ExpiresAt: claims.ExpiresAt.UnixMilli(), NeedChangePassword: u.MustChangePassword}, nil
}
func (s *SystemService) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error {
return s.uc.SetUserSetting(ctx, id, setting)
}
func convertAuthority(a biz.Authority) map[string]any {
return map[string]any{"authorityId": a.AuthorityID, "authorityName": a.AuthorityName, "parentId": a.ParentID, "children": []map[string]any{}, "menus": []any{}, "dataScope": a.DataScope, "defaultRouter": a.DefaultRouter}
}
func convertUser(u *biz.User) map[string]any {
authorities := make([]map[string]any, 0, len(u.Authorities))
for _, a := range u.Authorities {
authorities = append(authorities, convertAuthority(a))
}
departments := make([]map[string]any, 0, len(u.Departments))
for i := range u.Departments {
departments = append(departments, departmentDTO(&u.Departments[i]))
}
positions := make([]map[string]any, 0, len(u.Positions))
for i := range u.Positions {
positions = append(positions, positionDTO(&u.Positions[i]))
}
dept := map[string]any{}
if u.Department != nil {
dept = departmentDTO(u.Department)
}
return map[string]any{"ID": u.ID, "CreatedAt": u.CreatedAt, "UpdatedAt": u.UpdatedAt, "DeletedAt": nil, "uuid": u.UUID, "userName": u.Username, "nickName": u.NickName, "headerImg": u.HeaderImg, "authorityId": u.AuthorityID, "authority": convertAuthority(u.Authority), "authorities": authorities, "deptId": u.DeptID, "dept": dept, "departments": departments, "positions": positions, "phone": u.Phone, "email": u.Email, "enable": u.Enable, "originSetting": u.OriginSetting}
}
func convertMenu(menu *biz.Menu) map[string]any {
children := make([]map[string]any, 0, len(menu.Children))
for _, child := range menu.Children {
children = append(children, convertMenu(child))
}
buttons := make([]map[string]any, 0, len(menu.Buttons))
for _, button := range menu.Buttons {
buttons = append(buttons, map[string]any{"ID": button.ID, "name": button.Name, "desc": button.Description, "sysBaseMenuID": button.MenuID})
}
return map[string]any{"ID": menu.ID, "parentId": menu.ParentID, "path": menu.Path, "name": menu.Name, "hidden": menu.Hidden, "component": menu.Component, "sort": menu.Sort, "meta": map[string]any{"activeName": menu.ActiveName, "keepAlive": menu.KeepAlive, "defaultMenu": menu.DefaultMenu, "title": menu.Title, "icon": menu.Icon, "closeTab": menu.CloseTab, "transitionType": menu.TransitionType}, "children": children, "parameters": []any{}, "menuBtn": buttons}
}
+194
View File
@@ -0,0 +1,194 @@
package service
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/robfig/cron/v3"
"io"
"kra/internal/biz"
"net"
"net/http"
"net/url"
"strings"
"time"
)
type TaskService struct{ uc *biz.TaskUsecase }
func NewTaskService(uc *biz.TaskUsecase) *TaskService { return &TaskService{uc: uc} }
func (s *TaskService) Validate(v *biz.TimedTask) error {
if v.Name == "" {
return errors.New("任务名不能为空")
}
var err error
if v.WithSeconds {
_, err = cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor).Parse(v.Spec)
} else {
_, err = cron.ParseStandard(v.Spec)
}
if err != nil {
return fmt.Errorf("cron 表达式非法: %w", err)
}
switch v.ExecutorType {
case "method":
if v.MethodName != "ClearDB" {
return errors.New("方法未注册")
}
if len(v.Params) > 0 && !json.Valid(v.Params) {
return errors.New("params 必须是合法 JSON")
}
case "http":
parsed, parseErr := url.Parse(v.HTTPURL)
if parseErr != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Hostname() == "" || parsed.User != nil {
return errors.New("httpUrl 必须是合法的 http/https 地址")
}
if len(v.HTTPHeader) > 0 {
headers := map[string]string{}
if json.Unmarshal(v.HTTPHeader, &headers) != nil {
return errors.New("httpHeader 必须是 JSON 对象")
}
}
default:
return errors.New("executorType 必须为 method 或 http")
}
return nil
}
func (s *TaskService) Create(ctx context.Context, v *biz.TimedTask) error {
if err := s.Validate(v); err != nil {
return err
}
return s.uc.Repo().CreateTask(ctx, v)
}
func (s *TaskService) Update(ctx context.Context, v *biz.TimedTask) error {
if v.ID == 0 {
return errors.New("缺少任务 ID")
}
if err := s.Validate(v); err != nil {
return err
}
return s.uc.Repo().UpdateTask(ctx, v)
}
func (s *TaskService) Delete(ctx context.Context, id uint) error {
return s.uc.Repo().DeleteTask(ctx, id)
}
func (s *TaskService) Toggle(ctx context.Context, id uint, enabled bool) error {
return s.uc.Repo().ToggleTask(ctx, id, enabled)
}
func (s *TaskService) Task(ctx context.Context, id uint) (*biz.TimedTask, error) {
return s.uc.Repo().FindTask(ctx, id)
}
func taskDTO(v *biz.TimedTask, next *time.Time) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "name": v.Name, "description": v.Description, "spec": v.Spec, "withSeconds": v.WithSeconds, "executorType": v.ExecutorType, "methodName": v.MethodName, "params": json.RawMessage(v.Params), "httpUrl": v.HTTPURL, "httpMethod": v.HTTPMethod, "httpHeader": json.RawMessage(v.HTTPHeader), "httpBody": v.HTTPBody, "httpAllowPrivate": v.HTTPAllowPrivate, "enabled": v.Enabled, "nextRunAt": next}
}
func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask, next map[uint]time.Time) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListTasks(ctx, page, size, q)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
var ptr *time.Time
if value, ok := next[v.ID]; ok {
copy := value
ptr = &copy
}
out = append(out, taskDTO(v, ptr))
}
return out, total, nil
}
func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, status string) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListTaskLogs(ctx, page, size, taskID, status)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "DeletedAt": nil, "taskId": v.TaskID, "taskName": v.TaskName, "triggerType": v.TriggerType, "startedAt": v.StartedAt, "finishedAt": v.FinishedAt, "durationMs": v.DurationMS, "status": v.Status, "errorMsg": v.ErrorMsg, "output": v.Output})
}
return out, total, nil
}
func privateIP(ip net.IP) bool {
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsUnspecified()
}
func (s *TaskService) runHTTP(ctx context.Context, v *biz.TimedTask) (string, error) {
parsed, _ := url.Parse(v.HTTPURL)
if !v.HTTPAllowPrivate {
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", parsed.Hostname())
if err != nil {
return "", err
}
for _, ip := range ips {
if privateIP(ip) {
return "", errors.New("禁止访问内网或环回地址")
}
}
}
method := strings.ToUpper(v.HTTPMethod)
if method == "" {
method = http.MethodGet
}
request, err := http.NewRequestWithContext(ctx, method, v.HTTPURL, bytes.NewBufferString(v.HTTPBody))
if err != nil {
return "", err
}
headers := map[string]string{}
_ = json.Unmarshal(v.HTTPHeader, &headers)
for key, value := range headers {
request.Header.Set(key, value)
}
response, err := (&http.Client{Timeout: 30 * time.Second}).Do(request)
if err != nil {
return "", err
}
defer response.Body.Close()
body, _ := io.ReadAll(io.LimitReader(response.Body, 4096))
output := fmt.Sprintf("HTTP %d: %s", response.StatusCode, string(body))
if response.StatusCode < 200 || response.StatusCode >= 300 {
return output, fmt.Errorf("HTTP 状态码 %d", response.StatusCode)
}
return output, nil
}
func (s *TaskService) Run(ctx context.Context, v *biz.TimedTask, trigger string) (log *biz.TimedTaskLog) {
started := time.Now()
log = &biz.TimedTaskLog{TaskID: v.ID, TaskName: v.Name, TriggerType: trigger, StartedAt: started, Status: "success"}
defer func() {
if recovered := recover(); recovered != nil {
log.Status = "fail"
log.ErrorMsg = fmt.Sprint(recovered)
}
log.FinishedAt = time.Now()
log.DurationMS = log.FinishedAt.Sub(started).Milliseconds()
_ = s.uc.Repo().RecordTaskLog(context.Background(), log)
}()
runCtx, cancel := context.WithTimeout(ctx, 35*time.Second)
defer cancel()
var err error
switch v.ExecutorType {
case "method":
if v.MethodName == "ClearDB" {
days := 30
var params struct {
Days int `json:"days"`
}
_ = json.Unmarshal(v.Params, &params)
if params.Days > 0 {
days = params.Days
}
err = s.uc.Repo().CleanupLogs(runCtx, time.Now().AddDate(0, 0, -days))
log.Output = "过期日志清理完成"
}
case "http":
log.Output, err = s.runHTTP(runCtx, v)
}
if err != nil {
log.Status = "fail"
log.ErrorMsg = err.Error()
}
return log
}
func (s *TaskService) RegisteredMethods() []map[string]any {
return []map[string]any{{"name": "ClearDB", "description": "清理数据库过期日志(操作记录、登录日志、定时任务日志)"}}
}
+75
View File
@@ -0,0 +1,75 @@
package service
import (
"context"
"encoding/json"
"time"
"kra/internal/biz"
)
type VersionService struct{ uc *biz.VersionUsecase }
func NewVersionService(uc *biz.VersionUsecase) *VersionService { return &VersionService{uc: uc} }
func versionDTO(v *biz.Version) map[string]any {
return map[string]any{"ID": v.ID, "CreatedAt": v.CreatedAt, "UpdatedAt": v.UpdatedAt, "DeletedAt": nil, "versionName": v.Name, "versionCode": v.Code, "description": v.Description, "versionData": v.Data}
}
func (s *VersionService) Versions(ctx context.Context, page, size int, name, code string) ([]map[string]any, int64, error) {
items, total, err := s.uc.Repo().ListVersions(ctx, page, size, name, code)
if err != nil {
return nil, 0, err
}
out := make([]map[string]any, 0, len(items))
for _, v := range items {
out = append(out, versionDTO(v))
}
return out, total, nil
}
func (s *VersionService) Version(ctx context.Context, id uint) (map[string]any, error) {
v, err := s.uc.Repo().FindVersion(ctx, id)
if err != nil {
return nil, err
}
return versionDTO(v), nil
}
func (s *VersionService) VersionData(ctx context.Context, id uint) (string, string, error) {
v, err := s.uc.Repo().FindVersion(ctx, id)
if err != nil {
return "", "", err
}
return v.Data, v.Code, nil
}
func (s *VersionService) DeleteVersions(ctx context.Context, ids []uint) error {
return s.uc.Repo().DeleteVersions(ctx, ids)
}
func (s *VersionService) Export(ctx context.Context, name, code, description string, menuIDs, apiIDs, dictIDs []uint) error {
bundle, err := s.uc.Repo().BuildVersionBundle(ctx, menuIDs, apiIDs, dictIDs)
if err != nil {
return err
}
bundle.Name = name
bundle.Code = code
bundle.Description = description
bundle.ExportTime = time.Now().Format("2006-01-02 15:04:05")
menus := make([]map[string]any, 0, len(bundle.Menus))
for _, v := range bundle.Menus {
menus = append(menus, convertMenu(v))
}
apis := make([]map[string]any, 0, len(bundle.APIs))
for _, v := range bundle.APIs {
apis = append(apis, apiDTO(v))
}
dicts := make([]map[string]any, 0, len(bundle.Dictionaries))
for _, v := range bundle.Dictionaries {
dicts = append(dicts, dictionaryDTO(v))
}
payload := map[string]any{"version": map[string]any{"name": name, "code": code, "description": description, "exportTime": bundle.ExportTime}, "menus": menus, "apis": apis, "dictionaries": dicts}
raw, err := json.MarshalIndent(payload, "", " ")
if err != nil {
return err
}
return s.uc.Repo().CreateVersion(ctx, &biz.Version{Name: name, Code: code, Description: description, Data: string(raw)})
}
func (s *VersionService) Import(ctx context.Context, bundle *biz.VersionBundle) error {
return s.uc.Repo().ImportVersionBundle(ctx, bundle)
}