feat (infra): Trae 完成 asset_helper_backend 微服务基础架构 V1 初始化

核心实现:搭建 Monorepo 架构,完成 shared 共享包、gateway、user-service 基础框架开发
技术落地:严格匹配指定技术栈版本,完成 Docker、gRPC、FastAPI、PostgreSQL、Redis 等配置,实现服务间基础连通
配套文件:生成 Makefile、环境变量模板、数据库 / 脚本初始化文件及启动验证文档
架构定位:仅搭建基础架构骨架,无任何业务逻辑、业务规则及业务相关字段,为后续业务开发提供支撑
This commit is contained in:
fish
2026-03-27 20:38:10 +08:00
parent 1ad8ec9be5
commit 3f4165fe78
44 changed files with 1407 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
FROM python:3.13.7-alpine3.22
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ .
COPY ../../shared/ /shared/
# 安装共享包
RUN pip install -e /shared
EXPOSE 50051
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD python -c "import socket; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.connect(('localhost', 50051)); s.close(); print('Healthy')" || exit 1
CMD ["python", "main.py"]

View File

@@ -0,0 +1,112 @@
import grpc
from concurrent import futures
from app.grpc_generated import user_pb2, user_pb2_grpc
from app.db.models import User
from app.db.session import AsyncSessionLocal
from sqlalchemy import select
from datetime import datetime
class UserService(user_pb2_grpc.UserServiceServicer):
async def GetUser(self, request, context):
async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.id == request.id))
user = result.scalar_one_or_none()
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details("User not found")
return user_pb2.UserResponse()
return user_pb2.UserResponse(
user=user_pb2.User(
id=user.id,
username=user.username,
password_hash=user.password_hash,
created_at=user.created_at.isoformat(),
updated_at=user.updated_at.isoformat()
)
)
async def CreateUser(self, request, context):
async with AsyncSessionLocal() as session:
user = User(
username=request.username,
password_hash=request.password_hash
)
session.add(user)
await session.commit()
await session.refresh(user)
return user_pb2.UserResponse(
user=user_pb2.User(
id=user.id,
username=user.username,
password_hash=user.password_hash,
created_at=user.created_at.isoformat(),
updated_at=user.updated_at.isoformat()
)
)
async def UpdateUser(self, request, context):
async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.id == request.id))
user = result.scalar_one_or_none()
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details("User not found")
return user_pb2.UserResponse()
user.username = request.username
user.password_hash = request.password_hash
await session.commit()
await session.refresh(user)
return user_pb2.UserResponse(
user=user_pb2.User(
id=user.id,
username=user.username,
password_hash=user.password_hash,
created_at=user.created_at.isoformat(),
updated_at=user.updated_at.isoformat()
)
)
async def DeleteUser(self, request, context):
async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.id == request.id))
user = result.scalar_one_or_none()
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details("User not found")
return user_pb2.EmptyResponse()
await session.delete(user)
await session.commit()
return user_pb2.EmptyResponse()
async def ListUsers(self, request, context):
async with AsyncSessionLocal() as session:
offset = (request.page - 1) * request.page_size
result = await session.execute(
select(User).offset(offset).limit(request.page_size)
)
users = result.scalars().all()
total_result = await session.execute(select(User))
total = len(total_result.scalars().all())
user_list = [
user_pb2.User(
id=user.id,
username=user.username,
password_hash=user.password_hash,
created_at=user.created_at.isoformat(),
updated_at=user.updated_at.isoformat()
)
for user in users
]
return user_pb2.UsersResponse(
users=user_list,
total=total
)

View File

@@ -0,0 +1,7 @@
from shared.utils.config import Settings
class UserServiceSettings(Settings):
# 继承基础配置,可添加服务特定配置
service_name: str = "user-service"
settings = UserServiceSettings()

View File

@@ -0,0 +1,8 @@
from shared.models import BaseDBModel
from sqlalchemy import Column, String
class User(BaseDBModel):
__tablename__ = "users"
username = Column(String, unique=True, index=True, nullable=False)
password_hash = Column(String, nullable=False)

View File

@@ -0,0 +1,16 @@
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from services.user_service.app.core.config import settings
# 注意:这里使用 user_db 数据库
DATABASE_URL = f"postgresql+asyncpg://user_service:password@postgres:5432/user_db"
engine = create_async_engine(DATABASE_URL)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_db():
async with AsyncSessionLocal() as session:
try:
yield session
finally:
await session.close()

View File

@@ -0,0 +1,29 @@
import grpc
from concurrent import futures
from app.api.user_service import UserService
from app.grpc_generated import user_pb2_grpc
from app.core.config import settings
from app.db.models import BaseDBModel
from app.db.session import engine
import asyncio
from loguru import logger
async def init_db():
# 创建数据库表
async with engine.begin() as conn:
await conn.run_sync(BaseDBModel.metadata.create_all)
async def serve():
server = grpc.aio.server(futures.ThreadPoolExecutor(max_workers=10))
user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
server.add_insecure_port(f"0.0.0.0:{settings.grpc_port}")
# 初始化数据库
await init_db()
logger.info(f"Starting gRPC server on port {settings.grpc_port}")
await server.start()
await server.wait_for_termination()
if __name__ == "__main__":
asyncio.run(serve())

View File

@@ -0,0 +1,13 @@
fastapi==0.104.1
uvicorn==0.24.0
grpcio==1.59.0
grpcio-tools==1.59.0
pydantic==2.5.0
pydantic-settings==2.1.0
sqlalchemy==2.0.23
asyncpg==0.28.0
redis==5.0.1
python-dotenv==1.0.0
loguru==0.7.2
passlib==1.7.4
bcrypt==4.1.2