Просмотр исходного кода

中台页面分页筛选查询

mengqiankang 2 месяцев назад
Родитель
Сommit
e2cce611ad

+ 0 - 41
Dockerfile

@@ -1,41 +0,0 @@
-# 1. 基础镜像:Python 3.12 Slim 版本 (体积小,构建快)
-FROM python:3.12-slim
-
-# 2. 设置工作目录为 /app
-WORKDIR /app
-
-# 3. 设置环境变量
-# POETRY_VIRTUALENVS_CREATE=false: 直接安装到系统环境,不创建虚拟环境 (容器内不需要隔离)
-# PYTHONUNBUFFERED=1: 日志实时输出,方便 Docker logs 查看
-# PYTHONDONTWRITEBYTECODE=1: 不生成 .pyc 文件,减小体积
-ENV POETRY_VIRTUALENVS_CREATE=false \
-    PYTHONUNBUFFERED=1 \
-    PYTHONDONTWRITEBYTECODE=1
-
-# 4. 安装 Poetry
-RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
-RUN pip install --no-cache-dir poetry
-
-# 5. 复制依赖文件 (利用 Docker 缓存层)
-# 先复制 pyproject.toml 和 poetry.lock,如果依赖没变,这层构建会直接使用缓存
-COPY pyproject.toml poetry.lock ./
-
-# 6. 安装依赖
-# --no-root: 不安装当前项目本身 (因为我们是复制源码运行)
-# --no-interaction: 非交互模式,避免脚本卡住
-RUN poetry install --no-root --no-interaction --no-ansi
-
-# 7. 复制所有源代码
-# 将当前目录下的所有文件(alien_store, alien_gateway, common 等)都复制进容器
-COPY . .
-
-# 8. 暴露端口
-# 对应流水线中 -p 33333:33333 的设置
-EXPOSE 33333
-
-# 9. 启动命令
-# 同时启动 uvicorn 和 celery
-# 使用 shell 格式执行多个命令:后台启动 celery,前台启动 uvicorn
-CMD celery -A alien_util.celery_app worker --beat --loglevel=info & \
-    sleep 2 && \
-    exec uvicorn alien_store.main:app --host 0.0.0.0 --port 33333

+ 0 - 64
alien_store/README.md

@@ -1,64 +0,0 @@
-# Alien 3rd Esign 模块开发文档
-
-## 概览
-基于 FastAPI 的电子签约子模块,负责店铺合同签约相关的接口、服务与数据访问。当前实现为骨架,需在此基础上补充业务逻辑。
-
-## 目录结构
-```
-alien_3rd_esign/
-├── main.py                    # 应用入口,挂载路由与健康检查
-├── api/
-│   ├── router.py              # 路由分组,当前仅根路径
-│   └── deps.py                # 依赖注入,提供 ContractServer
-├── db/
-│   └── models/
-│       └── contract_store.py  # 店铺合同表模型
-├── repositories/
-│   └── contract_repo.py       # 合同仓储(待实现)
-├── services/
-│   └── contract_server.py     # 合同服务层(待实现)
-├── schemas/
-│   ├── request/               # 请求 Pydantic 模型(待补充)
-│   └── response/              # 响应 Pydantic 模型(待补充)
-└── __init__.py
-```
-
-## 运行与环境
-- 依赖外部配置 `alien_gateway.config.settings`,至少包含 `PROJECT_NAME`,以及数据库配置(见上游网关项目)。
-- 数据库会话来自 `alien_database.session.get_db`,确保上游已配置 SQLAlchemy Engine/Session。
-- 安装依赖后本地运行示例:
-  ```
-  uvicorn alien_3rd_esign.main:app --host 0.0.0.0 --port 8006 --reload
-  ```
-
-## 现有 API
-- `GET /api/esign/`:模块存活确认,返回 `{"module": "Contract", "status": "Ok"}`。
-- `GET /health`:健康检查,返回 `{"service": "sign", "status": "ok"}`。
-
-## 数据模型
-- `ContractStore`(表名 `contract_store`)
-  - `store_id` (BigInteger, PK) 店铺 ID
-  - `business_segment` (String) 经营板块
-  - `merchant_name` (String) 商家姓名
-  - `contact_phone` (String) 联系电话
-  - `signing_status` (String, 默认“未签署”) 状态:已签署/未签署/已到期
-  - `signing_time` (DateTime, 可空) 签署时间
-  - `effective_time` (DateTime, 可空) 生效时间
-  - `expiry_time` (DateTime, 可空) 到期时间
-  - 继承 `AuditMixin`,包含审计字段(创建/更新人、时间等,具体见 `alien_database.base`)。
-
-## 开发指引
-- 路由层:在 `api/router.py` 按资源划分路由,通过 `Depends(get_contract_service)` 注入服务。
-- 服务层:在 `services/contract_server.py` 编写业务逻辑(状态流转、有效期计算、外部电子签平台集成、权限校验)。
-- 仓储层:在 `repositories/contract_repo.py` 实现对 `ContractStore` 的 CRUD、列表分页、状态更新等。
-- Schema:在 `schemas/request/` 与 `schemas/response/` 定义 Pydantic 模型,约束字段、枚举和值域。
-- 异常与校验:使用 FastAPI/HTTPException 统一错误响应;对输入做类型与业务校验。
-- 测试:为服务和仓储编写单元/集成测试,可使用 SQLite 内存库或测试数据库。
-
-## 后续待办(建议)
-- 补充合同创建、查询、签署、续签、到期提醒等接口。
-- 定义请求/响应 schema 与枚举,完善文档化(可结合 FastAPI OpenAPI/Swagger)。
-- 增加仓储实现与事务管理,补齐状态机校验。
-- 编写自动化测试与示例请求(如 HTTPie/Thunder Client 集合)。
-
-

