dify插件初步构建
This commit is contained in:
16
difyPlugin/.env.example
Normal file
16
difyPlugin/.env.example
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 应用配置
|
||||||
|
APP_NAME=DifyPlugin
|
||||||
|
APP_VERSION=1.0.0
|
||||||
|
DEBUG=false
|
||||||
|
|
||||||
|
# API配置
|
||||||
|
API_V1_PREFIX=/api/v1
|
||||||
|
|
||||||
|
# 跨域配置
|
||||||
|
CORS_ORIGINS=["*"]
|
||||||
|
|
||||||
|
# Redis配置
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
REDIS_DB=0
|
||||||
27
difyPlugin/.gitignore
vendored
Normal file
27
difyPlugin/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# 环境配置
|
||||||
|
.env
|
||||||
|
|
||||||
|
# 日志
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# 测试
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
38
difyPlugin/README.md
Normal file
38
difyPlugin/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# DifyPlugin
|
||||||
|
|
||||||
|
Dify插件服务 - 基于FastAPI构建
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### API文档
|
||||||
|
|
||||||
|
- Swagger UI: http://localhost:8000/docs
|
||||||
|
- ReDoc: http://localhost:8000/redoc
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
difyPlugin/
|
||||||
|
├── app/
|
||||||
|
│ ├── main.py # 应用入口
|
||||||
|
│ ├── config.py # 配置管理
|
||||||
|
│ ├── api/v1/ # API路由
|
||||||
|
│ ├── schemas/ # Pydantic数据模型
|
||||||
|
│ ├── services/ # 业务逻辑
|
||||||
|
│ ├── core/ # 核心功能
|
||||||
|
│ └── utils/ # 工具函数
|
||||||
|
├── requirements.txt
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
1
difyPlugin/app/__init__.py
Normal file
1
difyPlugin/app/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# DifyPlugin FastAPI Application
|
||||||
16
difyPlugin/app/api/__init__.py
Normal file
16
difyPlugin/app/api/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# API模块
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from app.api.workcase import router as workcase_router
|
||||||
|
from app.api.bidding import router as bidding_router
|
||||||
|
from app.api.test import router as test_router
|
||||||
|
|
||||||
|
# 创建主路由器
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# 注册所有子路由
|
||||||
|
router.include_router(workcase_router, prefix="/workcase", tags=["工单相关服务"])
|
||||||
|
router.include_router(bidding_router, prefix="/bidding", tags=["招标相关服务"])
|
||||||
|
router.include_router(test_router, prefix="/test", tags=["招标相关服务"])
|
||||||
|
|
||||||
|
__all__ = ["router"]
|
||||||
38
difyPlugin/app/api/bidding/ReadFileAPI.py
Normal file
38
difyPlugin/app/api/bidding/ReadFileAPI.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"""文件读取相关接口"""
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from app.schemas import ResultDomain
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/read",
|
||||||
|
response_model=ResultDomain[dict],
|
||||||
|
summary="读取文件",
|
||||||
|
description="读取指定路径的文件内容"
|
||||||
|
)
|
||||||
|
async def read_file(file_path: str) -> ResultDomain[dict]:
|
||||||
|
"""
|
||||||
|
读取文件内容
|
||||||
|
|
||||||
|
- **file_path**: 文件路径
|
||||||
|
"""
|
||||||
|
# TODO: 实现文件读取逻辑
|
||||||
|
return ResultDomain.success(message="读取成功", data={"content": ""})
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/parse",
|
||||||
|
response_model=ResultDomain[dict],
|
||||||
|
summary="解析文件",
|
||||||
|
description="解析招标文件内容"
|
||||||
|
)
|
||||||
|
async def parse_file(file_path: str) -> ResultDomain[dict]:
|
||||||
|
"""
|
||||||
|
解析招标文件
|
||||||
|
|
||||||
|
- **file_path**: 文件路径
|
||||||
|
"""
|
||||||
|
# TODO: 实现文件解析逻辑
|
||||||
|
return ResultDomain.success(message="解析成功", data={"result": {}})
|
||||||
13
difyPlugin/app/api/bidding/__init__.py
Normal file
13
difyPlugin/app/api/bidding/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# API模块
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from .ReadFileAPI import router as readfile_router
|
||||||
|
|
||||||
|
|
||||||
|
# 创建主路由器
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# 注册所有子路由
|
||||||
|
router.include_router(readfile_router, prefix="/readfile", tags=["文件读取相关服务"])
|
||||||
|
|
||||||
|
__all__ = ["router"]
|
||||||
28
difyPlugin/app/api/test/HelloWordAPI.py
Normal file
28
difyPlugin/app/api/test/HelloWordAPI.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""测试相关接口"""
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from app.schemas.base import ResultDomain
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/world",
|
||||||
|
response_model=ResultDomain[str],
|
||||||
|
summary="Hello World",
|
||||||
|
description="测试接口连通性"
|
||||||
|
)
|
||||||
|
async def hello_word() -> ResultDomain[str]:
|
||||||
|
"""Hello World 测试接口"""
|
||||||
|
return ResultDomain.ok(message="Hello World", data="Hello World")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/ping",
|
||||||
|
response_model=ResultDomain[str],
|
||||||
|
summary="Ping测试",
|
||||||
|
description="测试服务是否正常运行"
|
||||||
|
)
|
||||||
|
async def ping() -> ResultDomain[str]:
|
||||||
|
"""Ping 测试接口"""
|
||||||
|
return ResultDomain.ok(message="pong", data="pong")
|
||||||
13
difyPlugin/app/api/test/__init__.py
Normal file
13
difyPlugin/app/api/test/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# API模块
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from .HelloWordAPI import router as hello_router
|
||||||
|
|
||||||
|
|
||||||
|
# 创建主路由器
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# 注册所有子路由
|
||||||
|
router.include_router(hello_router, prefix="/hello", tags=["测试服务"])
|
||||||
|
|
||||||
|
__all__ = ["router"]
|
||||||
38
difyPlugin/app/api/workcase/QrCodeAPI.py
Normal file
38
difyPlugin/app/api/workcase/QrCodeAPI.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"""二维码相关接口"""
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from app.schemas import ResultDomain
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/generate",
|
||||||
|
response_model=ResultDomain[dict],
|
||||||
|
summary="生成二维码",
|
||||||
|
description="根据内容生成二维码"
|
||||||
|
)
|
||||||
|
async def generate_qrcode(content: str) -> ResultDomain[dict]:
|
||||||
|
"""
|
||||||
|
生成二维码
|
||||||
|
|
||||||
|
- **content**: 二维码内容
|
||||||
|
"""
|
||||||
|
# TODO: 实现二维码生成逻辑
|
||||||
|
return ResultDomain.success(message="生成成功", data={"content": content})
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/parse",
|
||||||
|
response_model=ResultDomain[dict],
|
||||||
|
summary="解析二维码",
|
||||||
|
description="解析二维码图片内容"
|
||||||
|
)
|
||||||
|
async def parse_qrcode(image_url: str) -> ResultDomain[dict]:
|
||||||
|
"""
|
||||||
|
解析二维码
|
||||||
|
|
||||||
|
- **image_url**: 二维码图片URL
|
||||||
|
"""
|
||||||
|
# TODO: 实现二维码解析逻辑
|
||||||
|
return ResultDomain.success(message="解析成功", data={"result": ""})
|
||||||
13
difyPlugin/app/api/workcase/__init__.py
Normal file
13
difyPlugin/app/api/workcase/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# API模块
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from .QrCodeAPI import router as qrcode_router
|
||||||
|
|
||||||
|
|
||||||
|
# 创建主路由器
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
# 注册所有子路由
|
||||||
|
router.include_router(qrcode_router, prefix="/qrcode", tags=["二维码相关服务"])
|
||||||
|
|
||||||
|
__all__ = ["router"]
|
||||||
38
difyPlugin/app/config.py
Normal file
38
difyPlugin/app/config.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"""应用配置管理"""
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
"""应用配置"""
|
||||||
|
# 应用基础配置
|
||||||
|
APP_NAME: str = "DifyPlugin"
|
||||||
|
APP_VERSION: str = "1.0.0"
|
||||||
|
DEBUG: bool = False
|
||||||
|
|
||||||
|
# API配置
|
||||||
|
API_V1_PREFIX: str = "/api/v1"
|
||||||
|
HOST: str = "0.0.0.0"
|
||||||
|
API_HOST: str = "localhost" # OpenAPI servers 显示的地址
|
||||||
|
PORT: int = 8380
|
||||||
|
|
||||||
|
# 跨域配置
|
||||||
|
CORS_ORIGINS: list[str] = ["*"]
|
||||||
|
|
||||||
|
# Redis配置
|
||||||
|
REDIS_HOST: str = "localhost"
|
||||||
|
REDIS_PORT: int = 6379
|
||||||
|
REDIS_PASSWORD: str = "123456"
|
||||||
|
REDIS_DB: int = 0
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env"
|
||||||
|
case_sensitive = True
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def get_settings() -> Settings:
|
||||||
|
return Settings()
|
||||||
|
|
||||||
|
|
||||||
|
settings = get_settings()
|
||||||
1
difyPlugin/app/core/__init__.py
Normal file
1
difyPlugin/app/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Core模块
|
||||||
42
difyPlugin/app/core/exceptions.py
Normal file
42
difyPlugin/app/core/exceptions.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""自定义异常和异常处理器"""
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
from app.schemas.base import ResultDomain
|
||||||
|
|
||||||
|
|
||||||
|
class BusinessException(Exception):
|
||||||
|
"""业务异常"""
|
||||||
|
def __init__(self, code: int = 500, message: str = "业务异常"):
|
||||||
|
self.code = code
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
class NotFoundException(BusinessException):
|
||||||
|
"""资源不存在异常"""
|
||||||
|
def __init__(self, message: str = "资源不存在"):
|
||||||
|
super().__init__(code=404, message=message)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationException(BusinessException):
|
||||||
|
"""参数校验异常"""
|
||||||
|
def __init__(self, message: str = "参数校验失败"):
|
||||||
|
super().__init__(code=400, message=message)
|
||||||
|
|
||||||
|
|
||||||
|
def register_exception_handlers(app: FastAPI):
|
||||||
|
"""注册全局异常处理器"""
|
||||||
|
|
||||||
|
@app.exception_handler(BusinessException)
|
||||||
|
async def business_exception_handler(request: Request, exc: BusinessException):
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=200,
|
||||||
|
content=ResultDomain.fail(message=exc.message, code=exc.code).model_dump()
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.exception_handler(Exception)
|
||||||
|
async def global_exception_handler(request: Request, exc: Exception):
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=500,
|
||||||
|
content=ResultDomain.fail(message=str(exc), code=500).model_dump()
|
||||||
|
)
|
||||||
26
difyPlugin/app/core/middleware.py
Normal file
26
difyPlugin/app/core/middleware.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""中间件定义"""
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
from fastapi import Request
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
||||||
|
"""请求日志中间件"""
|
||||||
|
|
||||||
|
async def dispatch(self, request: Request, call_next):
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
response = await call_next(request)
|
||||||
|
|
||||||
|
process_time = time.time() - start_time
|
||||||
|
logger.info(
|
||||||
|
f"{request.method} {request.url.path} "
|
||||||
|
f"- Status: {response.status_code} "
|
||||||
|
f"- Time: {process_time:.3f}s"
|
||||||
|
)
|
||||||
|
|
||||||
|
response.headers["X-Process-Time"] = str(process_time)
|
||||||
|
return response
|
||||||
128
difyPlugin/app/core/redis.py
Normal file
128
difyPlugin/app/core/redis.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
"""Redis 服务"""
|
||||||
|
import json
|
||||||
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
|
import redis.asyncio as redis
|
||||||
|
from redis.asyncio import Redis
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
|
||||||
|
class RedisService:
|
||||||
|
"""Redis 服务类"""
|
||||||
|
|
||||||
|
_client: Optional[Redis] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def init(cls) -> None:
|
||||||
|
"""初始化 Redis 连接"""
|
||||||
|
cls._client = redis.Redis(
|
||||||
|
host=settings.REDIS_HOST,
|
||||||
|
port=settings.REDIS_PORT,
|
||||||
|
password=settings.REDIS_PASSWORD or None,
|
||||||
|
db=settings.REDIS_DB,
|
||||||
|
decode_responses=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def close(cls) -> None:
|
||||||
|
"""关闭 Redis 连接"""
|
||||||
|
if cls._client:
|
||||||
|
await cls._client.close()
|
||||||
|
cls._client = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_client(cls) -> Redis:
|
||||||
|
"""获取 Redis 客户端"""
|
||||||
|
if not cls._client:
|
||||||
|
raise RuntimeError("Redis 未初始化,请先调用 init()")
|
||||||
|
return cls._client
|
||||||
|
|
||||||
|
# ==================== String 操作 ====================
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get(cls, key: str) -> Optional[str]:
|
||||||
|
"""获取值"""
|
||||||
|
return await cls.get_client().get(key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def set(cls, key: str, value: Union[str, int, float], expire: Optional[int] = None) -> bool:
|
||||||
|
"""设置值"""
|
||||||
|
return await cls.get_client().set(key, value, ex=expire)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def delete(cls, *keys: str) -> int:
|
||||||
|
"""删除键"""
|
||||||
|
return await cls.get_client().delete(*keys)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def exists(cls, key: str) -> bool:
|
||||||
|
"""判断键是否存在"""
|
||||||
|
return await cls.get_client().exists(key) > 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def expire(cls, key: str, seconds: int) -> bool:
|
||||||
|
"""设置过期时间"""
|
||||||
|
return await cls.get_client().expire(key, seconds)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def ttl(cls, key: str) -> int:
|
||||||
|
"""获取剩余过期时间"""
|
||||||
|
return await cls.get_client().ttl(key)
|
||||||
|
|
||||||
|
# ==================== JSON 操作 ====================
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_json(cls, key: str) -> Optional[Any]:
|
||||||
|
"""获取 JSON 值"""
|
||||||
|
value = await cls.get(key)
|
||||||
|
return json.loads(value) if value else None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def set_json(cls, key: str, value: Any, expire: Optional[int] = None) -> bool:
|
||||||
|
"""设置 JSON 值"""
|
||||||
|
return await cls.set(key, json.dumps(value, ensure_ascii=False), expire)
|
||||||
|
|
||||||
|
# ==================== Hash 操作 ====================
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def hget(cls, name: str, key: str) -> Optional[str]:
|
||||||
|
"""获取 Hash 字段值"""
|
||||||
|
return await cls.get_client().hget(name, key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def hset(cls, name: str, key: str, value: str) -> int:
|
||||||
|
"""设置 Hash 字段值"""
|
||||||
|
return await cls.get_client().hset(name, key, value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def hgetall(cls, name: str) -> dict:
|
||||||
|
"""获取 Hash 所有字段"""
|
||||||
|
return await cls.get_client().hgetall(name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def hdel(cls, name: str, *keys: str) -> int:
|
||||||
|
"""删除 Hash 字段"""
|
||||||
|
return await cls.get_client().hdel(name, *keys)
|
||||||
|
|
||||||
|
# ==================== List 操作 ====================
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def lpush(cls, key: str, *values: str) -> int:
|
||||||
|
"""左侧插入列表"""
|
||||||
|
return await cls.get_client().lpush(key, *values)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def rpush(cls, key: str, *values: str) -> int:
|
||||||
|
"""右侧插入列表"""
|
||||||
|
return await cls.get_client().rpush(key, *values)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def lrange(cls, key: str, start: int = 0, end: int = -1) -> list:
|
||||||
|
"""获取列表范围"""
|
||||||
|
return await cls.get_client().lrange(key, start, end)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def llen(cls, key: str) -> int:
|
||||||
|
"""获取列表长度"""
|
||||||
|
return await cls.get_client().llen(key)
|
||||||
78
difyPlugin/app/main.py
Normal file
78
difyPlugin/app/main.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
"""FastAPI 应用入口"""
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
from app.api import router as api_router
|
||||||
|
from app.core.exceptions import register_exception_handlers
|
||||||
|
from app.core.redis import RedisService
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
"""应用生命周期管理"""
|
||||||
|
# 启动时初始化
|
||||||
|
await RedisService.init()
|
||||||
|
yield
|
||||||
|
# 关闭时清理
|
||||||
|
await RedisService.close()
|
||||||
|
|
||||||
|
|
||||||
|
def create_app() -> FastAPI:
|
||||||
|
"""创建FastAPI应用实例"""
|
||||||
|
app = FastAPI(
|
||||||
|
title=settings.APP_NAME,
|
||||||
|
version=settings.APP_VERSION,
|
||||||
|
description="Dify插件服务API",
|
||||||
|
openapi_url=f"{settings.API_V1_PREFIX}/openapi.json",
|
||||||
|
docs_url="/docs",
|
||||||
|
redoc_url="/redoc",
|
||||||
|
lifespan=lifespan,
|
||||||
|
servers=[
|
||||||
|
{"url": f"http://{settings.API_HOST}:{settings.PORT}", "description": "API服务器"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 注册CORS中间件
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=settings.CORS_ORIGINS,
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 注册异常处理器
|
||||||
|
register_exception_handlers(app)
|
||||||
|
|
||||||
|
# 注册路由
|
||||||
|
app.include_router(api_router, prefix=settings.API_V1_PREFIX)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
|
||||||
|
def print_routes(app: FastAPI):
|
||||||
|
"""打印所有注册的路由"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("Registered Routes:")
|
||||||
|
print("=" * 60)
|
||||||
|
for route in app.routes:
|
||||||
|
if hasattr(route, "methods"):
|
||||||
|
methods = ", ".join(route.methods - {"HEAD", "OPTIONS"})
|
||||||
|
print(f" {methods:8} {route.path}")
|
||||||
|
print("=" * 60 + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
# 启动时打印路由
|
||||||
|
print_routes(app)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health", tags=["健康检查"], summary="健康检查接口")
|
||||||
|
async def health_check():
|
||||||
|
"""服务健康检查"""
|
||||||
|
|
||||||
4
difyPlugin/app/schemas/__init__.py
Normal file
4
difyPlugin/app/schemas/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from app.schemas.base import ResultDomain
|
||||||
|
from app.schemas.plugin import PluginRequest, PluginResponse
|
||||||
|
|
||||||
|
__all__ = ["ResultDomain", "PluginRequest", "PluginResponse"]
|
||||||
52
difyPlugin/app/schemas/base.py
Normal file
52
difyPlugin/app/schemas/base.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""统一返回类型定义"""
|
||||||
|
from typing import TypeVar, Generic, Optional, List, Any
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
class PageDomain(BaseModel, Generic[T]):
|
||||||
|
"""分页数据模型"""
|
||||||
|
page: int = Field(default=1, description="当前页码")
|
||||||
|
pageSize: int = Field(default=10, description="每页大小")
|
||||||
|
total: int = Field(default=0, description="总记录数")
|
||||||
|
dataList: Optional[List[T]] = Field(default=None, description="数据列表")
|
||||||
|
|
||||||
|
|
||||||
|
class ResultDomain(BaseModel, Generic[T]):
|
||||||
|
"""统一返回类型"""
|
||||||
|
code: Optional[int] = Field(default=None, description="状态码")
|
||||||
|
success: Optional[bool] = Field(default=None, description="是否成功")
|
||||||
|
message: Optional[str] = Field(default=None, description="返回消息")
|
||||||
|
data: Optional[T] = Field(default=None, description="单条数据")
|
||||||
|
dataList: Optional[List[T]] = Field(default=None, description="数据列表")
|
||||||
|
pageDomain: Optional[PageDomain[T]] = Field(default=None, description="分页数据")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ok(message: str = "success", data: Any = None) -> "ResultDomain":
|
||||||
|
"""成功返回 - 单条数据"""
|
||||||
|
return ResultDomain(code=200, success=True, message=message, data=data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ok_list(message: str = "success", data_list: List[Any] = None) -> "ResultDomain":
|
||||||
|
"""成功返回 - 数据列表"""
|
||||||
|
return ResultDomain(code=200, success=True, message=message, dataList=data_list)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ok_page(message: str = "success", page_domain: "PageDomain" = None) -> "ResultDomain":
|
||||||
|
"""成功返回 - 分页数据"""
|
||||||
|
result = ResultDomain(code=200, success=True, message=message, pageDomain=page_domain)
|
||||||
|
if page_domain:
|
||||||
|
result.dataList = page_domain.dataList
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fail(message: str = "failure", code: int = 500) -> "ResultDomain":
|
||||||
|
"""失败返回"""
|
||||||
|
return ResultDomain(code=code, success=False, message=message)
|
||||||
|
|
||||||
|
model_config = {
|
||||||
|
"json_schema_extra": {
|
||||||
|
"examples": [{"code": 200, "success": True, "message": "操作成功"}]
|
||||||
|
}
|
||||||
|
}
|
||||||
43
difyPlugin/app/schemas/plugin.py
Normal file
43
difyPlugin/app/schemas/plugin.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"""插件相关数据模型"""
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class PluginRequest(BaseModel):
|
||||||
|
"""
|
||||||
|
插件请求模型
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
plugin_id: 插件ID
|
||||||
|
action: 执行动作
|
||||||
|
params: 请求参数
|
||||||
|
"""
|
||||||
|
plugin_id: str = Field(..., description="插件ID", examples=["plugin_001"])
|
||||||
|
action: str = Field(..., description="执行动作", examples=["execute"])
|
||||||
|
params: Optional[Dict[str, Any]] = Field(default=None, description="请求参数")
|
||||||
|
|
||||||
|
model_config = {
|
||||||
|
"json_schema_extra": {
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"plugin_id": "plugin_001",
|
||||||
|
"action": "execute",
|
||||||
|
"params": {"key": "value"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PluginResponse(BaseModel):
|
||||||
|
"""
|
||||||
|
插件响应模型
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
plugin_id: 插件ID
|
||||||
|
result: 执行结果
|
||||||
|
status: 执行状态
|
||||||
|
"""
|
||||||
|
plugin_id: str = Field(..., description="插件ID")
|
||||||
|
result: Optional[Dict[str, Any]] = Field(default=None, description="执行结果")
|
||||||
|
status: str = Field(default="success", description="执行状态")
|
||||||
3
difyPlugin/app/services/__init__.py
Normal file
3
difyPlugin/app/services/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from app.services.plugin_service import PluginService
|
||||||
|
|
||||||
|
__all__ = ["PluginService"]
|
||||||
45
difyPlugin/app/services/plugin_service.py
Normal file
45
difyPlugin/app/services/plugin_service.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"""插件业务逻辑层"""
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
|
||||||
|
from app.schemas.plugin import PluginRequest, PluginResponse
|
||||||
|
|
||||||
|
|
||||||
|
class PluginService:
|
||||||
|
"""插件服务类"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# 模拟插件数据
|
||||||
|
self._plugins: Dict[str, dict] = {
|
||||||
|
"plugin_001": {
|
||||||
|
"id": "plugin_001",
|
||||||
|
"name": "示例插件",
|
||||||
|
"description": "这是一个示例插件",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"enabled": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, request: PluginRequest) -> PluginResponse:
|
||||||
|
"""
|
||||||
|
执行插件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: 插件请求参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PluginResponse: 插件执行结果
|
||||||
|
"""
|
||||||
|
# TODO: 实现具体的插件执行逻辑
|
||||||
|
return PluginResponse(
|
||||||
|
plugin_id=request.plugin_id,
|
||||||
|
result={"executed": True, "action": request.action},
|
||||||
|
status="success"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_all_plugins(self) -> List[dict]:
|
||||||
|
"""获取所有插件列表"""
|
||||||
|
return list(self._plugins.values())
|
||||||
|
|
||||||
|
async def get_plugin_by_id(self, plugin_id: str) -> Optional[dict]:
|
||||||
|
"""根据ID获取插件"""
|
||||||
|
return self._plugins.get(plugin_id)
|
||||||
1
difyPlugin/app/utils/__init__.py
Normal file
1
difyPlugin/app/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Utils模块
|
||||||
22
difyPlugin/app/utils/helpers.py
Normal file
22
difyPlugin/app/utils/helpers.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"""工具函数"""
|
||||||
|
from typing import Any, Dict
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def format_datetime(dt: datetime, fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
|
||||||
|
"""格式化日期时间"""
|
||||||
|
return dt.strftime(fmt)
|
||||||
|
|
||||||
|
|
||||||
|
def safe_json_loads(json_str: str, default: Any = None) -> Any:
|
||||||
|
"""安全的JSON解析"""
|
||||||
|
try:
|
||||||
|
return json.loads(json_str)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def dict_filter_none(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""过滤字典中的None值"""
|
||||||
|
return {k: v for k, v in data.items() if v is not None}
|
||||||
78
difyPlugin/dify-tools.json
Normal file
78
difyPlugin/dify-tools.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "DifyPlugin",
|
||||||
|
"description": "Dify插件服务API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "http://192.168.0.253:8380/api/v1",
|
||||||
|
"description": "开发服务器"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/test/hello/world": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "HelloWord",
|
||||||
|
"summary": "Hello World",
|
||||||
|
"description": "测试接口连通性",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功响应",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ResultDomain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/test/hello/ping": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "Ping",
|
||||||
|
"summary": "Ping测试",
|
||||||
|
"description": "测试服务是否正常运行",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功响应",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ResultDomain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"ResultDomain": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "状态码"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "是否成功"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "返回消息"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"description": "返回数据"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
difyPlugin/requirements.txt
Normal file
7
difyPlugin/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fastapi
|
||||||
|
pydantic
|
||||||
|
pydantic-settings
|
||||||
|
python-dotenv
|
||||||
|
redis
|
||||||
|
anyio>=4.5
|
||||||
|
uvicorn[standard]>=0.31.1
|
||||||
11
difyPlugin/run.py
Normal file
11
difyPlugin/run.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"""启动脚本 - 从config读取配置"""
|
||||||
|
import uvicorn
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run(
|
||||||
|
"app.main:app",
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=settings.PORT,
|
||||||
|
reload=settings.DEBUG
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user