feat: 指数
This commit is contained in:
parent
887ce42659
commit
0c2769e6d1
54
.github/copilot-instructions.md
vendored
Normal file
54
.github/copilot-instructions.md
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
## 项目(快速)指导 — 供 AI 编码代理使用
|
||||
|
||||
下面的要点帮助你快速理解并在本代码库中高效工作。保持简短、具体并以可执行示例为主。
|
||||
|
||||
- 项目类型:FastAPI 后端 (Python 3.11) + Vue3/Vite 前端(目录 `web/`)。后端使用 Tortoise ORM(配置在 `app/settings/config.py`),前端用 pnpm/vite。
|
||||
|
||||
- 快速启动(后端):在项目根目录
|
||||
- 建议 Python venv,然后安装依赖:`pip install -r requirements.txt`(或使用项目 README 中的 uv/uvenv 过程)。
|
||||
- 启动:`python run.py`。这会通过 `uvicorn` 运行 `app:app`(见 `run.py`),开启 `reload=True`,OpenAPI 在 `/docs`。
|
||||
|
||||
- 快速启动(前端):进入 `web/`,使用 pnpm(或 npm)安装并运行:`pnpm i`,`pnpm dev`。
|
||||
|
||||
- 后端关键入口
|
||||
- `run.py`:应用启动脚本,设置 uvicorn 日志格式并运行 `app:app`。
|
||||
- `app/__init__.py`:创建 FastAPI app(调用 `core/init_app.py` 中的注册函数):init 数据、注册中间件、异常处理与路由(路由前缀为 `/api`)。
|
||||
- `app/core/init_app.py`(注意:此文件包含启动时的路由/中间件/异常注册逻辑,请优先阅读它来理解请求生命周期)。
|
||||
|
||||
- 重要配置点
|
||||
- `app/settings/config.py`:使用 Pydantic Settings,包含 `TORTOISE_ORM`(默认 SQLite,db 文件在项目根 `db.sqlite3`)、JWT、SECRET_KEY、CORS 等。修改环境变量即可覆盖设置。
|
||||
- `app/utils/api_config.py`:提供 `api_config` 全局实例,用来存放第三方 API(示例:`chinaz`、`xiaohongshu`)。常用方法:`api_config.get_api_key(provider)`、`get_endpoint_config(provider, endpoint)`、`add_endpoint(...)`、`save_config()`。
|
||||
|
||||
- 路由与模块约定
|
||||
- API 版本化:`app/api/v1/` 下放置 v1 接口。路由统一由 `core/init_app.py` 通过 `register_routers(..., prefix='/api')` 注册。
|
||||
- 控制器(HTTP handlers)位于 `app/controllers/`,数据模型在 `app/models/`,Pydantic schemas 在 `app/schemas/`。
|
||||
|
||||
- 数据库与迁移
|
||||
- 使用 Tortoise ORM,`TORTOISE_ORM` 在 `app/settings/config.py`。项目把 `aerich.models` 列入 models(见配置),repository 中存在 `migrations/` 文件夹。若需变更模型,按项目现有工具链(如 aerich)执行迁移;在不确定时,先检查 `pyproject.toml`/`requirements.txt` 是否包含 aerich 并复核 README。
|
||||
|
||||
- 日志与持久化
|
||||
- 日志目录:`app/logs`(可在 `settings.LOGS_ROOT` 找到)。运行时可根据 `run.py` 中的 LOGGING_CONFIG 调整格式。
|
||||
|
||||
- 第三方 API 集成(示例)
|
||||
- `api_config` 示例用法(Python):
|
||||
```py
|
||||
from app.utils.api_config import api_config
|
||||
cfg = api_config.get_endpoint_config('xiaohongshu', 'xiaohongshu_note_detail')
|
||||
base = api_config.get_base_url('xiaohongshu')
|
||||
key = api_config.get_api_key('xiaohongshu')
|
||||
```
|
||||
- 环境变量覆盖:CHINAZ_API_KEY、XIAOHONGSHU_TOKEN、EXAMPLE_API_KEY 等会被 `api_config` 或 settings 读取。
|
||||
|
||||
- 编辑/贡献约定(可自动推断的现有模式)
|
||||
- 新增 API:在 `app/api/v1/...` 添加路由模块,控制器放 `app/controllers/`,schema 放 `app/schemas/`,并在 `core/init_app.py` 中确保路由被注册。
|
||||
- 新增模型:更新 `app/models/` 并生成迁移(项目使用 Tortoise + aerich 风格)。先检查 `migrations/models` 是否有对应变更。
|
||||
|
||||
- 调试提示
|
||||
- 本地运行时使用 `python run.py`(reload=True),然后访问 `http://localhost:9999/docs` 查看 OpenAPI,确认路由/依赖注入是否按预期工作。
|
||||
- 常见故障点:环境变量未设置(导致 API keys 丢失)、Tortoise 连接配置错误(检查 `TORTOISE_ORM.connections`)、以及中间件注册顺序会影响异常处理。
|
||||
|
||||
- 其它注意事项(小而具体)
|
||||
- 前端以 `/api` 为后端前缀,修改后端接口时请同步前端 `web/src/api` 的调用。
|
||||
- `app/utils/api_config.py` 会在模块导入时创建 `api_config` 单例;修改该文件时注意导入时机(不要在模块顶层做阻塞网络调用)。
|
||||
|
||||
如果需要我把 README 中的启动说明转成更精确的 shell 命令(或添加 aerich 的迁移示例命令),我可以继续补充。请告诉我你希望强调的额外部分或需要澄清的地方。
|
||||
@ -2,7 +2,7 @@ FROM node:18.12.0-alpine3.16 AS web
|
||||
|
||||
WORKDIR /opt/vue-fastapi-admin
|
||||
COPY /web ./web
|
||||
RUN cd /opt/vue-fastapi-admin/web && npm i --registry=https://registry.npmmirror.com && npm run build
|
||||
RUN npm install -g pnpm && cd /opt/vue-fastapi-admin/web && pnpm install --registry=https://registry.npmmirror.com && pnpm run build
|
||||
|
||||
|
||||
FROM python:3.11-slim-bullseye
|
||||
|
||||
@ -9,6 +9,7 @@ from app.schemas.third_party_api import (
|
||||
BaseAPIRequest,
|
||||
ChinazAPIRequest,
|
||||
XiaohongshuNoteRequest,
|
||||
JizhiliaoSearchRequest,
|
||||
APIResponse,
|
||||
APIProviderInfo,
|
||||
APIEndpointInfo,
|
||||
@ -90,4 +91,24 @@ async def get_xiaohongshu_note(request: XiaohongshuNoteRequest):
|
||||
return Fail(message=result.message)
|
||||
except Exception as e:
|
||||
logger.error(f"获取小红书笔记失败: {e}")
|
||||
return Fail(message=f"获取小红书笔记失败: {str(e)}")
|
||||
return Fail(message=f"获取小红书笔记失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/jizhiliao/index_search", summary="极致聊指数搜索")
|
||||
async def jizhiliao_index_search(request: JizhiliaoSearchRequest):
|
||||
"""调用极致聊指数搜索接口"""
|
||||
try:
|
||||
params = request.model_dump(by_alias=True, exclude_none=True, exclude={"timeout"})
|
||||
timeout = request.timeout if request.timeout is not None else 30
|
||||
result = await third_party_api_controller.search_jizhiliao_index(
|
||||
params=params,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if result.success:
|
||||
return Success(data=result.data, message=result.message)
|
||||
else:
|
||||
return Fail(message=result.message)
|
||||
except Exception as e:
|
||||
logger.error(f"极致聊指数搜索失败: {e}")
|
||||
return Fail(message=f"极致聊指数搜索失败: {str(e)}")
|
||||
@ -96,11 +96,20 @@ class ThirdPartyAPIController:
|
||||
params = {"noteId": note_id}
|
||||
|
||||
return await self.make_api_request(
|
||||
provider="justoneapi",
|
||||
provider="xiaohongshu",
|
||||
endpoint="xiaohongshu_note_detail",
|
||||
params=params
|
||||
)
|
||||
|
||||
async def search_jizhiliao_index(self, params: Dict[str, Any], timeout: int = 30) -> APIResponse:
|
||||
"""执行极致聊指数搜索"""
|
||||
return await self.make_api_request(
|
||||
provider="jizhiliao",
|
||||
endpoint="index_search",
|
||||
params=params,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
|
||||
# 创建全局控制器实例
|
||||
third_party_api_controller = ThirdPartyAPIController()
|
||||
@ -1,5 +1,5 @@
|
||||
from typing import Dict, Any, Optional, List
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
|
||||
|
||||
class BaseAPIRequest(BaseModel):
|
||||
@ -22,6 +22,23 @@ class XiaohongshuNoteRequest(BaseModel):
|
||||
note_id: str = Field(..., description="笔记ID", example="68d2c71d000000000e00e9ea")
|
||||
|
||||
|
||||
class JizhiliaoSearchRequest(BaseModel):
|
||||
"""极致聊指数搜索请求"""
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
keyword: str = Field(..., description="搜索关键词", example="人民日报")
|
||||
mode: int = Field(2, description="搜索模式", example=2)
|
||||
business_type: int = Field(
|
||||
8192,
|
||||
alias="BusinessType",
|
||||
description="业务类型标识",
|
||||
example=8192,
|
||||
)
|
||||
sub_search_type: int = Field(0, description="子搜索类型", example=0)
|
||||
verifycode: Optional[str] = Field("", description="验证码", example="")
|
||||
timeout: Optional[int] = Field(30, description="超时时间(秒)")
|
||||
|
||||
|
||||
class APIResponse(BaseModel):
|
||||
"""API响应模型"""
|
||||
success: bool = Field(..., description="请求是否成功")
|
||||
|
||||
@ -55,36 +55,52 @@ class APIConfig:
|
||||
}
|
||||
}
|
||||
},
|
||||
"justoneapi": {
|
||||
"api_key": os.getenv("JUSTONEAPI_TOKEN", "YNSbIjdU"),
|
||||
"base_url": "https://api.justoneapi.com",
|
||||
"timeout": 30,
|
||||
"retries": 3,
|
||||
"xiaohongshu": {
|
||||
"api_key": os.getenv("XIAOHONGSHU_TOKEN", "YNSbIjdU"),
|
||||
"base_url": "https://api.justoneapi.com",
|
||||
"timeout": 30,
|
||||
"retries": 3,
|
||||
"endpoints": {
|
||||
"xiaohongshu_note_detail": {
|
||||
"path": "/api/xiaohongshu/get-note-detail/v7",
|
||||
"method": "GET",
|
||||
"description": "小红书笔记详情查询",
|
||||
"required_params": ["noteId"],
|
||||
"optional_params": ["token"]
|
||||
},
|
||||
"xiaohongshu_user_info": {
|
||||
"path": "/api/xiaohongshu/get-user-info/v7",
|
||||
"method": "GET",
|
||||
"description": "小红书用户信息查询",
|
||||
"required_params": ["userId"],
|
||||
"optional_params": ["token"]
|
||||
},
|
||||
"xiaohongshu_search_notes": {
|
||||
"path": "/api/xiaohongshu/search-notes/v7",
|
||||
"method": "GET",
|
||||
"description": "小红书笔记搜索",
|
||||
"required_params": ["keyword"],
|
||||
"optional_params": ["page", "size", "token"]
|
||||
}
|
||||
"path": "/api/xiaohongshu/get-note-detail/v7",
|
||||
"method": "GET",
|
||||
"description": "小红书笔记详情查询",
|
||||
"required_params": ["noteId"],
|
||||
"optional_params": ["token"]
|
||||
},
|
||||
"xiaohongshu_user_info": {
|
||||
"path": "/api/xiaohongshu/get-user-info/v7",
|
||||
"method": "GET",
|
||||
"description": "小红书用户信息查询",
|
||||
"required_params": ["userId"],
|
||||
"optional_params": ["token"]
|
||||
},
|
||||
"xiaohongshu_search_notes": {
|
||||
"path": "/api/xiaohongshu/search-notes/v7",
|
||||
"method": "GET",
|
||||
"description": "小红书笔记搜索",
|
||||
"required_params": ["keyword"],
|
||||
"optional_params": ["page", "size", "token"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"other_apis": {
|
||||
"jizhiliao": {
|
||||
"api_key": os.getenv("JIZHILIAO_API_KEY", "JZL089ef0b7d0315d96"),
|
||||
"base_url": "https://www.dajiala.com",
|
||||
"timeout": 30,
|
||||
"retries": 3,
|
||||
"verifycode": os.getenv("JIZHILIAO_VERIFYCODE", ""),
|
||||
"endpoints": {
|
||||
"index_search": {
|
||||
"path": "/fbmain/monitor/v3/web_search",
|
||||
"method": "POST",
|
||||
"description": "极致聊指数搜索",
|
||||
"required_params": ["keyword", "mode", "BusinessType", "sub_search_type"],
|
||||
"optional_params": ["key", "verifycode"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"other": {
|
||||
# 可以添加其他第三方API配置
|
||||
"example_api": {
|
||||
"api_key": os.getenv("EXAMPLE_API_KEY", ""),
|
||||
|
||||
@ -146,12 +146,23 @@ class UniversalAPIManager:
|
||||
if 'sign' not in prepared_params:
|
||||
prepared_params['sign'] = self._generate_chinaz_sign()
|
||||
|
||||
elif provider == 'justoneapi':
|
||||
# JustOneAPI需要token参数
|
||||
elif provider == 'xiaohongshu':
|
||||
# 小红书接口使用 JustOneAPI 平台,需要 token 参数
|
||||
if 'token' not in prepared_params or not prepared_params['token']:
|
||||
api_key = provider_config.get('api_key')
|
||||
if api_key:
|
||||
prepared_params['token'] = api_key
|
||||
|
||||
elif provider == 'jizhiliao':
|
||||
# 极致聊接口需要 key,默认从配置读取
|
||||
if 'key' not in prepared_params or not prepared_params['key']:
|
||||
api_key = provider_config.get('api_key')
|
||||
if api_key:
|
||||
prepared_params['key'] = api_key
|
||||
if 'verifycode' not in prepared_params:
|
||||
default_verifycode = provider_config.get('verifycode')
|
||||
if default_verifycode is not None:
|
||||
prepared_params['verifycode'] = default_verifycode
|
||||
|
||||
return prepared_params
|
||||
|
||||
@ -256,7 +267,7 @@ class UniversalAPIManager:
|
||||
}
|
||||
return self.make_request('chinaz', 'judgement', params)
|
||||
|
||||
# JustOneAPI的便捷方法
|
||||
# 小红书便捷方法
|
||||
def get_xiaohongshu_note_detail(self, note_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取小红书笔记详情
|
||||
@ -269,7 +280,31 @@ class UniversalAPIManager:
|
||||
"""
|
||||
params = {'noteId': note_id}
|
||||
|
||||
return self.make_request('justoneapi', 'xiaohongshu_note_detail', params)
|
||||
return self.make_request('xiaohongshu', 'xiaohongshu_note_detail', params)
|
||||
|
||||
# 极致聊便捷方法
|
||||
def search_jizhiliao_index(
|
||||
self,
|
||||
keyword: str,
|
||||
mode: int = 2,
|
||||
business_type: int = 8192,
|
||||
sub_search_type: int = 0,
|
||||
key: Optional[str] = None,
|
||||
verifycode: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""执行极致聊指数搜索"""
|
||||
params: Dict[str, Any] = {
|
||||
'keyword': keyword,
|
||||
'mode': mode,
|
||||
'BusinessType': business_type,
|
||||
'sub_search_type': sub_search_type,
|
||||
}
|
||||
if key is not None:
|
||||
params['key'] = key
|
||||
if verifycode is not None:
|
||||
params['verifycode'] = verifycode
|
||||
|
||||
return self.make_request('jizhiliao', 'index_search', params)
|
||||
|
||||
# 通用方法
|
||||
def list_providers(self) -> list:
|
||||
|
||||
@ -4,14 +4,16 @@ annotated-types==0.7.0
|
||||
anyio==4.8.0
|
||||
argon2-cffi==23.1.0
|
||||
argon2-cffi-bindings==21.2.0
|
||||
asyncclick==8.1.8
|
||||
asyncclick==8.1.8.0
|
||||
black==24.10.0
|
||||
certifi==2024.12.14
|
||||
cffi==1.17.1
|
||||
charset-normalizer==3.4.3
|
||||
click==8.1.8
|
||||
dictdiffer==0.9.0
|
||||
dnspython==2.7.0
|
||||
email-validator==2.2.0
|
||||
email_validator==2.2.0
|
||||
exceptiongroup==1.3.0
|
||||
fastapi==0.111.0
|
||||
fastapi-cli==0.0.7
|
||||
h11==0.14.0
|
||||
@ -21,40 +23,44 @@ httpx==0.28.1
|
||||
idna==3.10
|
||||
iso8601==2.1.0
|
||||
isort==5.13.2
|
||||
jinja2==3.1.5
|
||||
Jinja2==3.1.5
|
||||
loguru==0.7.3
|
||||
markdown-it-py==3.0.0
|
||||
markupsafe==3.0.2
|
||||
MarkupSafe==3.0.2
|
||||
mdurl==0.1.2
|
||||
mypy-extensions==1.0.0
|
||||
orjson==3.10.14
|
||||
packaging==24.2
|
||||
passlib==1.7.4
|
||||
pathspec==0.12.1
|
||||
pillow==11.3.0
|
||||
platformdirs==4.3.6
|
||||
pycparser==2.22
|
||||
pydantic==2.10.5
|
||||
pydantic-core==2.27.2
|
||||
pydantic-settings==2.7.1
|
||||
pygments==2.19.1
|
||||
pyjwt==2.10.1
|
||||
pydantic_core==2.27.2
|
||||
Pygments==2.19.1
|
||||
PyJWT==2.10.1
|
||||
pypika-tortoise==0.3.2
|
||||
python-dotenv==1.0.1
|
||||
python-multipart==0.0.20
|
||||
pytz==2024.2
|
||||
pyyaml==6.0.2
|
||||
PyYAML==6.0.2
|
||||
requests==2.32.5
|
||||
rich==13.9.4
|
||||
rich-toolkit==0.13.2
|
||||
ruff==0.9.1
|
||||
setuptools==75.8.0
|
||||
shellingham==1.5.4
|
||||
sniffio==1.3.1
|
||||
starlette==0.37.2
|
||||
tomli==2.2.1
|
||||
tortoise-orm==0.23.0
|
||||
tqdm==4.67.1
|
||||
typer==0.15.1
|
||||
typing-extensions==4.12.2
|
||||
typing_extensions==4.12.2
|
||||
ujson==5.10.0
|
||||
urllib3==2.5.0
|
||||
uvicorn==0.34.0
|
||||
uvloop==0.21.0; sys_platform != 'win32'
|
||||
uvloop==0.21.0
|
||||
watchfiles==1.0.4
|
||||
websockets==14.1
|
||||
|
||||
8415
web/package-lock.json
generated
8415
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,7 @@
|
||||
"@iconify/json": "^2.2.228",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@unocss/eslint-config": "^0.55.0",
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"@zclzone/eslint-config": "^0.0.4",
|
||||
"axios": "^1.4.0",
|
||||
@ -28,9 +29,9 @@
|
||||
"sass": "^1.65.1",
|
||||
"typescript": "^5.1.6",
|
||||
"unocss": "^0.55.0",
|
||||
"unplugin-auto-import": "^0.15.3",
|
||||
"unplugin-auto-import": "^0.16.6",
|
||||
"unplugin-icons": "^0.16.5",
|
||||
"unplugin-vue-components": "^0.24.1",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
|
||||
2464
web/pnpm-lock.yaml
generated
2464
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user