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: 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), }, ) 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: 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) 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, } config_service = ConfigService()