Эх сурвалжийг харах

feat(gateway): 更新网关组相关功能

- 添加编辑网关组功能
- 优化网关组列表展示
- 新增网关组删除接口
- 重构网关组相关代码,提高可维护性
fusu 1 сар өмнө
parent
commit
50bbfe9a55

+ 1 - 1
internal/handler/gatewaygroup.go

@@ -69,7 +69,7 @@ func (h *GatewayGroupHandler) EditGatewayGroup(ctx *gin.Context) {
 		return
 	}
 	defaults.SetDefaults(&req)
-	 err := h.gatewayGroupService.EditGatewayGroup(ctx, req)
+	 err := h.gatewayGroupService.EditGatewayGroupAdmin(ctx, req)
 	if err != nil {
 		v1.HandleError(ctx, http.StatusInternalServerError, err,nil)
 		return

+ 10 - 0
internal/repository/gatewaygroup.go

@@ -19,6 +19,7 @@ type GatewayGroupRepository interface {
 	GetGatewayGroupByHostId(ctx context.Context, hostId int64) (*[]model.GatewayGroup, error)
 	GetGatewayGroupByRuleId(ctx context.Context, ruleId int64) (*model.GatewayGroup, error)
 	GetGatewayGroupList(ctx context.Context,req v1.SearchGatewayGroupParams) (*v1.PaginatedResponse[model.GatewayGroup], error)
+	EditGatewayGroupById(ctx context.Context, req *model.GatewayGroup) error
 }
 
 func NewGatewayGroupRepository(
@@ -168,4 +169,13 @@ func (r *gatewayGroupRepository) GetGatewayGroupList(ctx context.Context,req v1.
 		TotalPages: int(math.Ceil(float64(total) / float64(pageSize))),
 
 	}, nil
+}
+
+func (r *gatewayGroupRepository) EditGatewayGroupById(ctx context.Context, req *model.GatewayGroup) error {
+
+	if err := r.DB(ctx).Model(&model.GatewayGroup{}).Where("id = ?", req.Id).Updates(req).Error; err != nil {
+		return err
+	}
+	return nil
+
 }

+ 1 - 1
internal/server/http.go

@@ -165,7 +165,7 @@ func NewHTTPServer(
 			strictAuthRouter.GET("/gateway/list", gatewayHandler.GetGatewayGroupList)
 			strictAuthRouter.POST("/gateway/add", gatewayHandler.AddGatewayGroup)
 			strictAuthRouter.PUT("/gateway/edit", gatewayHandler.EditGatewayGroup)
-			strictAuthRouter.POST("/gateway/del", gatewayHandler.DeleteGatewayGroup)
+			strictAuthRouter.DELETE("/gateway/del", gatewayHandler.DeleteGatewayGroup)
 		}
 	}
 

+ 68 - 25
internal/service/admin.go

