104 lines
4.1 KiB
Python
104 lines
4.1 KiB
Python
"""系统设置接口(公司信息 + 站点信息)。
|
||
|
||
`GET /public`、`GET /logo/public`、`GET /favicon/public` **无鉴权**:登录页
|
||
渲染公司名/logo、浏览器加载标签页图标时尚无令牌。响应经
|
||
`SettingPublicOut` 白名单收敛(见 schemas/setting.py),仅暴露展示必需字段。
|
||
|
||
`/logo/public` 与 `/favicon/public` 返回文件流而非统一信封——与参数导出
|
||
同一例外口径(二进制响应套 JSON 信封无意义)。
|
||
"""
|
||
|
||
from fastapi import APIRouter, Response, UploadFile
|
||
from fastapi.responses import FileResponse
|
||
|
||
from app.api.deps import AdminUser, DbSession, RequestMetaDep
|
||
from app.core.response import ApiResponse, ok
|
||
from app.schemas.setting import SettingOut, SettingPublicOut, SettingUpdateIn
|
||
from app.services import system_setting_service as service
|
||
|
||
router = APIRouter(prefix="/settings", tags=["系统管理 · 公司信息"])
|
||
|
||
_LOGO_MEDIA_TYPE = {"png": "image/png", "jpg": "image/jpeg"}
|
||
_FAVICON_MEDIA_TYPE = {"png": "image/png", "jpg": "image/jpeg", "ico": "image/x-icon"}
|
||
|
||
|
||
@router.get("", response_model=ApiResponse[SettingOut], summary="公司信息")
|
||
def get_settings(session: DbSession, _: AdminUser) -> ApiResponse[SettingOut]:
|
||
"""取公司信息(管理端)。"""
|
||
return ok(service.to_out(service.get_setting(session)))
|
||
|
||
|
||
@router.put("", response_model=ApiResponse[SettingOut], summary="更新公司信息")
|
||
def update_settings(
|
||
payload: SettingUpdateIn,
|
||
session: DbSession,
|
||
current_user: AdminUser,
|
||
meta: RequestMetaDep,
|
||
) -> ApiResponse[SettingOut]:
|
||
"""更新公司信息(部分更新;审计留痕)。"""
|
||
return ok(
|
||
service.update_company(session, payload, operator=current_user, meta=meta),
|
||
message="已保存",
|
||
)
|
||
|
||
|
||
@router.post("/logo", response_model=ApiResponse[SettingOut], summary="上传 logo")
|
||
async def upload_logo(
|
||
file: UploadFile,
|
||
session: DbSession,
|
||
current_user: AdminUser,
|
||
meta: RequestMetaDep,
|
||
) -> ApiResponse[SettingOut]:
|
||
"""上传公司 logo(png/jpg ≤2MB;固定名覆盖,旧扩展名文件清理)。"""
|
||
content = await file.read()
|
||
ext = (file.filename or "").rsplit(".", 1)[-1]
|
||
return ok(
|
||
service.save_logo(session, content, ext, operator=current_user, meta=meta),
|
||
message="logo 已更新",
|
||
)
|
||
|
||
|
||
@router.post("/favicon", response_model=ApiResponse[SettingOut], summary="上传站点图标")
|
||
async def upload_favicon(
|
||
file: UploadFile,
|
||
session: DbSession,
|
||
current_user: AdminUser,
|
||
meta: RequestMetaDep,
|
||
) -> ApiResponse[SettingOut]:
|
||
"""上传站点图标(png/jpg/ico ≤2MB;固定名覆盖,旧扩展名文件清理)。"""
|
||
content = await file.read()
|
||
ext = (file.filename or "").rsplit(".", 1)[-1]
|
||
return ok(
|
||
service.save_favicon(session, content, ext, operator=current_user, meta=meta),
|
||
message="站点图标已更新",
|
||
)
|
||
|
||
|
||
@router.get("/public", response_model=ApiResponse[SettingPublicOut], summary="公司信息(匿名)")
|
||
def get_settings_public(session: DbSession, response: Response) -> ApiResponse[SettingPublicOut]:
|
||
"""公司信息(登录页消费,无鉴权)。短缓存减轻匿名端点压力。"""
|
||
response.headers["Cache-Control"] = "public, max-age=300"
|
||
return ok(service.to_public_out(service.get_setting(session)))
|
||
|
||
|
||
@router.get("/logo/public", summary="logo 图片(匿名)")
|
||
def get_logo_public(session: DbSession) -> FileResponse:
|
||
"""logo 文件流(无鉴权)。no-cache:图片替换后由版本参数破缓存兜底。"""
|
||
path, ext = service.read_logo(session)
|
||
return FileResponse(
|
||
path,
|
||
media_type=_LOGO_MEDIA_TYPE.get(ext, "application/octet-stream"),
|
||
headers={"Cache-Control": "no-cache"},
|
||
)
|
||
|
||
|
||
@router.get("/favicon/public", summary="站点图标(匿名)")
|
||
def get_favicon_public(session: DbSession) -> FileResponse:
|
||
"""站点图标文件流(无鉴权)。no-cache:图标替换后由版本参数破缓存兜底。"""
|
||
path, ext = service.read_favicon(session)
|
||
return FileResponse(
|
||
path,
|
||
media_type=_FAVICON_MEDIA_TYPE.get(ext, "application/octet-stream"),
|
||
headers={"Cache-Control": "no-cache"},
|
||
)
|