From 37d43bd17725f33c9532e015c85d3fa9c92a61e0 Mon Sep 17 00:00:00 2001 From: pgmsoul Date: Mon, 22 Jun 2026 10:21:13 +0800 Subject: [PATCH] add docs --- .env.example | 2 ++ README.md | 30 ++++++++++++++++++++++++++++++ src/openocr_markdown/api.py | 27 +++++++++++++++++---------- src/openocr_markdown/config.py | 8 ++++++++ 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index a5cae9c..5b52462 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +# Reverse-proxy subpath, e.g. /openocr when served at https://example.com/openocr/ +OPENOCR_ROOT_PATH= OPENOCR_OUTPUT_DIR=data/output OPENOCR_UPLOAD_DIR=data/uploads OPENOCR_USE_GPU=false diff --git a/README.md b/README.md index ca92d91..3f3761c 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ docker compose up -d --build # 重新构建并启动 | 变量 | 默认值 | 说明 | |------|--------|------| +| `OPENOCR_ROOT_PATH` | *(空)* | 反向代理子路径,如 `/openocr` | | `OPENOCR_OUTPUT_DIR` | `data/output` | 解析结果输出目录 | | `OPENOCR_UPLOAD_DIR` | `data/uploads` | 上传文件临时目录 | | `OPENOCR_USE_GPU` | `false` | 是否使用 GPU | @@ -191,6 +192,35 @@ docker compose up -d --build # 重新构建并启动 └── .env.example ``` +## 反向代理(Nginx 子路径) + +若通过 `https://jsuse.com/openocr/` 这类**子路径**访问(而非直接 `:8000`),必须在 `.env` 中设置: + +```env +OPENOCR_ROOT_PATH=/openocr +``` + +否则 `/health` 可能正常,但 `/docs` 会报 **Failed to load API definition**——因为 Swagger 会去根路径请求 `/openapi.json`,而不是 `/openocr/openapi.json`。 + +Nginx 参考配置: + +```nginx +location /openocr/ { + proxy_pass http://127.0.0.1:8000/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} +``` + +修改后重启容器: + +```bash +docker compose up -d --build +``` + +--- + ## 注意事项 - `.env`、`data/`、模型缓存目录已在 `.gitignore` 中排除,不会进入 Git diff --git a/src/openocr_markdown/api.py b/src/openocr_markdown/api.py index 54bc688..fe4583b 100644 --- a/src/openocr_markdown/api.py +++ b/src/openocr_markdown/api.py @@ -36,7 +36,7 @@ def create_app() -> FastAPI: settings.output_dir.mkdir(parents=True, exist_ok=True) settings.upload_dir.mkdir(parents=True, exist_ok=True) - app = FastAPI(title=settings.app_name) + app = FastAPI(title=settings.app_name, root_path=settings.root_path or "") app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origin_list, @@ -51,7 +51,7 @@ def create_app() -> FastAPI: @app.get("/", include_in_schema=False) async def root() -> RedirectResponse: - return RedirectResponse(url="/docs") + return RedirectResponse(url=settings.url_path("/docs")) @app.get("/health") async def health() -> dict[str, str]: @@ -71,9 +71,15 @@ def create_app() -> FastAPI: items.append( HistoryItem( id=item_dir.name, - markdown_url=f"/output/{item_dir.name}/{markdown_file.name}", - json_url=f"/output/{item_dir.name}/{json_file.name}" if json_file else None, - source_url=f"/output/{item_dir.name}/{source_file.name}" if source_file else None, + markdown_url=settings.url_path( + f"/output/{item_dir.name}/{markdown_file.name}" + ), + json_url=settings.url_path(f"/output/{item_dir.name}/{json_file.name}") + if json_file + else None, + source_url=settings.url_path(f"/output/{item_dir.name}/{source_file.name}") + if source_file + else None, updated_at=markdown_file.stat().st_mtime, ) ) @@ -104,8 +110,8 @@ def create_app() -> FastAPI: id=doc_id, status="success", message="Existing result reused. Set force=true to re-run.", - markdown_url=f"/output/{doc_id}/{markdown_file.name}", - json_url=f"/output/{doc_id}/{doc_id}.json", + markdown_url=settings.url_path(f"/output/{doc_id}/{markdown_file.name}"), + json_url=settings.url_path(f"/output/{doc_id}/{doc_id}.json"), ) output_dir.mkdir(parents=True, exist_ok=True) @@ -130,9 +136,9 @@ def create_app() -> FastAPI: id=doc_id, status="success", message=f"Parsed successfully in {elapsed}s.", - markdown_url=f"/output/{doc_id}/{doc_id}.md", - json_url=f"/output/{doc_id}/{doc_id}.json", - source_url=f"/output/{doc_id}/{source_path.name}", + markdown_url=settings.url_path(f"/output/{doc_id}/{doc_id}.md"), + json_url=settings.url_path(f"/output/{doc_id}/{doc_id}.json"), + source_url=settings.url_path(f"/output/{doc_id}/{source_path.name}"), ) app.mount("/output", StaticFiles(directory=settings.output_dir), name="output") @@ -149,6 +155,7 @@ def main() -> None: port=8000, reload=False, log_level=settings.log_level, + root_path=settings.root_path or "", ) diff --git a/src/openocr_markdown/config.py b/src/openocr_markdown/config.py index 2fd66e7..a23a57c 100644 --- a/src/openocr_markdown/config.py +++ b/src/openocr_markdown/config.py @@ -11,6 +11,7 @@ class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore") app_name: str = "OpenOCR Markdown Service" + root_path: str = Field(default="", alias="OPENOCR_ROOT_PATH") output_dir: Path = Field(default=Path("data/output"), alias="OPENOCR_OUTPUT_DIR") upload_dir: Path = Field(default=Path("data/uploads"), alias="OPENOCR_UPLOAD_DIR") use_gpu: bool = Field(default=False, alias="OPENOCR_USE_GPU") @@ -29,5 +30,12 @@ class Settings(BaseSettings): return ["*"] return [origin.strip() for origin in self.cors_origins.split(",") if origin.strip()] + def url_path(self, path: str) -> str: + """Build a public URL path, honoring reverse-proxy root prefix.""" + prefix = self.root_path.rstrip("/") + if not prefix: + return path + return f"{prefix}{path}" + settings = Settings()