@@ -330,51 +330,94 @@ func (s *adminService) MenuDelete(ctx context.Context, id uint) error {
 }
 
 func (s *adminService) GetMenus(ctx context.Context, uid uint) (*v1.GetMenuResponseData, error) {
+	// 步骤 1: 从数据库中一次性获取所有菜单项。
 	menuList, err := s.adminRepository.GetMenuList(ctx)
 	if err != nil {
 		s.logger.WithContext(ctx).Error("GetMenuList error", zap.Error(err))
 		return nil, err
 	}
-	data := &v1.GetMenuResponseData{
-		List: make([]v1.MenuDataItem, 0),
-	}
-	// 获取权限的菜单
+
+	// 步骤 2: 获取当前用户的完整权限列表。
 	permissions, err := s.adminRepository.GetUserPermissions(ctx, uid)
 	if err != nil {
 		return nil, err
 	}
-	menuPermMap := map[string]struct{}{}
+
+	// 步骤 3: 创建一个以菜单ID为键、菜单对象指针为值的映射(Map),用于快速查找。
+	// 这样可以避免在后续操作中反复遍历列表,提高效率。
+	allMenusMap := make(map[uint]*model.Menu)
+	for i := range menuList {
+		allMenusMap[menuList[i].ID] = &menuList[i]
+	}
+
+	// 步骤 4: 创建一个集合(Set),存放用户被明确授权可以访问的菜单路径。
+	permittedMenuPaths := make(map[string]struct{})
 	for _, permission := range permissions {
-		// 防呆设置,超管可以看到所有菜单
+		// 特殊处理:超级管理员(Admin)拥有所有菜单的权限。
 		if convertor.ToString(uid) == model.AdminUserID {
-			menuPermMap[strings.TrimPrefix(permission[1], model.MenuResourcePrefix)] = struct{}{}
+			// Casbin返回的权限格式通常是 [role, resource, action],我们取 resource (permission[1])。
+			// 并移除菜单资源特有的前缀,得到干净的菜单路径。
+			permittedMenuPaths[strings.TrimPrefix(permission[1], model.MenuResourcePrefix)] = struct{}{}
 		} else {
+			// 普通用户的权限策略,只处理菜单相关的权限。
 			if len(permission) == 3 && strings.HasPrefix(permission[1], model.MenuResourcePrefix) {
-				menuPermMap[strings.TrimPrefix(permission[1], model.MenuResourcePrefix)] = struct{}{}
+				permittedMenuPaths[strings.TrimPrefix(permission[1], model.MenuResourcePrefix)] = struct{}{}
 			}
 		}
 	}
 
+	// 步骤 5: 构建最终的菜单列表,核心逻辑是确保所有有权限访问的子菜单,其父菜单也一定会被包含进来。
+	// 使用 Map (finalMenusMap) 来存放最终结果,可以巧妙地实现自动去重。
+	finalMenusMap := make(map[uint]v1.MenuDataItem)
 	for _, menu := range menuList {
-		if _, ok := menuPermMap[menu.Path]; ok {
-			data.List = append(data.List, v1.MenuDataItem{
-				ID:         menu.ID,
-				Name:       menu.Name,
-				Title:      menu.Title,
-				Path:       menu.Path,
-				Component:  menu.Component,
-				Redirect:   menu.Redirect,
-				KeepAlive:  menu.KeepAlive,
-				HideInMenu: menu.HideInMenu,
-				Locale:     menu.Locale,
-				Weight:     menu.Weight,
-				Icon:       menu.Icon,
-				ParentID:   menu.ParentID,
-				UpdatedAt:  menu.UpdatedAt.Format("2006-01-02 15:04:05"),
-				URL:        menu.URL,
-			})
+		// 检查当前遍历到的菜单,其路径是否在用户的权限集合内。
+		if _, ok := permittedMenuPaths[menu.Path]; ok {
+			// 如果用户有权访问此菜单,则启动一个循环,向上追溯并添加其所有的父级菜单。
+			curr := &menu // 从当前这个有权限的菜单开始追溯。
+			for curr != nil {
+				// 如果当前菜单(或其父菜单)已经存在于最终结果中,说明这条路径已经处理过,跳出循环以避免重复工作。
+				if _, exists := finalMenusMap[curr.ID]; exists {
+					break
+				}
+
+				// 将当前菜单转换为API需要的数据结构,并添加到最终结果Map中。
+				finalMenusMap[curr.ID] = v1.MenuDataItem{
+					ID:         curr.ID,
+					Name:       curr.Name,
+					Title:      curr.Title,
+					Path:       curr.Path,
+					Component:  curr.Component,
+					Redirect:   curr.Redirect,
+					KeepAlive:  curr.KeepAlive,
+					HideInMenu: curr.HideInMenu,
+					Locale:     curr.Locale,
+					Weight:     curr.Weight,
+					Icon:       curr.Icon,
+					ParentID:   curr.ParentID,
+					UpdatedAt:  curr.UpdatedAt.Format("2006-01-02 15:04:05"),
+					URL:        curr.URL,
+				}
+
+				// 移动到父菜单,准备下一次循环。
+				if curr.ParentID > 0 {
+					// 利用步骤3创建的快速索引Map,高效地找到父菜单对象。
+					curr = allMenusMap[curr.ParentID]
+				} else {
+					// 如果没有ParentID了,说明已经追溯到根节点,结束循环。
+					curr = nil
+				}
+			}
 		}
 	}
+
+	// 步骤 6: 将Map形式的最终结果转换为列表(Slice),以符合API响应的格式。
+	data := &v1.GetMenuResponseData{
+		List: make([]v1.MenuDataItem, 0, len(finalMenusMap)),
+	}
+	for _, menuItem := range finalMenusMap {
+		data.List = append(data.List, menuItem)
+	}
+
 	return data, nil
 }
 func (s *adminService) GetAdminMenus(ctx context.Context) (*v1.GetMenuResponseData, error) {

+ 18 - 0
internal/service/gatewaygroup.go

@@ -19,6 +19,7 @@ type GatewayGroupService interface {
 	GetGatewayGroupByHostId(ctx context.Context, hostId int) ([]model.GatewayGroup, error)
 	GetGatewayGroupList(ctx context.Context,req v1.SearchGatewayGroupParams) (*v1.PaginatedResponse[model.GatewayGroup]	, error)
 	AddGatewayGroupAdmin(ctx context.Context,req v1.AddGateWayGroupAdminRequest) error
+	EditGatewayGroupAdmin(ctx context.Context, req v1.AddGateWayGroupAdminRequest) error
 }
 func NewGatewayGroupService(
     service *Service,
@@ -129,4 +130,21 @@ func (s *gatewayGroupService) AddGatewayGroupAdmin(ctx context.Context,req v1.Ad
 	}
 	return nil
 
+}
+
+func (s *gatewayGroupService) EditGatewayGroupAdmin(ctx context.Context, req v1.AddGateWayGroupAdminRequest) error {
+	if err := s.gatewayGroupRepository.EditGatewayGroupById(ctx,  &model.GatewayGroup{
+		Id: req.Id,
+		Name: req.Name,
+		Comment: req.Comment,
+		HostId: req.HostId,
+		RuleId: req.RuleId,
+		BanUdp: req.BanUdp,
+		BanOverseas: req.BanOverseas,
+		Operator: req.Operator,
+	}); err != nil {
+		return err
+	}
+	return nil
+
 }

+ 1 - 1
web/src/api/list/gateway-list.js

@@ -13,5 +13,5 @@ export async function updateApi(data) {
 }
 
 export async function deleteApi(id) {
-  return useDelete(`/v1/gateway/del/${id}`)
+  return useDelete(`/v1/gateway/del`, { id })
 }

+ 3 - 1
web/src/locales/lang/pages/zh-CN.js

@@ -194,11 +194,13 @@ export default {
   //网关组
   'gateway.group.title': '网关组',
   'gateway.group.name': '网关组名称',
+  'gateway.group.ruleId': '规则ID',
+  'gateway.group.hostId': '订单ID',
   'gateway.group.id': '网关组ID',
   'gateway.group.comment': '网关组备注',
   'gateway.group.operator': '网关组运营商',
   'gateway.group.banUdp': '是否封UDP',
-  'gateway.group.move': '移动',
+  'gateway.group.telecommunications': '电信',
   'gateway.group.BGP': 'BGP',
   'gateway.group.banOverseas': '是否封海外',
   'gateway.group.create_time': '创建时间',

+ 11 - 3
web/src/pages/menu/components/crud-table-modal.vue

@@ -14,7 +14,8 @@ const formRef = ref()
 const formData = ref({
   id: 0,
   name: '',
-  hostId: '',
+  hostId: 0,
+  ruleId : 0,
   comment: '',
   operator: 0,
   banUdp: 0,
@@ -29,7 +30,8 @@ function open(record) {
   formData.value = cloneDeep(record) ?? {
     id: 0,
     name: '',
-    hostId: '',
+    hostId: 0,
+    ruleId : 0,
     comment: '',
     operator: 1,
     banUdp: 0,
@@ -68,6 +70,12 @@ defineExpose({
       <a-form-item  :label="t('gateway.group.name')" name="name" :rules="[{ required: true, message: '请输入网关组名称' }]">
         <a-input v-model:value="formData.name" :maxlength="50" placeholder="请输入网关组名称" />
       </a-form-item>
+      <a-form-item  :label="t('gateway.group.hostId')" name="hostId" :rules="[{ required: true, message: '请输入订单ID' }]">
+        <a-input v-model:value="formData.hostId" :maxlength="50" placeholder="请输入订单ID" />
+      </a-form-item>
+      <a-form-item  :label="t('gateway.group.ruleId')" name="ruleId" :rules="[{ required: true, message: '请输入规则ID' }]">
+        <a-input v-model:value="formData.ruleId" :maxlength="50" placeholder="请输入规则ID" />
+      </a-form-item>
       <a-form-item :label="t('gateway.group.comment')" name="comment">
         <a-textarea v-model:value="formData.comment" show-count :maxlength="200" placeholder="请输入备注" />
       </a-form-item>
@@ -84,7 +92,7 @@ defineExpose({
       <a-form-item :label="t('gateway.group.operator')"  name="operator">
         <a-radio-group  v-model:value="formData.operator">
           <a-radio :value="1">
-            {{ t('gateway.group.move') }}
+            {{ t('gateway.group.telecommunications') }}
           </a-radio>
           <a-radio :value="2">
             {{ t('gateway.group.BGP') }}

+ 2 - 4
web/src/pages/menu/menu1.vue

@@ -65,7 +65,6 @@ const { state, initQuery, resetQuery, query } = useTableQuery({
     name: undefined,
   },
   afterQuery: (res) => {
-    console.log(res)
     if (!res || !Array.isArray(res.records)) {
       // 如果数据格式不正确,返回一个空状态,防止程序崩溃
       return { list: [], total: 0 }
@@ -82,13 +81,12 @@ async function handleDelete(record) {
     if (res.code === 200)
       await query()
     message.success('删除成功')
+
   }
   catch (e) {
     console.log(e)
   }
-  finally {
-    close()
-  }
+
 }
 function handleAdd() {
   crudTableModal.value?.open()