2026-05-15 11:24:15 +08:00
|
|
|
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
|
2026-05-15 11:47:29 +08:00
|
|
|
from backend.app.services.audit_service import audit_service
|
2026-05-15 11:24:15 +08:00
|
|
|
from backend.app.services.bootstrap import DEFAULT_CONFIGS
|
|
|
|
|
|
|
|
|
|
|
2026-05-14 13:51:06 +08:00
|
|
|
class ConfigService:
|
2026-05-15 11:24:15 +08:00
|
|
|
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:
|
|
|
|
|
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:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
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),
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-15 11:47:29 +08:00
|
|
|
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}",
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-15 11:24:15 +08:00
|
|
|
else:
|
2026-05-15 11:47:29 +08:00
|
|
|
before_snapshot = self._map_config(config)
|
2026-05-15 11:24:15 +08:00
|
|
|
self.repository.update_config(
|
|
|
|
|
config,
|
|
|
|
|
{
|
|
|
|
|
"config_value": payload["config_value"].strip(),
|
|
|
|
|
"config_name": config_name,
|
|
|
|
|
"remark": payload.get("remark"),
|
|
|
|
|
"status": payload.get("status", 1),
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-15 11:47:29 +08:00
|
|
|
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}",
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-15 11:24:15 +08:00
|
|
|
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:
|
|
|
|
|
session.rollback()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"config_key": config_key,
|
|
|
|
|
"config_value": payload["config_value"].strip(),
|
|
|
|
|
"config_name": config_name,
|
|
|
|
|
"remark": payload.get("remark"),
|
|
|
|
|
"status": payload.get("status", 1),
|
|
|
|
|
"updated": True,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _ensure_config_key(self, config_key: str) -> None:
|
|
|
|
|
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:
|
|
|
|
|
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", "delivered"}:
|
|
|
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="欠款生成模式仅支持 shipped 或 delivered", 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)
|
2026-05-14 13:51:06 +08:00
|
|
|
|
2026-05-15 11:24:15 +08:00
|
|
|
def _map_config(self, config) -> dict:
|
|
|
|
|
return {
|
|
|
|
|
"config_key": config.config_key,
|
|
|
|
|
"config_value": config.config_value,
|
|
|
|
|
"config_name": config.config_name,
|
|
|
|
|
"remark": config.remark,
|
|
|
|
|
"status": config.status,
|
|
|
|
|
}
|
2026-05-14 13:51:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
config_service = ConfigService()
|