# 高级 ACL 接口设计说明 本文档描述如何在不改动现有低级接口(`login_post`, `acl_post.get_acl_rules`, `add_acl_post.add_acl_rule`, `edit_acl_post.edit_acl_rule`, `del_acl_post.del_acl_rule`)的前提下,设计并实现一组高级接口,用于集中管理以备注前缀(例如 `Test_`)分组的 ACL 目的地址集合。 ## 目标 - 从所有 ACL 规则中筛选出备注以 `Test_`(可在 `config.json` 中配置)为前缀的规则(例如 `Test_1`, `Test_2` 等)。 - 提取这些规则中 `dst_addr` 字段的所有 IP(逗号分隔),合并为一个全局去重的文本数组(字符串列表)。 - 基于该集合提供三个高级操作接口: 1. 查询所有 IP 集合(返回去重列表) 2. 添加一个 IP 到集合:如果新 IP 导致某个分组规则(如 `Test_1`)超过容量限制(例如 1000),则创建或选用下一个编号的 `Test_n` 规则;必要时使用添加或编辑低级接口来写回。重复 IP 不执行任何操作。 3. 删除一个 IP:从对应规则中删除该 IP(若规则变为空可选择删除规则或保留空规则),并更新/编辑/删除低级规则。 ## 配置项 - 在 `config.json` 中新增或使用字段: - `test_prefix`(string, 默认 `"Test_"`) — 用来匹配规则备注前缀。 - `rule_ip_limit`(int, 默认 1000) — 单条 ACL 规则允许的最大 IP 数量。 示例: ```json { "base_url": "http://103.236.55.143:8080/", "test_prefix": "Test_", "rule_ip_limit": 1000 } ``` ## 数据契约 - IP 集合类型:List[str](去重,保留字符串格式的IP或IP段,按原样存储) - 内部表示:Python set 用于去重,返回时转换为 list(可选按字典序排序) ## 高级接口签名(伪 API) - get_all_test_ips() -> List[str] - 描述:返回合并后的去重 IP 列表 - add_ip(ip: str) -> dict - 描述:向集合添加单个 IP,返回操作结果 {"added": True/False, "rule": "Test_1", "row_id": 123, "message": "..."} - del_ip(ip: str) -> dict - 描述:从集合删除单个 IP,返回操作结果 {"deleted": True/False, "rule": "Test_1", "message": "..."} ## 实现逻辑(详细步骤) ### A. 获取并构建全局 IP 集合(get_all_test_ips) 1. 使用现有低级接口 `acl_post.get_acl_rules()` 获取全部规则(`Data.data` 列表)。 2. 从配置读取 `test_prefix`。 3. 遍历规则列表,筛选 `comment` 字段以 `test_prefix` 开头的规则(例如 `Test_1`, `Test_2`)。 4. 对每条匹配规则,读取 `dst_addr` 字段;如果 `dst_addr` 为空则跳过。否则: - 以逗号拆分字符串,去除前后空白,保留原样(IP 或 IP 范围)。 - 将拆分出的元素加入一个 Python set 用于去重。 5. 返回去重后的 sorted(list(set)) 或者原顺序 list(实现可配置)。 注意点: - `dst_addr` 可能包含 IP 段(例如 `1.2.3.4-1.2.3.10`)或单个IP,当前实现策略是直接作为字符串项存储,不做IP范围展开。 - 若需要支持范围展开(拆分多个IP),这是一个可选扩展(成本高,谨慎)。 ### B. 添加 IP(add_ip) 输入:单个字符串 `ip`(例如 `8.8.8.8` 或 `1.2.3.4`)。 输出:操作结果字典,含是否添加成功、目标规则名/ID 等。 步骤: 1. 调用 get_all_test_ips() 获取当前去重集合(set)。如果 `ip` 已存在,直接返回 {"added": False, "message": "already exists"}。 2. 否则需要把 `ip` 放入某个 `Test_n` 规则中: - 首先通过 `acl_post.get_acl_rules()` 获取所有匹配 `test_prefix` 的规则,并按 `comment` 中的编号从小到大排序(例如 `Test_1`, `Test_2`)。同时记录每个规则的 `dst_addr` 列表与 `id`(rule id)。 - 遍历这些规则(从编号最小的开始),对于每个规则计算当前 IP 数量,如果加入新 IP 后满足 `current_count + 1 <= rule_ip_limit`,则向该规则进行编辑(使用 `edit_acl_post.edit_acl_rule(rule_id=..., dst_addr=new_dst_str, comment=...)`),将新的 IP 附加到 `dst_addr`(注意去重),并返回该规则的信息(rule id/comment)。 - 如果所有已有规则都已满(或没有规则),则创建新的规则: - 若没有任何匹配的 `Test_*` 规则,则创建 `Test_1`(使用 `add_acl_post.add_acl_rule(dst_addr=ip, comment=f"{test_prefix}1")`); - 否则创建 `Test_{k+1}`(k 为当前最大的编号),使用 `add_acl_post.add_acl_rule(dst_addr=ip, comment=f"{test_prefix}{k+1}")`。创建成功后返回新创建的 RowId。 3. 成功写回后,返回 {"added": True, "rule": "Test_k or Test_{k+1}", "row_id": , "message": "ok"} 错误与回滚考虑: - 在编辑时,如果低级接口返回失败(HTTP非200或Result不为Success),应记录错误并返回失败,不应丢失客户端数据。 - 可选的回滚:如果添加操作分为多步(例如先创建然后编辑),在部分失败情况下尝试回滚(例如删除新创建的规则)。为降低复杂度,初版可以不做复杂回滚,而是保持幂等性并记录失败信息供人工干预。 并发处理: - 多个并发添加请求可能导致竞态(例如同时检测到 Test_k 未满并同时写入,超出 limit)。解决策略: 1. 在服务端加分布式锁(最佳,但超出本地实现范围); 2. 在客户端(高级接口实现处)使用本地文件锁或线程锁,尽量序列化对 ACL 更新的操作; 3. 在写回后再次验证规则大小:编辑后重新读取规则并验证数量如超限则将多出的IP移到下一个规则或提示人工处理。 ### C. 删除 IP(del_ip) 输入:单个字符串 `ip`。 步骤: 1. 通过 `acl_post.get_acl_rules()` 获取所有匹配 `test_prefix` 的规则,并解析每条规则的 `dst_addr` 到列表形式。 - 找到包含目标 `ip` 的所有规则(即支持在多个规则中删除)。对于每个匹配的规则: - 从该规则的 `dst_addr` 列表中移除该 `ip`。 - 如果移除后列表不为空,调用 `edit_acl_post.edit_acl_rule(rule_id=..., dst_addr=new_dst_str, comment=...)` 更新该规则; - 如果移除后列表为空,默认应删除该规则(调用 `del_acl_post.del_acl_rule(rule_id=...)`),不保留空规则,以保持规则集合整洁并避免无用占位。 3. 返回操作结果,包含受影响规则的列表(每个元素包含 `rule_id`、`comment`、`action`:`edited` 或 `deleted`),以及整体操作状态。 边缘情况: - IP 在多个规则中存在:默认行为为删除所有出现的地方,并返回受影响规则列表(而非只删除第一次出现)。 - 删除导致 hole(例如 Test_2 删除后 Test_1、Test_3 可能不连续),可以不强制重编号,保持 comment 名称不变以减少对外影响。 ## 幂等性与幂等操作 - `add_ip` 对已存在的 IP 操作应当是幂等的(不重复添加)。 - `del_ip` 对不存在的 IP 操作也应当返回成功/无操作结果。 ## 权限与认证 - 所有高级接口在内部依赖已有的登录与 `sess_key` 管理,不需要额外的认证步骤;但调用者应确保先执行登录流程并设置好全局 sess_key。 ## 日志与监控 - 使用现有 `log_util.log_request` 记录每次低级请求及响应。 - 在高级接口处记录高层动作(add/del),包括原始IP、目标规则、操作结果与错误信息。