+ 20 - 6
alien_store/api/router.py

@@ -1,10 +1,8 @@
 import datetime
 from fastapi import APIRouter, Depends, Query
-from typing import Any, List, Union, Optional
-import json
+from typing import Any,  Union, Optional
 import os
 import logging
-from datetime import datetime
 from alien_store.api.deps import get_contract_service
 from alien_store.schemas.request.contract_store import TemplatesCreate, SignUrl
 from alien_store.schemas.response.contract_store import (
@@ -227,10 +225,27 @@ async def get_contract_detail(
 async def get_all_templates(
     page: int = Query(1, ge=1, description="页码,从1开始"),
     page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"),
+    store_name: Optional[str] = Query(None, description="店铺名称(模糊查询)"),
+    merchant_name: Optional[str] = Query(None, description="商家姓名(模糊查询)"),
+    signing_status: Optional[str] = Query(None, description="签署状态"),
+    business_segment: Optional[str] = Query(None, description="经营板块"),
+    store_status: Optional[str] = Query(None, description="店铺状态:正常/禁用"),
+    expiry_start: Optional[datetime] = Query(None, description="到期时间起"),
+    expiry_end: Optional[datetime] = Query(None, description="到期时间止"),
     templates_server: ContractServer = Depends(get_contract_service)
 ) -> PaginatedResponse:
-    """分页查询所有合同"""
-    rows, total = await templates_server.list_all_paged(page, page_size)
+    """分页查询所有合同,支持筛选"""
+    rows, total = await templates_server.list_all_paged(
+        page,
+        page_size,
+        store_name=store_name,
+        merchant_name=merchant_name,
+        signing_status=signing_status,
+        business_segment=business_segment,
+        store_status=store_status,
+        expiry_start=expiry_start,
+        expiry_end=expiry_end,
+    )
     total_pages = (total + page_size - 1) // page_size if total > 0 else 0
     
     items = [ContractStoreResponse(**row) for row in rows]
@@ -304,7 +319,6 @@ async def esign_callback(
             contract_download_url = download_json["data"]["files"][0]["downloadUrl"]
         except Exception as e:
             logger.error(f"file_download_url failed for sign_flow_id={sign_flow_id}: {e}")
-        print(contract_download_url,666666666666666666666666666666)
         updated = await templates_server.mark_signed_by_phone(contact_phone, sign_flow_id, signing_dt, contract_download_url)
         logger.info(f"esign_callback success phone={contact_phone}, sign_flow_id={sign_flow_id}, updated={updated}")
         return SuccessResponse(code="200", msg="success")

+ 49 - 9
alien_store/repositories/contract_repo.py

@@ -1,7 +1,7 @@
 import logging
 import json
 from datetime import datetime, timedelta
-from sqlalchemy import func, select, text
+from sqlalchemy import func, select, text, or_
 from sqlalchemy.ext.asyncio import AsyncSession
 from sqlalchemy.exc import DBAPIError
 from alien_store.db.models.contract_store import ContractStore
@@ -98,18 +98,58 @@ class ContractRepository:
         result = await self._execute_with_retry(ContractStore.__table__.select())
         return [dict(row) for row in result.mappings().all()]
 
-    async def get_all_paged(self, page: int, page_size: int = 10):
-        """分页查询所有合同,返回 (items, total)"""
+    async def get_all_paged(
+        self,
+        page: int,
+        page_size: int = 10,
+        store_name: str | None = None,
+        merchant_name: str | None = None,
+        signing_status: str | None = None,
+        business_segment: str | None = None,
+        store_status: str | None = None,
+        expiry_start: datetime | None = None,
+        expiry_end: datetime | None = None,
+    ):
+        """分页查询所有合同,支持筛选,返回 (items, total)"""
         offset = (page - 1) * page_size
+        table = ContractStore.__table__
+        conditions = []
+
+        if store_name:
+            conditions.append(table.c.store_name.like(f"%{store_name}%"))
+        if merchant_name:
+            conditions.append(table.c.merchant_name.like(f"%{merchant_name}%"))
+        if signing_status:
+            conditions.append(table.c.signing_status == signing_status)
+        if business_segment:
+            conditions.append(table.c.business_segment == business_segment)
+
+        if store_status:
+            if store_status == "正常":
+                conditions.append(table.c.signing_status == "已签署")
+            elif store_status == "禁用":
+                conditions.append(
+                    or_(table.c.signing_status != "已签署", table.c.signing_status.is_(None))
+                )
+
+        if expiry_start:
+            conditions.append(table.c.expiry_time >= expiry_start)
+        if expiry_end:
+            conditions.append(table.c.expiry_time <= expiry_end)
+
         # 查询总数
-        count_result = await self._execute_with_retry(
-            select(func.count()).select_from(ContractStore.__table__)
-        )
+        count_stmt = select(func.count()).select_from(table)
+        if conditions:
+            count_stmt = count_stmt.where(*conditions)
+        count_result = await self._execute_with_retry(count_stmt)
         total = count_result.scalar() or 0
+
         # 查询分页数据
-        result = await self._execute_with_retry(
-            ContractStore.__table__.select().offset(offset).limit(page_size)
-        )
+        data_stmt = table.select()
+        if conditions:
+            data_stmt = data_stmt.where(*conditions)
+        data_stmt = data_stmt.offset(offset).limit(page_size)
+        result = await self._execute_with_retry(data_stmt)
         items = [dict(row) for row in result.mappings().all()]
         return items, total
 

+ 23 - 2
alien_store/services/contract_server.py

@@ -26,8 +26,29 @@ class ContractServer:
     async def update_contract_items(self, row_id: int, items: list) -> bool:
         return await self.esign_repo.update_contract_items(row_id, items)
 
-    async def list_all_paged(self, page: int, page_size: int = 10):
-        items, total = await self.esign_repo.get_all_paged(page, page_size)
+    async def list_all_paged(
+        self,
+        page: int,
+        page_size: int = 10,
+        store_name: str | None = None,
+        merchant_name: str | None = None,
+        signing_status: str | None = None,
+        business_segment: str | None = None,
+        store_status: str | None = None,
+        expiry_start=None,
+        expiry_end=None,
+    ):
+        items, total = await self.esign_repo.get_all_paged(
+            page,
+            page_size,
+            store_name=store_name,
+            merchant_name=merchant_name,
+            signing_status=signing_status,
+            business_segment=business_segment,
+            store_status=store_status,
+            expiry_start=expiry_start,
+            expiry_end=expiry_end,
+        )
         return items, total
 
     async def mark_signed_by_phone(self, contact_phone: str, sign_flow_id: str, signing_time, contract_download_url):

+ 2 - 1
common/esigntool/main.py

@@ -283,6 +283,7 @@ def create_by_file(file_id, file_name,  contact_phone, store_name, merchant_name
     return resp.text
 
 def sign_url(sign_flow_id, contact_phone):
+    """获取签署页面链接"""
     api_path = f"/v3/sign-flow/{sign_flow_id}/sign-url"
     method = "POST"
     body = {
@@ -318,4 +319,4 @@ def file_download_url(sign_flow_id):
 # sign_json  = json.loads(sing_data)
 # sing_id = sign_json["data"]["signFlowId"]
 # sign_url("", "13503301290")
-file_download_url("15156afb603e4145b112ad6eab0815d5")
+# file_download_url("15156afb603e4145b112ad6eab0815d5")