216 lines
8.8 KiB
Python
216 lines
8.8 KiB
Python
"""系统配置服务模块
|
||
|
||
负责系统配置项的查询和更新。
|
||
提供数据库优先、默认值兜底的读取策略,支持对关键配置项的值校验。
|
||
所有写操作均会记录审计日志。
|
||
|
||
被调用方:configs 路由(配置查询、配置更新接口)。
|
||
"""
|
||
|
||
from sqlalchemy.exc import SQLAlchemyError
|
||
from sqlalchemy.orm import Session
|
||
|
||
from backend.app.core.error_codes import ErrorCode
|
||
from backend.app.core.exceptions import AppException
|
||
from backend.app.repositories.config_repository import ConfigRepository
|
||
from backend.app.services.audit_service import audit_service
|
||
from backend.app.services.bootstrap import DEFAULT_CONFIGS
|
||
|
||
|
||
class ConfigService:
|
||
"""系统配置业务服务。
|
||
|
||
依赖:
|
||
- ConfigRepository:配置数据的读写操作。
|
||
- AuditService:操作审计日志记录。
|
||
- DEFAULT_CONFIGS:bootstrap 阶段初始化的默认配置列表。
|
||
"""
|
||
|
||
def __init__(self) -> None:
|
||
self.repository = ConfigRepository()
|
||
self.default_configs = {item["config_key"]: item for item in DEFAULT_CONFIGS}
|
||
|
||
def get_config(self, config_key: str, session: Session | None = None) -> dict:
|
||
"""查询单个配置项。
|
||
|
||
优先从数据库读取,若数据库无数据则从默认配置中兜底返回。
|
||
|
||
参数:
|
||
config_key: 配置键名。
|
||
session: 数据库会话,为 None 时直接返回默认值。
|
||
|
||
返回:
|
||
包含 config_key、config_value、config_name、remark、status 的字典。
|
||
|
||
被调用方:configs 路由(配置查询接口)。
|
||
"""
|
||
self._ensure_config_key(config_key)
|
||
|
||
if session is not None:
|
||
try:
|
||
config = self.repository.get_by_key(session, config_key)
|
||
if config is not None:
|
||
return self._map_config(config)
|
||
except SQLAlchemyError as exc:
|
||
raise AppException(code=ErrorCode.INTERNAL_ERROR, message="数据库异常", status_code=500) from exc
|
||
|
||
default_item = self.default_configs.get(config_key)
|
||
if default_item is None:
|
||
raise AppException(code=ErrorCode.NOT_FOUND, message="配置不存在", status_code=404)
|
||
return {
|
||
"config_key": default_item["config_key"],
|
||
"config_value": default_item["config_value"],
|
||
"config_name": default_item["config_name"],
|
||
"remark": None,
|
||
"status": 1,
|
||
}
|
||
|
||
def update_config(self, config_key: str, payload: dict, session: Session | None = None) -> dict:
|
||
"""更新或新建配置项。
|
||
|
||
若配置已存在则更新,不存在则创建。写操作会记录审计日志。
|
||
针对特定配置键有值校验(如欠款生成模式、超时小时数等)。
|
||
|
||
参数:
|
||
config_key: 配置键名。
|
||
payload: 配置数据字典,必含 config_value。
|
||
session: 数据库会话,不可为 None。
|
||
|
||
返回:
|
||
包含配置各字段及 updated 标志的字典。
|
||
|
||
被调用方:configs 路由(配置更新接口)。
|
||
"""
|
||
self._ensure_config_key(config_key)
|
||
self._ensure_config_value(config_key, payload["config_value"])
|
||
config_name = (payload.get("config_name") or self.default_configs.get(config_key, {}).get("config_name") or config_key).strip()
|
||
|
||
if session is not None:
|
||
try:
|
||
config = self.repository.get_by_key(session, config_key)
|
||
if config is None:
|
||
config = self.repository.create_config(
|
||
session,
|
||
{
|
||
"config_key": config_key,
|
||
"config_value": payload["config_value"].strip(),
|
||
"config_name": config_name,
|
||
"remark": payload.get("remark"),
|
||
"status": payload.get("status", 1),
|
||
},
|
||
)
|
||
audit_service.write_log(
|
||
session,
|
||
{
|
||
"operate_type": "config_create",
|
||
"biz_type": "system_config",
|
||
"biz_id": config.id,
|
||
"before_value": None,
|
||
"after_value": self._map_config(config),
|
||
"remark": f"新增配置 {config_key}",
|
||
},
|
||
)
|
||
else:
|
||
before_snapshot = self._map_config(config)
|
||
self.repository.update_config(
|
||
config,
|
||
{
|
||
"config_value": payload["config_value"].strip(),
|
||
"config_name": config_name,
|
||
"remark": payload.get("remark"),
|
||
"status": payload.get("status", 1),
|
||
},
|
||
)
|
||
audit_service.write_log(
|
||
session,
|
||
{
|
||
"operate_type": "config_update",
|
||
"biz_type": "system_config",
|
||
"biz_id": config.id,
|
||
"before_value": before_snapshot,
|
||
"after_value": self._map_config(config),
|
||
"remark": f"更新配置 {config_key}",
|
||
},
|
||
)
|
||
session.commit()
|
||
return {
|
||
"config_key": config.config_key,
|
||
"config_value": config.config_value,
|
||
"config_name": config.config_name,
|
||
"remark": config.remark,
|
||
"status": config.status,
|
||
"updated": True,
|
||
}
|
||
except AppException:
|
||
session.rollback()
|
||
raise
|
||
except SQLAlchemyError as exc:
|
||
session.rollback()
|
||
raise AppException(code=ErrorCode.INTERNAL_ERROR, message="数据库异常", status_code=500) from exc
|
||
|
||
raise AppException(code=ErrorCode.INTERNAL_ERROR, message="数据库连接不可用", status_code=500)
|
||
|
||
def _ensure_config_key(self, config_key: str) -> None:
|
||
"""校验配置键名不为空。
|
||
|
||
参数:
|
||
config_key: 配置键名。
|
||
|
||
异常:
|
||
键名为空时抛出 AppException(PARAM_ERROR)。
|
||
"""
|
||
if not config_key.strip():
|
||
raise AppException(code=ErrorCode.PARAM_ERROR, message="配置键不能为空", status_code=400)
|
||
|
||
def _ensure_config_value(self, config_key: str, config_value: str) -> None:
|
||
"""校验配置值的合法性,针对特定配置键有专用校验规则。
|
||
|
||
校验规则:
|
||
- arrears_generate_mode:仅允许 'shipped' 或 'delivered'。
|
||
- logistics_timeout_hours / inactive_customer_days:必须为正整数。
|
||
- inactive_customer_amount_threshold:必须为非负数。
|
||
|
||
参数:
|
||
config_key: 配置键名。
|
||
config_value: 配置值。
|
||
|
||
异常:
|
||
值不合法时抛出 AppException(PARAM_ERROR)。
|
||
"""
|
||
value = config_value.strip()
|
||
if not value:
|
||
raise AppException(code=ErrorCode.PARAM_ERROR, message="配置值不能为空", status_code=400)
|
||
|
||
if config_key == "arrears_generate_mode" and value not in {"shipped", "pending_logistics"}:
|
||
raise AppException(code=ErrorCode.PARAM_ERROR, message="欠款生成模式仅支持 shipped 或 pending_logistics", status_code=400)
|
||
if config_key in {"logistics_timeout_hours", "inactive_customer_days"}:
|
||
if not value.isdigit() or int(value) <= 0:
|
||
raise AppException(code=ErrorCode.PARAM_ERROR, message="配置值必须为正整数", status_code=400)
|
||
if config_key == "inactive_customer_amount_threshold":
|
||
try:
|
||
amount = float(value)
|
||
except ValueError as exc:
|
||
raise AppException(code=ErrorCode.PARAM_ERROR, message="金额阈值格式错误", status_code=400) from exc
|
||
if amount < 0:
|
||
raise AppException(code=ErrorCode.PARAM_ERROR, message="金额阈值不能小于 0", status_code=400)
|
||
|
||
def _map_config(self, config) -> dict:
|
||
"""将数据库配置对象转换为 API 返回用的字典。
|
||
|
||
参数:
|
||
config: SQLAlchemy 的配置模型对象。
|
||
|
||
返回:
|
||
包含 config_key、config_value、config_name、remark、status 的字典。
|
||
"""
|
||
return {
|
||
"config_key": config.config_key,
|
||
"config_value": config.config_value,
|
||
"config_name": config.config_name,
|
||
"remark": config.remark,
|
||
"status": config.status,
|
||
}
|
||
|
||
|
||
config_service = ConfigService()
|