Init project
This commit is contained in:
53
doc-service/Dockerfile
Normal file
53
doc-service/Dockerfile
Normal file
@@ -0,0 +1,53 @@
|
||||
# Многоэтапная сборка для Python приложения
|
||||
FROM python:3.11-slim AS builder
|
||||
|
||||
# Установка зависимостей для сборки
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Установка рабочей директории
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование requirements файла
|
||||
COPY requirements.txt .
|
||||
|
||||
# Установка зависимостей Python
|
||||
RUN pip install --no-cache-dir --user -r requirements.txt
|
||||
|
||||
# Финальный образ
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Установка системных зависимостей
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Создание пользователя для безопасности
|
||||
RUN groupadd -r appuser && useradd -r -g appuser appuser
|
||||
|
||||
# Установка рабочей директории
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование Python пакетов из builder
|
||||
COPY --from=builder /root/.local /root/.local
|
||||
|
||||
# Копирование исходного кода
|
||||
COPY . .
|
||||
|
||||
# Создание необходимых директорий
|
||||
RUN mkdir -p /app/templates /app/output && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
# Добавление локальных пакетов в PATH
|
||||
ENV PATH=/root/.local/bin:$PATH
|
||||
|
||||
# Переключение на непривилегированного пользователя
|
||||
USER appuser
|
||||
|
||||
# Экспорт порта
|
||||
EXPOSE 8000
|
||||
|
||||
# Команда запуска
|
||||
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
56
doc-service/app/config.py
Normal file
56
doc-service/app/config.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import os
|
||||
from typing import List
|
||||
from pydantic_settings import BaseSettings
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Загрузка .env файла
|
||||
load_dotenv()
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Настройки приложения"""
|
||||
|
||||
# Основные настройки
|
||||
DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
|
||||
HOST: str = os.getenv("HOST", "0.0.0.0")
|
||||
PORT: int = int(os.getenv("PORT", "8000"))
|
||||
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
|
||||
|
||||
# CORS
|
||||
ALLOWED_ORIGINS: List[str] = [
|
||||
"http://localhost:3000",
|
||||
"http://localhost:8080",
|
||||
"https://localhost:3000",
|
||||
"https://localhost:8080"
|
||||
]
|
||||
|
||||
# Redis
|
||||
REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost")
|
||||
REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379"))
|
||||
REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "")
|
||||
REDIS_DB: int = int(os.getenv("REDIS_DB", "0"))
|
||||
|
||||
# Core Service
|
||||
CORE_SERVICE_URL: str = os.getenv("CORE_SERVICE_URL", "http://localhost:8080")
|
||||
|
||||
# Документы
|
||||
DOCUMENTS_CACHE_TTL: int = int(os.getenv("DOCUMENTS_CACHE_TTL", "86400")) # 24 часа
|
||||
MAX_DOCUMENT_SIZE: int = int(os.getenv("MAX_DOCUMENT_SIZE", "10485760")) # 10MB
|
||||
|
||||
# Пути для файлов
|
||||
TEMPLATES_DIR: str = os.getenv("TEMPLATES_DIR", "app/templates")
|
||||
OUTPUT_DIR: str = os.getenv("OUTPUT_DIR", "app/output")
|
||||
|
||||
# QR коды
|
||||
QR_CODE_SIZE: int = int(os.getenv("QR_CODE_SIZE", "10"))
|
||||
QR_CODE_BORDER: int = int(os.getenv("QR_CODE_BORDER", "2"))
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = False
|
||||
|
||||
# Создание экземпляра настроек
|
||||
settings = Settings()
|
||||
|
||||
# Создание директорий если не существуют
|
||||
os.makedirs(settings.TEMPLATES_DIR, exist_ok=True)
|
||||
os.makedirs(settings.OUTPUT_DIR, exist_ok=True)
|
||||
80
doc-service/app/main.py
Normal file
80
doc-service/app/main.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import uvicorn
|
||||
import structlog
|
||||
|
||||
from app.config import settings
|
||||
from app.api.routes import documents, templates
|
||||
from app.core.redis_client import redis_client
|
||||
from app.core.logging import setup_logging
|
||||
|
||||
# Настройка логирования
|
||||
setup_logging()
|
||||
logger = structlog.get_logger()
|
||||
|
||||
# Создание FastAPI приложения
|
||||
app = FastAPI(
|
||||
title="ERP Document Service",
|
||||
description="Сервис для генерации документов (PDF, Excel, Word)",
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc"
|
||||
)
|
||||
|
||||
# Настройка CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Подключение роутов
|
||||
app.include_router(documents.router, prefix="/api/documents", tags=["documents"])
|
||||
app.include_router(templates.router, prefix="/api/templates", tags=["templates"])
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Событие запуска приложения"""
|
||||
logger.info("Starting Document Service")
|
||||
|
||||
# Подключение к Redis
|
||||
await redis_client.connect()
|
||||
logger.info("Connected to Redis")
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""Событие остановки приложения"""
|
||||
logger.info("Shutting down Document Service")
|
||||
|
||||
# Отключение от Redis
|
||||
await redis_client.disconnect()
|
||||
logger.info("Disconnected from Redis")
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Проверка здоровья сервиса"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "document-service",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""Корневой эндпоинт"""
|
||||
return {
|
||||
"message": "ERP Document Service",
|
||||
"docs": "/docs",
|
||||
"health": "/health"
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host=settings.HOST,
|
||||
port=settings.PORT,
|
||||
reload=settings.DEBUG,
|
||||
log_level=settings.LOG_LEVEL.lower()
|
||||
)
|
||||
86
doc-service/app/models/document.py
Normal file
86
doc-service/app/models/document.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
class DocumentType(str, Enum):
|
||||
"""Типы документов"""
|
||||
PDF = "pdf"
|
||||
EXCEL = "excel"
|
||||
WORD = "word"
|
||||
QR_CODE = "qr_code"
|
||||
|
||||
class DocumentStatus(str, Enum):
|
||||
"""Статусы документов"""
|
||||
PENDING = "pending"
|
||||
PROCESSING = "processing"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
class QRCodeRequest(BaseModel):
|
||||
"""Запрос на генерацию QR-кода"""
|
||||
location_id: str = Field(..., description="ID места хранения")
|
||||
location_address: str = Field(..., description="Адрес места")
|
||||
organization_id: str = Field(..., description="ID организации")
|
||||
size: Optional[int] = Field(10, description="Размер QR-кода")
|
||||
border: Optional[int] = Field(2, description="Размер границы")
|
||||
|
||||
class QRCodeResponse(BaseModel):
|
||||
"""Ответ с QR-кодом"""
|
||||
document_id: str = Field(..., description="ID документа")
|
||||
qr_code_url: str = Field(..., description="URL для скачивания QR-кода")
|
||||
qr_code_data: str = Field(..., description="Данные QR-кода")
|
||||
expires_at: datetime = Field(..., description="Время истечения")
|
||||
|
||||
class ReportRequest(BaseModel):
|
||||
"""Запрос на генерацию отчета"""
|
||||
report_type: str = Field(..., description="Тип отчета")
|
||||
organization_id: str = Field(..., description="ID организации")
|
||||
filters: Optional[Dict[str, Any]] = Field({}, description="Фильтры для отчета")
|
||||
format: DocumentType = Field(DocumentType.PDF, description="Формат отчета")
|
||||
|
||||
class ReportResponse(BaseModel):
|
||||
"""Ответ с отчетом"""
|
||||
document_id: str = Field(..., description="ID документа")
|
||||
download_url: str = Field(..., description="URL для скачивания")
|
||||
file_size: int = Field(..., description="Размер файла в байтах")
|
||||
expires_at: datetime = Field(..., description="Время истечения")
|
||||
|
||||
class DocumentStatusResponse(BaseModel):
|
||||
"""Ответ со статусом документа"""
|
||||
document_id: str = Field(..., description="ID документа")
|
||||
status: DocumentStatus = Field(..., description="Статус документа")
|
||||
progress: Optional[int] = Field(None, description="Прогресс в процентах")
|
||||
error_message: Optional[str] = Field(None, description="Сообщение об ошибке")
|
||||
created_at: datetime = Field(..., description="Время создания")
|
||||
updated_at: datetime = Field(..., description="Время обновления")
|
||||
|
||||
class GeneratePDFRequest(BaseModel):
|
||||
"""Запрос на генерацию PDF"""
|
||||
template_name: str = Field(..., description="Название шаблона")
|
||||
data: Dict[str, Any] = Field(..., description="Данные для шаблона")
|
||||
filename: Optional[str] = Field(None, description="Имя файла")
|
||||
|
||||
class GenerateExcelRequest(BaseModel):
|
||||
"""Запрос на генерацию Excel"""
|
||||
data: List[Dict[str, Any]] = Field(..., description="Данные для таблицы")
|
||||
sheet_name: str = Field("Sheet1", description="Название листа")
|
||||
filename: Optional[str] = Field(None, description="Имя файла")
|
||||
|
||||
class TemplateInfo(BaseModel):
|
||||
"""Информация о шаблоне"""
|
||||
name: str = Field(..., description="Название шаблона")
|
||||
description: str = Field(..., description="Описание шаблона")
|
||||
variables: List[str] = Field(..., description="Переменные шаблона")
|
||||
created_at: datetime = Field(..., description="Время создания")
|
||||
updated_at: datetime = Field(..., description="Время обновления")
|
||||
|
||||
class DocumentInfo(BaseModel):
|
||||
"""Информация о документе"""
|
||||
id: str = Field(..., description="ID документа")
|
||||
type: DocumentType = Field(..., description="Тип документа")
|
||||
status: DocumentStatus = Field(..., description="Статус документа")
|
||||
filename: str = Field(..., description="Имя файла")
|
||||
file_size: int = Field(..., description="Размер файла")
|
||||
created_at: datetime = Field(..., description="Время создания")
|
||||
expires_at: datetime = Field(..., description="Время истечения")
|
||||
43
doc-service/requirements.txt
Normal file
43
doc-service/requirements.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
# Web framework
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
|
||||
# PDF generation
|
||||
reportlab==4.0.4
|
||||
weasyprint==60.1
|
||||
|
||||
# Office documents
|
||||
python-docx==1.1.0
|
||||
openpyxl==3.1.2
|
||||
|
||||
# Templates
|
||||
jinja2==3.1.2
|
||||
|
||||
# Redis client
|
||||
redis==5.0.1
|
||||
|
||||
# gRPC
|
||||
grpcio==1.59.0
|
||||
protobuf==4.24.4
|
||||
|
||||
# Data validation
|
||||
pydantic==2.4.2
|
||||
|
||||
# Environment variables
|
||||
python-dotenv==1.0.0
|
||||
|
||||
# Logging
|
||||
structlog==23.2.0
|
||||
|
||||
# QR code generation
|
||||
qrcode==7.4.2
|
||||
Pillow==10.0.1
|
||||
|
||||
# HTTP client
|
||||
httpx==0.25.1
|
||||
|
||||
# CORS
|
||||
fastapi-cors==0.0.6
|
||||
|
||||
# Monitoring
|
||||
prometheus-client==0.19.0
|
||||
Reference in New Issue
Block a user