```
feat(feishu): 添加飞书长连接支持并重构事件处理 添加了 FeishuEventService 来统一处理飞书消息事件, 新增 long_connection.py 实现长连接客户端, 修改 webhook 路由使用新的事件处理服务, 添加了 lark-oapi 依赖支持长连接功能, 更新测试用例覆盖新的事件处理逻辑。 BREAKING CHANGE: 飞书事件处理逻辑重构,统一使用 FeishuEventService 进行消息处理和审计记录。 ```
This commit is contained in:
40
.env.example
40
.env.example
@@ -1,40 +0,0 @@
|
||||
APP_NAME=Company AI Management Platform
|
||||
APP_ENV=local
|
||||
DEBUG=true
|
||||
API_PREFIX=/api/v1
|
||||
API_KEY=change-me
|
||||
|
||||
# Main application database. For production, point this to your service database.
|
||||
DATABASE_URL=mysql+pymysql://root:password@127.0.0.1:3306/company_ai?charset=utf8mb4
|
||||
|
||||
# Existing project management system database. Keep this read-only at first.
|
||||
LEGACY_DATABASE_URL=mysql+pymysql://readonly_user:readonly_password@127.0.0.1:3306/existing_project_system?charset=utf8mb4
|
||||
LEGACY_PROJECT_QUERY=SELECT id, name, owner, status, progress, start_date, due_date, budget, actual_cost FROM projects ORDER BY id DESC LIMIT :limit
|
||||
LEGACY_PROJECT_CODE_PREFIX=LEGACY
|
||||
|
||||
REDIS_URL=redis://127.0.0.1:6379/0
|
||||
|
||||
# Feishu / Lark Open Platform.
|
||||
FEISHU_BASE_URL=https://open.feishu.cn/open-apis
|
||||
FEISHU_APP_ID=
|
||||
FEISHU_APP_SECRET=
|
||||
FEISHU_VERIFICATION_TOKEN=
|
||||
FEISHU_ENCRYPT_KEY=
|
||||
FEISHU_DEFAULT_CHAT_ID=
|
||||
|
||||
# AI provider: openclaw, hermes, direct_llm, noop.
|
||||
MODEL_PROVIDER=noop
|
||||
OPENCLAW_BASE_URL=http://127.0.0.1:18789
|
||||
OPENCLAW_API_KEY=
|
||||
HERMES_BASE_URL=http://127.0.0.1:8080
|
||||
HERMES_API_KEY=
|
||||
DIRECT_LLM_BASE_URL=https://api.openai.com/v1
|
||||
DIRECT_LLM_API_KEY=
|
||||
DIRECT_LLM_MODEL=gpt-4.1-mini
|
||||
|
||||
SCHEDULER_ENABLED=false
|
||||
DAILY_BRIEF_CRON_HOUR=9
|
||||
DAILY_BRIEF_CRON_MINUTE=0
|
||||
WEEKLY_PROJECT_REPORT_DAY_OF_WEEK=mon
|
||||
WEEKLY_PROJECT_REPORT_CRON_HOUR=9
|
||||
WEEKLY_PROJECT_REPORT_CRON_MINUTE=30
|
||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.env
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
|
||||
# Local docs and agent instructions
|
||||
/docs/
|
||||
/read.md
|
||||
/README.md
|
||||
/AGENTS.md
|
||||
41
AGENTS.md
41
AGENTS.md
@@ -1,41 +0,0 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
This is a FastAPI modular monolith for company AI management workflows. Source lives in `app/`. Shared infrastructure is under `app/core/`; `app/api/router.py` wires modules under `/api/v1`. Business capabilities live in `app/modules/`: `business`, `legacy_mysql`, `feishu`, `ai_agent`, `reports`, `risk`, `approvals`, and `audit`. Database tools are in `app/tools/`. Tests live in `tests/`, scripts in `scripts/`, and notes in `docs/`.
|
||||
|
||||
## Modular Design & Technology Direction
|
||||
|
||||
All new work must start with modular design: define boundaries, services, schemas, and adapters before coding. Prefer modern, maintained patterns and tech aligned with FastAPI, SQLAlchemy 2, Pydantic Settings, and async-ready integrations. Use service layer, adapter, dependency injection, and ports/adapters for external systems. Add dependencies only with clear benefit.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
Common commands:
|
||||
|
||||
```powershell
|
||||
conda env create -f environment.yml
|
||||
conda env update -f environment.yml --prune
|
||||
conda run -n company-ai-platform python -m app.tools.init_db
|
||||
conda run -n company-ai-platform uvicorn app.main:app --reload --host 0.0.0.0 --port 8010
|
||||
conda run -n company-ai-platform python -m compileall app tests scripts
|
||||
conda run -n company-ai-platform python scripts\verify_smoke.py
|
||||
conda run -n company-ai-platform pytest -q
|
||||
```
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
Use Python 3.11, 4-space indentation, type hints, and concise service classes. Follow `models.py`, `schemas.py`, `service.py`, and `routes.py`. Keep routes thin and put business logic in services. Use snake_case for functions, variables, filenames; use PascalCase for SQLAlchemy models and Pydantic schemas. Ruff uses line length `100`.
|
||||
|
||||
Follow the Google Python Style Guide: group imports as standard library, third-party, local; write docstrings for non-trivial public APIs; prefer explicit exceptions and early returns; and keep functions focused. Use type annotations instead of type comments.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
Tests use `pytest` and FastAPI `TestClient`. Name files `test_*.py` and functions `test_*`. Prefer temporary SQLite databases, as in `tests/test_smoke.py`, so tests do not require MySQL, Feishu, or external AI providers. Cover approval gates, audit-sensitive flows, and API responses for high-risk modules.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
No Git history is available. Use short, imperative commits such as `Add approval audit test`. Pull requests should describe the change, list verification, mention config or migration impacts, and link related issues. Include screenshots only for API docs or visible UI changes.
|
||||
|
||||
## Security & Configuration Tips
|
||||
|
||||
Do not commit real `.env` files or secrets. Start from `.env.example`. Keep `LEGACY_DATABASE_URL` read-only, set `API_KEY` outside local-only testing, and configure Feishu verification tokens before exposing webhooks. High-risk `fund-accounts` and `performance-metrics` updates must require approved tickets.
|
||||
311
README.md
311
README.md
@@ -1,311 +0,0 @@
|
||||
# Company AI Management Platform
|
||||
|
||||
这是一个公司全生命周期 AI 管理系统的后端工程,定位是:
|
||||
|
||||
```text
|
||||
现有项目管理系统 / MySQL
|
||||
+
|
||||
FastAPI AI 集成层
|
||||
+
|
||||
飞书推送与审批入口
|
||||
+
|
||||
OpenClaw / Hermes / 模型适配接口
|
||||
```
|
||||
|
||||
第一版已经实现:
|
||||
|
||||
- FastAPI 后端工程
|
||||
- Conda 独立环境配置
|
||||
- MySQL 主库连接
|
||||
- 现有项目管理系统 MySQL 只读接入
|
||||
- 业务台账 CRUD:项目、任务、采购、费用、资金、制度、规范、绩效、供应商
|
||||
- 飞书文本和卡片推送接口
|
||||
- 飞书 Webhook 入口
|
||||
- 飞书消息命令路由:日报、周报、风险、AI 问答
|
||||
- AI 适配器:OpenClaw、Hermes、OpenAI-compatible、noop
|
||||
- 经营晨报、项目周报
|
||||
- 风险检测:逾期任务、延期项目、超预算项目、资金低于安全线
|
||||
- 审批单:高风险资金/绩效更新必须通过审批 ticket
|
||||
- 现有 MySQL 项目数据同步到内部台账,支持 dry-run 和字段映射
|
||||
- AI 操作审计日志
|
||||
- Docker Compose 本地运行模板
|
||||
|
||||
## 1. 技术栈
|
||||
|
||||
```text
|
||||
Python 3.11
|
||||
FastAPI
|
||||
SQLAlchemy 2
|
||||
MySQL / PyMySQL
|
||||
Redis
|
||||
APScheduler / Celery 预留
|
||||
httpx
|
||||
Pydantic Settings
|
||||
```
|
||||
|
||||
## 2. Conda 环境
|
||||
|
||||
重新创建专用环境:
|
||||
|
||||
```powershell
|
||||
cd C:\Users\20143\Documents\Codex\2026-06-21\new-chat\outputs\company-ai-platform
|
||||
conda env remove -n company-ai-platform
|
||||
conda env create -f environment.yml
|
||||
conda activate company-ai-platform
|
||||
```
|
||||
|
||||
更新已有环境:
|
||||
|
||||
```powershell
|
||||
conda env update -f environment.yml --prune
|
||||
```
|
||||
|
||||
也可以直接运行:
|
||||
|
||||
```powershell
|
||||
.\scripts\recreate_conda_env.ps1
|
||||
```
|
||||
|
||||
## 3. 配置
|
||||
|
||||
复制配置文件:
|
||||
|
||||
```powershell
|
||||
Copy-Item .env.example .env
|
||||
```
|
||||
|
||||
至少配置:
|
||||
|
||||
```text
|
||||
DATABASE_URL=你的新业务库
|
||||
LEGACY_DATABASE_URL=现有项目管理系统的只读 MySQL 账号
|
||||
LEGACY_PROJECT_QUERY=从现有系统读取项目的 SELECT 查询
|
||||
API_KEY=你的内部 API Key
|
||||
```
|
||||
|
||||
飞书和模型可以后续再配置。未配置模型时,`MODEL_PROVIDER=noop` 会返回可预测占位结果,确保项目先能启动。
|
||||
|
||||
## 4. 初始化数据库
|
||||
|
||||
```powershell
|
||||
python -m app.tools.init_db
|
||||
```
|
||||
|
||||
## 5. 启动服务
|
||||
|
||||
```powershell
|
||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8010
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8010/docs
|
||||
```
|
||||
|
||||
带 API Key 请求时加 header:
|
||||
|
||||
```text
|
||||
X-API-Key: 你的 API_KEY
|
||||
```
|
||||
|
||||
## 6. 关键接口
|
||||
|
||||
### 系统健康
|
||||
|
||||
```text
|
||||
GET /api/v1/health
|
||||
```
|
||||
|
||||
### 业务台账
|
||||
|
||||
```text
|
||||
GET /api/v1/business/domains
|
||||
GET /api/v1/business/projects
|
||||
POST /api/v1/business/projects
|
||||
PATCH /api/v1/business/projects/{id}
|
||||
```
|
||||
|
||||
支持的 domain:
|
||||
|
||||
```text
|
||||
projects
|
||||
tasks
|
||||
procurements
|
||||
expenses
|
||||
fund-accounts
|
||||
policies
|
||||
standards
|
||||
performance-metrics
|
||||
suppliers
|
||||
```
|
||||
|
||||
### 现有 MySQL
|
||||
|
||||
```text
|
||||
GET /api/v1/integrations/mysql/health
|
||||
GET /api/v1/integrations/mysql/tables
|
||||
GET /api/v1/integrations/mysql/tables/{table_name}
|
||||
POST /api/v1/integrations/mysql/query
|
||||
GET /api/v1/integrations/mysql/projects
|
||||
POST /api/v1/integrations/mysql/projects/sync
|
||||
```
|
||||
|
||||
只允许 `SELECT`,默认第一阶段不写回原系统。
|
||||
|
||||
项目同步默认 `dry_run=true`,确认映射无误后再设置为 `false`。
|
||||
|
||||
### 飞书
|
||||
|
||||
```text
|
||||
POST /api/v1/integrations/feishu/send-text
|
||||
POST /api/v1/integrations/feishu/send-card
|
||||
POST /api/v1/integrations/feishu/webhook
|
||||
POST /api/v1/integrations/feishu/commands/preview
|
||||
```
|
||||
|
||||
飞书命令支持:
|
||||
|
||||
```text
|
||||
日报 / 晨报 / 经营日报
|
||||
周报 / 项目周报
|
||||
风险 / 预警
|
||||
问 xxx / AI xxx / 普通问题
|
||||
```
|
||||
|
||||
### AI
|
||||
|
||||
```text
|
||||
POST /api/v1/ai/ask
|
||||
POST /api/v1/ai/draft-policy
|
||||
POST /api/v1/ai/investment-research
|
||||
```
|
||||
|
||||
### 审批
|
||||
|
||||
```text
|
||||
POST /api/v1/approvals
|
||||
GET /api/v1/approvals
|
||||
GET /api/v1/approvals/{ticket_id}
|
||||
POST /api/v1/approvals/{ticket_id}/approve
|
||||
POST /api/v1/approvals/{ticket_id}/reject
|
||||
```
|
||||
|
||||
`fund-accounts` 和 `performance-metrics` 更新需要已批准的 `approval_ticket_id`。
|
||||
|
||||
### 报表
|
||||
|
||||
```text
|
||||
GET /api/v1/reports/daily-brief
|
||||
GET /api/v1/reports/project-weekly
|
||||
POST /api/v1/reports/daily-brief/push
|
||||
POST /api/v1/reports/project-weekly/push
|
||||
```
|
||||
|
||||
### 风险
|
||||
|
||||
```text
|
||||
GET /api/v1/risks/summary
|
||||
GET /api/v1/risks/overdue-tasks
|
||||
GET /api/v1/risks/delayed-projects
|
||||
GET /api/v1/risks/over-budget-projects
|
||||
GET /api/v1/risks/funds
|
||||
```
|
||||
|
||||
## 7. 接入现有项目管理系统
|
||||
|
||||
第一阶段建议只读接入:
|
||||
|
||||
```text
|
||||
现有项目系统 MySQL -> FastAPI 只读查询 -> 报表/风险 -> 飞书推送
|
||||
```
|
||||
|
||||
不要一开始让 AI 直接改原系统数据库。
|
||||
|
||||
配置示例:
|
||||
|
||||
```text
|
||||
LEGACY_DATABASE_URL=mysql+pymysql://readonly_user:password@10.0.0.10:3306/project_system?charset=utf8mb4
|
||||
LEGACY_PROJECT_QUERY=SELECT id, name, owner, status, progress, start_date, due_date, budget, actual_cost FROM project ORDER BY id DESC LIMIT :limit
|
||||
```
|
||||
|
||||
## 8. 接入 OpenClaw / Hermes / 模型
|
||||
|
||||
OpenClaw:
|
||||
|
||||
```text
|
||||
MODEL_PROVIDER=openclaw
|
||||
OPENCLAW_BASE_URL=http://127.0.0.1:18789
|
||||
OPENCLAW_API_KEY=
|
||||
```
|
||||
|
||||
Hermes:
|
||||
|
||||
```text
|
||||
MODEL_PROVIDER=hermes
|
||||
HERMES_BASE_URL=http://127.0.0.1:8080
|
||||
HERMES_API_KEY=
|
||||
```
|
||||
|
||||
OpenAI-compatible:
|
||||
|
||||
```text
|
||||
MODEL_PROVIDER=direct_llm
|
||||
DIRECT_LLM_BASE_URL=https://api.openai.com/v1
|
||||
DIRECT_LLM_API_KEY=你的Key
|
||||
DIRECT_LLM_MODEL=gpt-4.1-mini
|
||||
```
|
||||
|
||||
## 9. 安全边界
|
||||
|
||||
已经内置的边界:
|
||||
|
||||
- 现有 MySQL 只允许 SELECT
|
||||
- 高风险模块更新需要 `approval_ticket_id`
|
||||
- 飞书和 AI 动作写入审计日志
|
||||
- API 支持 `X-API-Key`
|
||||
- AI 不直接审批付款、绩效、投资交易
|
||||
|
||||
后续建议补:
|
||||
|
||||
- 用户登录
|
||||
- RBAC 角色权限
|
||||
- 字段级脱敏
|
||||
- 飞书审批回调
|
||||
- 数据库迁移 Alembic
|
||||
- 对接真实财务/采购系统 API
|
||||
|
||||
## 10. 下一步实施顺序
|
||||
|
||||
```text
|
||||
1. 配置现有 MySQL 只读账号
|
||||
2. 调通 /integrations/mysql/health
|
||||
3. 调通 /integrations/mysql/projects
|
||||
4. 初始化新业务库
|
||||
5. 导入或同步项目数据
|
||||
6. 配置飞书应用
|
||||
7. 调通飞书 send-text/send-card
|
||||
8. 配置 MODEL_PROVIDER
|
||||
9. 开启日报、周报、风险推送
|
||||
10. 再逐步做写回和审批闭环
|
||||
```
|
||||
|
||||
## 11. 验证
|
||||
|
||||
编译:
|
||||
|
||||
```powershell
|
||||
conda run -n company-ai-platform python -m compileall app tests scripts
|
||||
```
|
||||
|
||||
Smoke 验证:
|
||||
|
||||
```powershell
|
||||
conda run -n company-ai-platform python scripts\verify_smoke.py
|
||||
```
|
||||
|
||||
测试:
|
||||
|
||||
```powershell
|
||||
conda run -n company-ai-platform pytest -q
|
||||
```
|
||||
43
app/modules/feishu/events.py
Normal file
43
app/modules/feishu/events.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.modules.audit.schemas import AuditLogCreate
|
||||
from app.modules.feishu.commands import FeishuCommandService
|
||||
from app.modules.feishu.service import FeishuService
|
||||
|
||||
|
||||
class FeishuEventService:
|
||||
"""Handle Feishu message events from webhook or long connection."""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.feishu = FeishuService(db)
|
||||
self.commands = FeishuCommandService(db)
|
||||
|
||||
def handle_event(
|
||||
self,
|
||||
payload: dict[str, Any],
|
||||
source: str,
|
||||
auto_reply: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
self.feishu.verify_event(payload)
|
||||
self.feishu.audit.log(
|
||||
AuditLogCreate(
|
||||
actor="feishu",
|
||||
source="feishu",
|
||||
action=f"{source}_event",
|
||||
request_payload=payload,
|
||||
response_payload={"accepted": True},
|
||||
)
|
||||
)
|
||||
command = self.commands.extract_event_command(payload)
|
||||
if not command:
|
||||
return {"ok": True, "handled": False}
|
||||
result = self.commands.handle_text(
|
||||
command["text"],
|
||||
chat_id=command["chat_id"],
|
||||
actor=command["actor"],
|
||||
auto_reply=auto_reply,
|
||||
)
|
||||
return {"ok": True, "handled": True, "result": result}
|
||||
79
app/modules/feishu/long_connection.py
Normal file
79
app/modules/feishu/long_connection.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.feishu.events import FeishuEventService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _sdk_domain(base_url: str) -> str:
|
||||
parsed = urlsplit(base_url)
|
||||
if not parsed.scheme or not parsed.netloc:
|
||||
return "https://open.feishu.cn"
|
||||
return f"{parsed.scheme}://{parsed.netloc}"
|
||||
|
||||
|
||||
def _sdk_event_to_payload(event: Any) -> dict[str, Any]:
|
||||
try:
|
||||
from lark_oapi.core.json import JSON
|
||||
except ImportError as exc:
|
||||
raise RuntimeError("lark-oapi is required for Feishu long connection") from exc
|
||||
|
||||
data = JSON.marshal(event)
|
||||
if not data:
|
||||
return {}
|
||||
return json.loads(data)
|
||||
|
||||
|
||||
def _handle_message_event(event: Any) -> None:
|
||||
payload = _sdk_event_to_payload(event)
|
||||
db = SessionLocal()
|
||||
try:
|
||||
result = FeishuEventService(db).handle_event(
|
||||
payload,
|
||||
source="long_connection",
|
||||
auto_reply=True,
|
||||
)
|
||||
logger.info("Handled Feishu long connection event: %s", result)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def run_long_connection() -> None:
|
||||
"""Start the Feishu long connection client and block forever."""
|
||||
|
||||
settings = get_settings()
|
||||
if not settings.feishu_app_id or not settings.feishu_app_secret:
|
||||
raise RuntimeError("FEISHU_APP_ID and FEISHU_APP_SECRET are required")
|
||||
|
||||
try:
|
||||
import lark_oapi as lark
|
||||
except ImportError as exc:
|
||||
raise RuntimeError("Install lark-oapi before starting Feishu long connection") from exc
|
||||
|
||||
event_handler = (
|
||||
lark.EventDispatcherHandler.builder(
|
||||
settings.feishu_encrypt_key or "",
|
||||
settings.feishu_verification_token or "",
|
||||
)
|
||||
.register_p2_im_message_receive_v1(_handle_message_event)
|
||||
.build()
|
||||
)
|
||||
client = lark.ws.Client(
|
||||
app_id=settings.feishu_app_id,
|
||||
app_secret=settings.feishu_app_secret,
|
||||
event_handler=event_handler,
|
||||
log_level=lark.LogLevel.WARNING,
|
||||
domain=_sdk_domain(settings.feishu_base_url),
|
||||
)
|
||||
logger.info("Starting Feishu long connection client")
|
||||
client.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
run_long_connection()
|
||||
@@ -3,8 +3,8 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import require_api_key
|
||||
from app.modules.audit.schemas import AuditLogCreate
|
||||
from app.modules.feishu.commands import FeishuCommandService
|
||||
from app.modules.feishu.events import FeishuEventService
|
||||
from app.modules.feishu.schemas import (
|
||||
FeishuCardMessage,
|
||||
FeishuCommandRequest,
|
||||
@@ -26,25 +26,7 @@ async def feishu_webhook(request: Request, db: Session = Depends(get_db)) -> dic
|
||||
service.verify_event(payload)
|
||||
if payload.get("challenge"):
|
||||
return {"challenge": payload["challenge"]}
|
||||
service.audit.log(
|
||||
AuditLogCreate(
|
||||
actor="feishu",
|
||||
source="feishu",
|
||||
action="webhook_event",
|
||||
request_payload=payload,
|
||||
response_payload={"accepted": True},
|
||||
)
|
||||
)
|
||||
command = FeishuCommandService(db).extract_event_command(payload)
|
||||
if not command:
|
||||
return {"ok": True, "handled": False}
|
||||
result = FeishuCommandService(db).handle_text(
|
||||
command["text"],
|
||||
chat_id=command["chat_id"],
|
||||
actor=command["actor"],
|
||||
auto_reply=True,
|
||||
)
|
||||
return {"ok": True, "handled": True, "result": result}
|
||||
return FeishuEventService(db).handle_event(payload, source="webhook", auto_reply=True)
|
||||
|
||||
|
||||
@router.post("/send-text", response_model=FeishuSendResult, dependencies=[Depends(require_api_key)])
|
||||
|
||||
@@ -20,7 +20,8 @@ class FeishuService:
|
||||
def verify_event(self, payload: dict[str, Any]) -> None:
|
||||
settings = get_settings()
|
||||
expected = settings.feishu_verification_token
|
||||
token = payload.get("token")
|
||||
header = payload.get("header") or {}
|
||||
token = payload.get("token") or header.get("token")
|
||||
if expected and token and token != expected:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
||||
BIN
company_ai_local.db
Normal file
BIN
company_ai_local.db
Normal file
Binary file not shown.
@@ -1,73 +0,0 @@
|
||||
# AI 接入说明
|
||||
|
||||
## Provider 选择
|
||||
|
||||
```text
|
||||
MODEL_PROVIDER=noop
|
||||
MODEL_PROVIDER=openclaw
|
||||
MODEL_PROVIDER=hermes
|
||||
MODEL_PROVIDER=direct_llm
|
||||
```
|
||||
|
||||
## 统一接口
|
||||
|
||||
业务层只调用:
|
||||
|
||||
```text
|
||||
AIService.ask(prompt, context)
|
||||
```
|
||||
|
||||
所以后续切换 OpenClaw、Hermes 或 OpenAI-compatible 模型时,不需要改业务模块。
|
||||
|
||||
## OpenClaw
|
||||
|
||||
```text
|
||||
MODEL_PROVIDER=openclaw
|
||||
OPENCLAW_BASE_URL=http://127.0.0.1:18789
|
||||
OPENCLAW_API_KEY=
|
||||
```
|
||||
|
||||
适合:
|
||||
|
||||
- 飞书/桌面/手机协同
|
||||
- 工具调用
|
||||
- 执行网关
|
||||
|
||||
## Hermes
|
||||
|
||||
```text
|
||||
MODEL_PROVIDER=hermes
|
||||
HERMES_BASE_URL=http://127.0.0.1:8080
|
||||
HERMES_API_KEY=
|
||||
```
|
||||
|
||||
适合:
|
||||
|
||||
- 长期记忆
|
||||
- 技能沉淀
|
||||
- 自学习
|
||||
|
||||
## Direct LLM
|
||||
|
||||
```text
|
||||
MODEL_PROVIDER=direct_llm
|
||||
DIRECT_LLM_BASE_URL=https://api.openai.com/v1
|
||||
DIRECT_LLM_API_KEY=
|
||||
DIRECT_LLM_MODEL=gpt-4.1-mini
|
||||
```
|
||||
|
||||
## 安全原则
|
||||
|
||||
AI 可以:
|
||||
|
||||
- 查询数据
|
||||
- 生成报告
|
||||
- 生成草稿
|
||||
- 推送提醒
|
||||
|
||||
AI 不可以自动:
|
||||
|
||||
- 审批付款
|
||||
- 修改绩效最终分
|
||||
- 删除业务数据
|
||||
- 自动投资下单
|
||||
@@ -1,55 +0,0 @@
|
||||
# 架构说明
|
||||
|
||||
## 总体架构
|
||||
|
||||
```text
|
||||
用户 / 管理层 / 财务 / 项目负责人
|
||||
|
|
||||
v
|
||||
飞书入口:群聊、私聊、卡片、审批、文档
|
||||
|
|
||||
v
|
||||
FastAPI 集成层
|
||||
|
|
||||
|-- legacy_mysql:读取现有项目管理系统 MySQL
|
||||
|-- business:内部业务台账
|
||||
|-- reports:日报、周报、经营报告
|
||||
|-- risk:延期、超预算、资金风险
|
||||
|-- feishu:飞书推送和 Webhook
|
||||
|-- ai_agent:OpenClaw / Hermes / 模型适配
|
||||
|-- audit:审计日志
|
||||
|
|
||||
v
|
||||
MySQL / Redis / OpenClaw / Hermes / Feishu Open Platform
|
||||
```
|
||||
|
||||
## 为什么先做模块化单体
|
||||
|
||||
当前最重要的是把流程跑通,而不是上来拆微服务。
|
||||
|
||||
模块化单体的好处:
|
||||
|
||||
- 开发和部署简单
|
||||
- 业务边界清晰
|
||||
- 后期可以按模块拆服务
|
||||
- 适合已有 MySQL 系统的外挂式增强
|
||||
|
||||
后期可以拆分:
|
||||
|
||||
```text
|
||||
ai-service
|
||||
feishu-service
|
||||
report-service
|
||||
risk-service
|
||||
finance-service
|
||||
investment-service
|
||||
```
|
||||
|
||||
## 数据边界
|
||||
|
||||
```text
|
||||
现有项目系统 MySQL:事实源,第一阶段只读
|
||||
新业务库 MySQL:AI 中台自有数据、审计日志、补充台账
|
||||
飞书:协同入口和消息入口
|
||||
AI 记忆:偏好、经验、技能,不保存财务事实
|
||||
```
|
||||
@@ -1,102 +0,0 @@
|
||||
# 定制化流程
|
||||
|
||||
## 项目跟踪
|
||||
|
||||
```text
|
||||
现有 MySQL 读取项目
|
||||
-> 风险引擎检查延期/超预算
|
||||
-> AI 生成项目摘要
|
||||
-> 飞书项目群推送
|
||||
-> 负责人确认
|
||||
-> 必要时进入审批或整改
|
||||
```
|
||||
|
||||
## 采购
|
||||
|
||||
```text
|
||||
采购申请
|
||||
-> 关联项目和预算
|
||||
-> AI 检查重复采购、供应商风险、是否三方比价
|
||||
-> 生成比价报告
|
||||
-> 飞书审批
|
||||
-> 下单/验收/付款
|
||||
-> 审计归档
|
||||
```
|
||||
|
||||
## 费用
|
||||
|
||||
```text
|
||||
费用申请
|
||||
-> 分类
|
||||
-> 关联项目/部门/预算
|
||||
-> 检查发票和凭证
|
||||
-> 飞书审批
|
||||
-> 财务付款
|
||||
-> 月度分析
|
||||
```
|
||||
|
||||
## 账户资金
|
||||
|
||||
```text
|
||||
导入账户余额
|
||||
-> 汇总应收应付
|
||||
-> 计算安全线
|
||||
-> 识别资金缺口
|
||||
-> 飞书资金日报
|
||||
```
|
||||
|
||||
## 制度和规范
|
||||
|
||||
```text
|
||||
制度草案
|
||||
-> AI 辅助起草
|
||||
-> 管理层审批
|
||||
-> 飞书 Wiki 发布
|
||||
-> 签收
|
||||
-> AI 抽取检查项
|
||||
-> 执行检查
|
||||
```
|
||||
|
||||
## 绩效
|
||||
|
||||
```text
|
||||
指标定义
|
||||
-> 绑定数据来源
|
||||
-> 自动计算草稿
|
||||
-> AI 解释
|
||||
-> 部门负责人确认
|
||||
-> 员工申诉
|
||||
-> 管理层最终确认
|
||||
```
|
||||
|
||||
AI 不能自动最终定绩效。
|
||||
|
||||
## 金融投资
|
||||
|
||||
```text
|
||||
研究
|
||||
-> 模拟交易
|
||||
-> 风控验证
|
||||
-> 人工审批
|
||||
-> 半自动执行
|
||||
```
|
||||
|
||||
第一版只做投资研究接口,不做真实交易。
|
||||
|
||||
## 审批闭环
|
||||
|
||||
```text
|
||||
高风险动作
|
||||
-> 创建审批单
|
||||
-> 管理者批准或拒绝
|
||||
-> 系统校验 approval_ticket_id
|
||||
-> 执行业务更新
|
||||
-> 写入审计日志
|
||||
```
|
||||
|
||||
当前强制审批的模块:
|
||||
|
||||
```text
|
||||
fund-accounts
|
||||
performance-metrics
|
||||
```
|
||||
@@ -1,84 +0,0 @@
|
||||
# 飞书接入说明
|
||||
|
||||
## 飞书应用权限
|
||||
|
||||
建议先申请最小权限:
|
||||
|
||||
- 发送消息
|
||||
- 读取群信息
|
||||
- 接收消息事件
|
||||
- 卡片消息
|
||||
|
||||
后续再逐步增加:
|
||||
|
||||
- 多维表格
|
||||
- 审批
|
||||
- 文档/Wiki
|
||||
- 任务
|
||||
- 日历
|
||||
|
||||
## 配置项
|
||||
|
||||
```text
|
||||
FEISHU_APP_ID=
|
||||
FEISHU_APP_SECRET=
|
||||
FEISHU_VERIFICATION_TOKEN=
|
||||
FEISHU_DEFAULT_CHAT_ID=
|
||||
```
|
||||
|
||||
## 推送文本
|
||||
|
||||
```text
|
||||
POST /api/v1/integrations/feishu/send-text
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"receive_id": "oc_xxx",
|
||||
"receive_id_type": "chat_id",
|
||||
"text": "项目日报测试"
|
||||
}
|
||||
```
|
||||
|
||||
## 推送卡片
|
||||
|
||||
```text
|
||||
POST /api/v1/integrations/feishu/send-card
|
||||
```
|
||||
|
||||
## Webhook
|
||||
|
||||
```text
|
||||
POST /api/v1/integrations/feishu/webhook
|
||||
```
|
||||
|
||||
当前 Webhook 已支持 challenge 验证和事件审计。后续可在这里接入:
|
||||
|
||||
- 飞书群聊问答
|
||||
- 审批回调
|
||||
- 卡片按钮回调
|
||||
- 任务状态同步
|
||||
|
||||
## 消息命令
|
||||
|
||||
Webhook 已支持基础命令路由:
|
||||
|
||||
```text
|
||||
日报 / 晨报 -> 每日经营晨报
|
||||
周报 / 项目周报 -> 项目周报
|
||||
风险 / 预警 -> 风险摘要
|
||||
问 xxx / AI xxx -> AI 问答
|
||||
```
|
||||
|
||||
本地预览:
|
||||
|
||||
```text
|
||||
POST /api/v1/integrations/feishu/commands/preview
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "日报",
|
||||
"auto_reply": false
|
||||
}
|
||||
```
|
||||
@@ -1,94 +0,0 @@
|
||||
# 现有 MySQL 接入说明
|
||||
|
||||
## 推荐方式
|
||||
|
||||
先创建只读账号:
|
||||
|
||||
```sql
|
||||
CREATE USER 'company_ai_ro'@'%' IDENTIFIED BY 'strong_password';
|
||||
GRANT SELECT ON existing_project_system.* TO 'company_ai_ro'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```text
|
||||
LEGACY_DATABASE_URL=mysql+pymysql://company_ai_ro:strong_password@host:3306/existing_project_system?charset=utf8mb4
|
||||
```
|
||||
|
||||
## 表结构探查
|
||||
|
||||
```text
|
||||
GET /api/v1/integrations/mysql/tables
|
||||
GET /api/v1/integrations/mysql/tables/{table_name}
|
||||
```
|
||||
|
||||
## 只读查询
|
||||
|
||||
```json
|
||||
{
|
||||
"sql": "SELECT id, name, status FROM projects WHERE status != :status",
|
||||
"params": {"status": "closed"},
|
||||
"limit": 100
|
||||
}
|
||||
```
|
||||
|
||||
系统会拒绝:
|
||||
|
||||
```text
|
||||
INSERT
|
||||
UPDATE
|
||||
DELETE
|
||||
DROP
|
||||
ALTER
|
||||
TRUNCATE
|
||||
CREATE
|
||||
```
|
||||
|
||||
## 项目同步
|
||||
|
||||
预览同步:
|
||||
|
||||
```text
|
||||
POST /api/v1/integrations/mysql/projects/sync
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"dry_run": true,
|
||||
"limit": 100,
|
||||
"field_map": {
|
||||
"name": "project_name",
|
||||
"owner": "manager_name",
|
||||
"status": "project_status",
|
||||
"progress_percent": "progress",
|
||||
"budget_amount": "budget",
|
||||
"actual_amount": "actual_cost"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
确认无误后正式同步:
|
||||
|
||||
```json
|
||||
{
|
||||
"dry_run": false,
|
||||
"limit": 100,
|
||||
"actor": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
同步只写入本系统内部 `projects` 表,不写回原项目管理系统。
|
||||
|
||||
## 写回策略
|
||||
|
||||
第一阶段不要写回。
|
||||
|
||||
第二阶段如果必须写回,优先走原项目管理系统 API;没有 API 时,再做受控写回,并必须包含:
|
||||
|
||||
- 权限校验
|
||||
- 参数校验
|
||||
- 审批单号
|
||||
- 事务
|
||||
- 审计日志
|
||||
- 回滚方案
|
||||
@@ -12,6 +12,7 @@ dependencies:
|
||||
- pydantic-settings==2.7.1
|
||||
- python-dotenv==1.0.1
|
||||
- httpx==0.28.1
|
||||
- lark-oapi==1.6.8
|
||||
- apscheduler==3.10.4
|
||||
- redis==5.2.1
|
||||
- celery==5.4.0
|
||||
|
||||
14
logs/feishu_long_connection.err.log
Normal file
14
logs/feishu_long_connection.err.log
Normal file
@@ -0,0 +1,14 @@
|
||||
INFO:__main__:Starting Feishu long connection client
|
||||
INFO:httpx:HTTP Request: POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal "HTTP/1.1 200 OK"
|
||||
INFO:httpx:HTTP Request: POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id "HTTP/1.1 200 OK"
|
||||
INFO:__main__:Handled Feishu long connection event: {'ok': True, 'handled': True, 'result': {'command': 'risk_summary', 'reply_type': 'card', 'title': '<27><><EFBFBD><EFBFBD>Ԥ<EFBFBD><D4A4>', 'content': '- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low\n- <20><><EFBFBD>շ֣<D5B7>0\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\n- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\n- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0', 'lines': ['- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low', '- <20><><EFBFBD>շ֣<D5B7>0', '- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0', '- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0', '- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0', '- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0'], 'provider_response': {'code': 0, 'data': {'body': {'content': '{"title":"<22><><EFBFBD><EFBFBD>Ԥ<EFBFBD><D4A4>","elements":[[{"tag":"text","text":"- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low\\n- <20><><EFBFBD>շ֣<D5B7>0\\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0\\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\\n- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\\n- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0"}]]}'}, 'chat_id': 'oc_4ea0ed9fbfe4da2aca67cca9ee22cb36', 'create_time': '1782103535265', 'deleted': False, 'message_id': 'om_x100b6cbf902318acb15878ba4b86f90', 'msg_type': 'interactive', 'sender': {'id': 'cli_aab22b1674799bef', 'id_type': 'app_id', 'sender_type': 'app', 'tenant_key': '1b3485e1c8ea174f'}, 'update_time': '1782103535265', 'updated': False}, 'msg': 'success'}}}
|
||||
INFO:httpx:HTTP Request: POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal "HTTP/1.1 200 OK"
|
||||
INFO:httpx:HTTP Request: POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id "HTTP/1.1 200 OK"
|
||||
INFO:__main__:Handled Feishu long connection event: {'ok': True, 'handled': True, 'result': {'command': 'risk_summary', 'reply_type': 'card', 'title': '<27><><EFBFBD><EFBFBD>Ԥ<EFBFBD><D4A4>', 'content': '- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low\n- <20><><EFBFBD>շ֣<D5B7>0\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\n- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\n- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0', 'lines': ['- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low', '- <20><><EFBFBD>շ֣<D5B7>0', '- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0', '- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0', '- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0', '- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0'], 'provider_response': {'code': 0, 'data': {'body': {'content': '{"title":"<22><><EFBFBD><EFBFBD>Ԥ<EFBFBD><D4A4>","elements":[[{"tag":"text","text":"- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low\\n- <20><><EFBFBD>շ֣<D5B7>0\\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0\\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\\n- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\\n- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0"}]]}'}, 'chat_id': 'oc_4ea0ed9fbfe4da2aca67cca9ee22cb36', 'create_time': '1782103549102', 'deleted': False, 'message_id': 'om_x100b6cbf91060cacb1fa837ac0932ee', 'msg_type': 'interactive', 'sender': {'id': 'cli_aab22b1674799bef', 'id_type': 'app_id', 'sender_type': 'app', 'tenant_key': '1b3485e1c8ea174f'}, 'update_time': '1782103549102', 'updated': False}, 'msg': 'success'}}}
|
||||
INFO:httpx:HTTP Request: POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal "HTTP/1.1 200 OK"
|
||||
INFO:httpx:HTTP Request: POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id "HTTP/1.1 200 OK"
|
||||
INFO:__main__:Handled Feishu long connection event: {'ok': True, 'handled': True, 'result': {'command': 'fallback_ai', 'reply_type': 'text', 'title': 'AI <20>ظ<EFBFBD>', 'content': 'AI provider is not configured yet. This is a deterministic placeholder. Set MODEL_PROVIDER to openclaw, hermes, or direct_llm after credentials are ready.', 'provider_response': {'code': 0, 'data': {'body': {'content': '{"text":"AI provider is not configured yet. This is a deterministic placeholder. Set MODEL_PROVIDER to openclaw, hermes, or direct_llm after credentials are ready."}'}, 'chat_id': 'oc_4ea0ed9fbfe4da2aca67cca9ee22cb36', 'create_time': '1782103556285', 'deleted': False, 'message_id': 'om_x100b6cbfae8fc8acb03feca923be229', 'msg_type': 'text', 'sender': {'id': 'cli_aab22b1674799bef', 'id_type': 'app_id', 'sender_type': 'app', 'tenant_key': '1b3485e1c8ea174f'}, 'update_time': '1782103556285', 'updated': False}, 'msg': 'success'}}}
|
||||
INFO:httpx:HTTP Request: POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal "HTTP/1.1 200 OK"
|
||||
INFO:httpx:HTTP Request: POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id "HTTP/1.1 200 OK"
|
||||
INFO:__main__:Handled Feishu long connection event: {'ok': True, 'handled': True, 'result': {'command': 'risk_summary', 'reply_type': 'card', 'title': '<27><><EFBFBD><EFBFBD>Ԥ<EFBFBD><D4A4>', 'content': '- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low\n- <20><><EFBFBD>շ֣<D5B7>0\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\n- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\n- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0', 'lines': ['- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low', '- <20><><EFBFBD>շ֣<D5B7>0', '- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0', '- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0', '- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0', '- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0'], 'provider_response': {'code': 0, 'data': {'body': {'content': '{"title":"<22><><EFBFBD><EFBFBD>Ԥ<EFBFBD><D4A4>","elements":[[{"tag":"text","text":"- <20>ۺϷ<DBBA><CFB7>յȼ<D5B5><C8BC><EFBFBD>low\\n- <20><><EFBFBD>շ֣<D5B7>0\\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0\\n- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\\n- <20><>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>0\\n- <20>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><CBBB><EFBFBD>0"}]]}'}, 'chat_id': 'oc_4ea0ed9fbfe4da2aca67cca9ee22cb36', 'create_time': '1782105193432', 'deleted': False, 'message_id': 'om_x100b6cb8085c94a4b12d99465abb1ca', 'msg_type': 'interactive', 'sender': {'id': 'cli_aab22b1674799bef', 'id_type': 'app_id', 'sender_type': 'app', 'tenant_key': '1b3485e1c8ea174f'}, 'update_time': '1782105193432', 'updated': False}, 'msg': 'success'}}}
|
||||
ERROR:Lark:receive message loop exit, err: no close frame received or sent [conn_id=7654075862284225524]
|
||||
1
logs/feishu_long_connection.out.log
Normal file
1
logs/feishu_long_connection.out.log
Normal file
@@ -0,0 +1 @@
|
||||
[Lark] [2026-06-22 13:36:22,942] [ERROR] receive message loop exit, err: no close frame received or sent [conn_id=7654075862284225524]
|
||||
48
read.md
48
read.md
@@ -1,48 +0,0 @@
|
||||
# 项目位置与启动说明
|
||||
|
||||
## 项目位置
|
||||
|
||||
当前项目已经移动到:
|
||||
|
||||
```text
|
||||
D:\Python\AI\company-ai-platform
|
||||
```
|
||||
|
||||
原来的临时输出目录已经不再使用:
|
||||
|
||||
```text
|
||||
C:\Users\20143\Documents\Codex\2026-06-21\new-chat\outputs\company-ai-platform
|
||||
```
|
||||
|
||||
## Conda 环境
|
||||
|
||||
Conda 环境名:
|
||||
|
||||
```text
|
||||
company-ai-platform
|
||||
```
|
||||
|
||||
## 启动命令
|
||||
|
||||
在 PowerShell 中执行:
|
||||
|
||||
```powershell
|
||||
cd D:\Python\AI\company-ai-platform
|
||||
conda activate company-ai-platform
|
||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8010
|
||||
```
|
||||
|
||||
启动后访问接口文档:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8010/docs
|
||||
```
|
||||
|
||||
## 常用验证命令
|
||||
|
||||
```powershell
|
||||
conda run -n company-ai-platform python -m compileall app tests scripts
|
||||
conda run -n company-ai-platform python scripts\verify_smoke.py
|
||||
conda run -n company-ai-platform pytest -q
|
||||
```
|
||||
|
||||
1
scripts/run_feishu_long_connection.ps1
Normal file
1
scripts/run_feishu_long_connection.ps1
Normal file
@@ -0,0 +1 @@
|
||||
conda run -n company-ai-platform python -m app.modules.feishu.long_connection
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
@@ -7,6 +8,8 @@ _db.close()
|
||||
|
||||
os.environ["DATABASE_URL"] = "sqlite:///" + _db.name.replace("\\", "/")
|
||||
os.environ["API_KEY"] = "test-key"
|
||||
os.environ["FEISHU_APP_ID"] = ""
|
||||
os.environ["FEISHU_APP_SECRET"] = ""
|
||||
os.environ["MODEL_PROVIDER"] = "noop"
|
||||
os.environ["SCHEDULER_ENABLED"] = "false"
|
||||
|
||||
@@ -60,6 +63,26 @@ def test_project_report_and_feishu_command_preview() -> None:
|
||||
assert response.json()["command"] == "daily_brief"
|
||||
|
||||
|
||||
def test_feishu_webhook_routes_message_event() -> None:
|
||||
payload = {
|
||||
"schema": "2.0",
|
||||
"header": {"event_type": "im.message.receive_v1"},
|
||||
"event": {
|
||||
"sender": {"sender_id": {"open_id": "ou_test"}},
|
||||
"message": {
|
||||
"chat_id": "oc_test",
|
||||
"message_type": "text",
|
||||
"content": json.dumps({"text": "risk"}),
|
||||
},
|
||||
},
|
||||
}
|
||||
response = client.post("/api/v1/integrations/feishu/webhook", json=payload)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["handled"] is True
|
||||
assert data["result"]["command"] == "risk_summary"
|
||||
|
||||
|
||||
def test_approval_gate_for_high_risk_update() -> None:
|
||||
create_response = client.post(
|
||||
"/api/v1/business/fund-accounts",
|
||||
|
||||
Reference in New Issue
Block a user