feat(shop-role): 实现店铺角色继承功能和权限检查优化
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m39s

- 新增店铺角色管理 API 和数据模型
- 实现角色继承和权限检查逻辑
- 添加流程测试框架和集成测试
- 更新权限服务和账号管理逻辑
- 添加数据库迁移脚本
- 归档 OpenSpec 变更文档

Ultraworked with Sisyphus
This commit is contained in:
2026-02-03 10:06:13 +08:00
parent bc7e5d6f6d
commit 5a90caa619
61 changed files with 21284 additions and 131 deletions

100
flow_tests/core/client.py Normal file
View File

@@ -0,0 +1,100 @@
import logging
from dataclasses import dataclass
from typing import Any, Optional
import requests
from config.settings import settings
logger = logging.getLogger(__name__)
@dataclass
class APIResponse:
status_code: int
code: int
msg: str
data: Any
raw: dict
def ok(self) -> bool:
return self.code == 0
def __bool__(self) -> bool:
return self.ok()
class APIClient:
def __init__(self, base_url: Optional[str] = None):
self.base_url = base_url or settings.api_base_url
self.timeout = settings.api_timeout
self.token: Optional[str] = None
self.session = requests.Session()
def set_token(self, token: str):
self.token = token
self.session.headers["Authorization"] = f"Bearer {token}"
def clear_token(self):
self.token = None
self.session.headers.pop("Authorization", None)
def _request(self, method: str, path: str, **kwargs) -> APIResponse:
url = f"{self.base_url}{path}"
kwargs.setdefault("timeout", self.timeout)
logger.info(f"{method} {path}")
try:
resp = self.session.request(method, url, **kwargs)
except requests.exceptions.RequestException as e:
logger.error(f"请求失败: {e}")
return APIResponse(status_code=0, code=-1, msg=str(e), data=None, raw={})
try:
raw = resp.json()
except ValueError:
return APIResponse(
status_code=resp.status_code, code=-1,
msg="响应不是有效的 JSON", data=None, raw={}
)
return APIResponse(
status_code=resp.status_code,
code=raw.get("code", -1),
msg=raw.get("msg", ""),
data=raw.get("data"),
raw=raw,
)
def get(self, path: str, params: Optional[dict] = None, **kwargs) -> APIResponse:
return self._request("GET", path, params=params, **kwargs)
def post(self, path: str, json: Optional[dict] = None, **kwargs) -> APIResponse:
return self._request("POST", path, json=json, **kwargs)
def put(self, path: str, json: Optional[dict] = None, **kwargs) -> APIResponse:
return self._request("PUT", path, json=json, **kwargs)
def delete(self, path: str, **kwargs) -> APIResponse:
return self._request("DELETE", path, **kwargs)
def patch(self, path: str, json: Optional[dict] = None, **kwargs) -> APIResponse:
return self._request("PATCH", path, json=json, **kwargs)
def upload(self, path: str, file, field_name: str = "file", **kwargs) -> APIResponse:
files = {field_name: file}
return self._request("POST", path, files=files, **kwargs)
def login(self, username: str, password: str, login_path: str = "/api/admin/auth/login") -> APIResponse:
resp = self.post(login_path, json={
"username": username,
"password": password,
})
if resp.ok() and resp.data:
token = resp.data.get("token") or resp.data.get("access_token")
if token:
self.set_token(token)
logger.info(f"登录成功: {username}")
return resp