首次提交by MimoCode

This commit is contained in:
tang1219
2026-06-15 14:50:15 +08:00
parent 584b31da50
commit ead13f863c
166 changed files with 14908 additions and 1 deletions
View File
+115
View File
@@ -0,0 +1,115 @@
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.core.deps import get_current_user, require_role
from app.models.user import User
from app.config import settings
router = APIRouter()
@router.post("/recognize-medicine")
async def recognize_medicine(
file: UploadFile = File(...),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
from app.ai.manager import ai_manager
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=400, detail="请上传图片文件")
image_bytes = await file.read()
if len(image_bytes) > settings.MAX_UPLOAD_SIZE:
raise HTTPException(status_code=413, detail="文件过大")
vision_provider = ai_manager.get_vision_provider(settings.AI_PROVIDER)
if not vision_provider:
raise HTTPException(status_code=500, detail="AI 服务未配置")
try:
result = await vision_provider.recognize_medicine(image_bytes)
return result.model_dump()
except Exception as e:
raise HTTPException(status_code=500, detail=f"识别失败: {str(e)}")
@router.post("/recognize-dates")
async def recognize_dates(
file: UploadFile = File(...),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
from app.ai.manager import ai_manager
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=400, detail="请上传图片文件")
image_bytes = await file.read()
if len(image_bytes) > settings.MAX_UPLOAD_SIZE:
raise HTTPException(status_code=413, detail="文件过大")
vision_provider = ai_manager.get_vision_provider(settings.AI_PROVIDER)
if not vision_provider:
raise HTTPException(status_code=500, detail="AI 服务未配置")
try:
result = await vision_provider.recognize_dates(image_bytes)
return result.model_dump()
except Exception as e:
raise HTTPException(status_code=500, detail=f"识别失败: {str(e)}")
@router.post("/recognize-leaflet")
async def recognize_leaflet(
file: UploadFile = File(...),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
from app.ai.manager import ai_manager
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=400, detail="请上传图片文件")
image_bytes = await file.read()
if len(image_bytes) > settings.MAX_UPLOAD_SIZE:
raise HTTPException(status_code=413, detail="文件过大")
text_provider = ai_manager.get_text_provider(settings.AI_PROVIDER)
if not text_provider:
raise HTTPException(status_code=500, detail="AI 服务未配置")
try:
import base64
base64_image = base64.b64encode(image_bytes).decode('utf-8')
from openai import AsyncOpenAI
client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY)
response = await client.chat.completions.create(
model=settings.OPENAI_MODEL,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "请识别这张说明书图片中的文字内容,返回纯文本。"
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
]
}
]
)
ocr_text = response.choices[0].message.content
result = await text_provider.summarize_leaflet(ocr_text)
return result.model_dump()
except Exception as e:
raise HTTPException(status_code=500, detail=f"识别失败: {str(e)}")
+42
View File
@@ -0,0 +1,42 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.core.deps import get_current_user
from app.services.auth import AuthService
from app.schemas.user import UserLogin, Token, UserResponse
from app.schemas.auth import PasswordChangeRequest
from app.services.user import UserService
router = APIRouter()
@router.post("/login", response_model=Token)
async def login(data: UserLogin, db: AsyncSession = Depends(get_db)):
service = AuthService(db)
result = await service.login(data.username, data.password)
if not result:
raise HTTPException(status_code=401, detail="用户名或密码错误")
return Token(
access_token=result["access_token"],
token_type=result["token_type"],
user=UserResponse.model_validate(result["user"])
)
@router.get("/me", response_model=UserResponse)
async def get_current_user_info(current_user=Depends(get_current_user)):
return UserResponse.model_validate(current_user)
@router.put("/password")
async def change_password(
data: PasswordChangeRequest,
current_user=Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
service = UserService(db)
success = await service.change_password(current_user.id, data.old_password, data.new_password)
if not success:
raise HTTPException(status_code=400, detail="原密码错误")
return {"message": "密码修改成功"}
+104
View File
@@ -0,0 +1,104 @@
from typing import List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.core.deps import get_current_user, require_role
from app.models.user import User
from app.schemas.batch import BatchCreate, BatchUpdate, BatchResponse, BatchDispense, BatchAddStock
from app.services.batch import BatchService
router = APIRouter()
@router.get("/medicine/{medicine_id}", response_model=List[BatchResponse])
async def list_batches_by_medicine(
medicine_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = BatchService(db)
batches = await service.get_batches_by_medicine(medicine_id)
return [BatchResponse.model_validate(b) for b in batches]
@router.get("/{batch_id}", response_model=BatchResponse)
async def get_batch(
batch_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = BatchService(db)
batch = await service.get_batch(batch_id)
if not batch:
raise HTTPException(status_code=404, detail="批次不存在")
return BatchResponse.model_validate(batch)
@router.post("/medicine/{medicine_id}", response_model=BatchResponse)
async def create_batch(
medicine_id: int,
data: BatchCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
service = BatchService(db)
batch = await service.create_batch(medicine_id, data)
return BatchResponse.model_validate(batch)
@router.put("/{batch_id}", response_model=BatchResponse)
async def update_batch(
batch_id: int,
data: BatchUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
service = BatchService(db)
batch = await service.update_batch(batch_id, data)
if not batch:
raise HTTPException(status_code=404, detail="批次不存在")
return BatchResponse.model_validate(batch)
@router.delete("/{batch_id}")
async def delete_batch(
batch_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = BatchService(db)
success = await service.delete_batch(batch_id)
if not success:
raise HTTPException(status_code=404, detail="批次不存在")
return {"message": "删除成功"}
@router.post("/{batch_id}/dispense", response_model=BatchResponse)
async def dispense_batch(
batch_id: int,
data: BatchDispense,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
service = BatchService(db)
try:
batch = await service.dispense(batch_id, data.quantity, current_user.id)
return BatchResponse.model_validate(batch)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/{batch_id}/add-stock", response_model=BatchResponse)
async def add_stock_batch(
batch_id: int,
data: BatchAddStock,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
service = BatchService(db)
try:
batch = await service.add_stock(batch_id, data.quantity, current_user.id)
return BatchResponse.model_validate(batch)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
+83
View File
@@ -0,0 +1,83 @@
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.core.deps import get_current_user, require_role
from app.models.user import User
from app.schemas.category import CategoryCreate, CategoryUpdate, CategoryResponse
from app.services.category import CategoryService
router = APIRouter()
@router.get("/")
async def list_categories(
level: Optional[int] = Query(None),
parent_id: Optional[int] = Query(None),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = CategoryService(db)
categories = await service.get_categories(level=level, parent_id=parent_id)
return [CategoryResponse.model_validate(c) for c in categories]
@router.get("/tree")
async def get_category_tree(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = CategoryService(db)
return await service.get_category_tree()
@router.get("/{category_id}", response_model=CategoryResponse)
async def get_category(
category_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = CategoryService(db)
category = await service.get_category(category_id)
if not category:
raise HTTPException(status_code=404, detail="分类不存在")
return CategoryResponse.model_validate(category)
@router.post("/", response_model=CategoryResponse)
async def create_category(
data: CategoryCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = CategoryService(db)
category = await service.create_category(data.model_dump())
return CategoryResponse.model_validate(category)
@router.put("/{category_id}", response_model=CategoryResponse)
async def update_category(
category_id: int,
data: CategoryUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = CategoryService(db)
category = await service.update_category(category_id, data.model_dump(exclude_unset=True))
if not category:
raise HTTPException(status_code=404, detail="分类不存在")
return CategoryResponse.model_validate(category)
@router.delete("/{category_id}")
async def delete_category(
category_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = CategoryService(db)
success = await service.delete_category(category_id)
if not success:
raise HTTPException(status_code=404, detail="分类不存在")
return {"message": "删除成功"}
+86
View File
@@ -0,0 +1,86 @@
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.core.deps import get_current_user, require_role
from app.models.user import User
from app.schemas.medicine import MedicineCreate, MedicineUpdate, MedicineResponse, MedicineWithStock
from app.services.medicine import MedicineService
router = APIRouter()
@router.get("/")
async def list_medicines(
category_id: Optional[int] = Query(None),
search: Optional[str] = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = MedicineService(db)
medicines, total = await service.get_medicines(
category_id=category_id,
search=search,
page=page,
page_size=page_size
)
return {
"data": [MedicineWithStock.model_validate(m) for m in medicines],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{medicine_id}", response_model=MedicineResponse)
async def get_medicine(
medicine_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = MedicineService(db)
medicine = await service.get_medicine(medicine_id)
if not medicine:
raise HTTPException(status_code=404, detail="药品不存在")
return MedicineResponse.model_validate(medicine)
@router.post("/", response_model=MedicineResponse)
async def create_medicine(
data: MedicineCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
service = MedicineService(db)
medicine = await service.create_medicine(data, current_user.id)
return MedicineResponse.model_validate(medicine)
@router.put("/{medicine_id}", response_model=MedicineResponse)
async def update_medicine(
medicine_id: int,
data: MedicineUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin", "user"]))
):
service = MedicineService(db)
medicine = await service.update_medicine(medicine_id, data)
if not medicine:
raise HTTPException(status_code=404, detail="药品不存在")
return MedicineResponse.model_validate(medicine)
@router.delete("/{medicine_id}")
async def delete_medicine(
medicine_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = MedicineService(db)
success = await service.delete_medicine(medicine_id)
if not success:
raise HTTPException(status_code=404, detail="药品不存在")
return {"message": "删除成功"}
+77
View File
@@ -0,0 +1,77 @@
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.core.deps import get_current_user
from app.models.user import User
from app.services.notification import NotificationService
router = APIRouter()
@router.get("/")
async def list_notifications(
is_read: Optional[bool] = Query(None),
type: Optional[str] = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = NotificationService(db)
notifications = await service.get_notifications(
user_id=current_user.id,
is_read=is_read,
type=type,
page=page,
page_size=page_size
)
return [
{
"id": n.id,
"type": n.type,
"title": n.title,
"content": n.content,
"is_read": n.is_read,
"related_id": n.related_id,
"created_at": n.created_at
}
for n in notifications
]
@router.put("/{notification_id}/read")
async def mark_notification_read(
notification_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = NotificationService(db)
success = await service.mark_as_read(notification_id)
if not success:
raise HTTPException(status_code=404, detail="通知不存在")
return {"message": "success"}
@router.put("/read-all")
async def mark_all_notifications_read(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = NotificationService(db)
count = await service.mark_all_as_read(current_user.id)
return {"message": "success", "count": count}
@router.delete("/{notification_id}")
async def delete_notification(
notification_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = NotificationService(db)
success = await service.delete_notification(notification_id)
if not success:
raise HTTPException(status_code=404, detail="通知不存在")
return {"message": "删除成功"}
+66
View File
@@ -0,0 +1,66 @@
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from app.database import get_db
from app.core.deps import get_current_user
from app.models.user import User
from app.services.medicine import MedicineService
router = APIRouter()
class NaturalSearchRequest(BaseModel):
query: str
@router.get("/")
async def search_medicines(
q: str = Query(...),
type: str = Query("name"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
service = MedicineService(db)
medicines = await service.search_medicines(q)
return [
{
"id": m.id,
"name": m.name,
"generic_name": m.generic_name,
"indications": m.indications,
"total_quantity": sum(b.quantity for b in m.batches if not b.is_expired)
}
for m in medicines
]
@router.post("/natural")
async def natural_language_search(
data: NaturalSearchRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
from app.ai.manager import ai_manager
from app.config import settings
service = MedicineService(db)
medicines = await service.search_medicines(data.query)
if not medicines:
return {"results": [], "ai_response": "未找到相关药品"}
text_provider = ai_manager.get_text_provider(settings.AI_PROVIDER)
if not text_provider:
return {"results": [], "ai_response": "AI 服务未配置"}
medicines_data = [
{"name": m.name, "indications": m.indications or ""}
for m in medicines
]
try:
results = await text_provider.natural_language_search(data.query, medicines_data)
return {"results": results, "ai_response": "搜索完成"}
except Exception as e:
return {"results": [], "ai_response": f"搜索失败: {str(e)}"}
+82
View File
@@ -0,0 +1,82 @@
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from pydantic import BaseModel
from app.database import get_db
from app.core.deps import require_role
from app.models.user import User
from app.models.setting import Setting
router = APIRouter()
class SettingResponse(BaseModel):
key: str
value: Optional[str] = None
description: Optional[str] = None
class SettingUpdate(BaseModel):
value: str
class BulkSettingUpdate(BaseModel):
settings: List[SettingUpdate]
@router.get("/")
async def list_settings(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
result = await db.execute(select(Setting))
settings = list(result.scalars().all())
return [
{
"key": s.key,
"value": s.value,
"description": s.description
}
for s in settings
]
@router.get("/{key}", response_model=SettingResponse)
async def get_setting(
key: str,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
result = await db.execute(select(Setting).where(Setting.key == key))
setting = result.scalar_one_or_none()
if not setting:
raise HTTPException(status_code=404, detail="设置不存在")
return SettingResponse(
key=setting.key,
value=setting.value,
description=setting.description
)
@router.put("/{key}", response_model=SettingResponse)
async def update_setting(
key: str,
data: SettingUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
result = await db.execute(select(Setting).where(Setting.key == key))
setting = result.scalar_one_or_none()
if not setting:
setting = Setting(key=key, value=data.value)
db.add(setting)
else:
setting.value = data.value
await db.commit()
return SettingResponse(
key=setting.key,
value=setting.value,
description=setting.description
)
+94
View File
@@ -0,0 +1,94 @@
from typing import List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from app.database import get_db
from app.core.deps import require_role
from app.models.user import User
from app.schemas.user import UserCreate, UserUpdate, UserResponse
from app.services.user import UserService
router = APIRouter()
class ResetPasswordRequest(BaseModel):
new_password: str
@router.get("/", response_model=List[UserResponse])
async def list_users(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = UserService(db)
users = await service.get_all_users()
return [UserResponse.model_validate(u) for u in users]
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
user_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = UserService(db)
user = await service.get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return UserResponse.model_validate(user)
@router.post("/", response_model=UserResponse)
async def create_user(
data: UserCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = UserService(db)
existing_user = await service.get_user_by_username(data.username)
if existing_user:
raise HTTPException(status_code=409, detail="用户名已存在")
user = await service.create_user(data.model_dump())
return UserResponse.model_validate(user)
@router.put("/{user_id}", response_model=UserResponse)
async def update_user(
user_id: int,
data: UserUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = UserService(db)
user = await service.update_user(user_id, data.model_dump(exclude_unset=True))
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return UserResponse.model_validate(user)
@router.delete("/{user_id}")
async def delete_user(
user_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = UserService(db)
success = await service.delete_user(user_id)
if not success:
raise HTTPException(status_code=404, detail="用户不存在")
return {"message": "删除成功"}
@router.post("/{user_id}/reset-password")
async def reset_password(
user_id: int,
data: ResetPasswordRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_role(["admin"]))
):
service = UserService(db)
success = await service.reset_password(user_id, data.new_password)
if not success:
raise HTTPException(status_code=404, detail="用户不存在")
return {"message": "success"}