# coding=utf-8 """本机基地编码:注册/解析 bases,供模板 source_base_id 与 API 上报使用。""" import datetime import os from dao.database import is_server_available, local_session_scope, server_session_scope from dao.settings import DISH_BASE_CODE, DISH_BASE_NAME, DISH_BASE_REGION from models.App_settings import AppSetting from models.Bases import BaseInfo SETTING_BASE_ID = 'local_base_id' SETTING_BASE_CODE = 'local_base_code' SETTING_BASE_NAME = 'local_base_name' def _set_setting(session, key, value): row = session.get(AppSetting, key) if row: row.value = str(value) if value is not None else '' else: session.add(AppSetting(key=key, value=str(value) if value is not None else '')) def _get_setting(session, key, default=None): row = session.get(AppSetting, key) if row and row.value not in (None, ''): return row.value return default def _cache_local(base_id, base_code, base_name, region=None, contact=None): """把基地信息写入本地 SQLite(AppSetting + bases 镜像,避免 FK 问题)。""" with local_session_scope() as session: _set_setting(session, SETTING_BASE_ID, base_id if base_id is not None else '') _set_setting(session, SETTING_BASE_CODE, base_code or '') _set_setting(session, SETTING_BASE_NAME, base_name or '') if base_id is None: return existing = session.get(BaseInfo, base_id) now = datetime.datetime.now() if existing: existing.base_code = base_code existing.base_name = base_name existing.region = region existing.contact = contact existing.is_active = 1 existing.last_heartbeat = now else: session.add(BaseInfo( id=base_id, base_code=base_code, base_name=base_name, region=region, contact=contact, is_active=1, register_time=now, last_heartbeat=now, )) def _read_cached(): with local_session_scope() as session: raw_id = _get_setting(session, SETTING_BASE_ID) base_id = None if raw_id is not None: try: base_id = int(raw_id) except (TypeError, ValueError): base_id = None return { 'id': base_id, 'base_code': _get_setting(session, SETTING_BASE_CODE, DISH_BASE_CODE), 'base_name': _get_setting(session, SETTING_BASE_NAME, DISH_BASE_NAME), 'online': False, 'source': 'cache', } def resolve_local_base(force_refresh=False): """ 解析本机基地: - 先从管理系统拉取基地列表写入 MySQL(可关 DISH_MGMT_SYNC_ON_START) - 有 MySQL:按 DISH_BASE_CODE 查找;找不到再注册 - 无 MySQL:用本地缓存 / 环境变量(source_base_id 可能为空) """ import dao.settings as cfg code = (cfg.DISH_BASE_CODE or '').strip() or 'BASE_LOCAL_01' name = (cfg.DISH_BASE_NAME or '').strip() or code region = (getattr(cfg, 'DISH_BASE_REGION', None) or '').strip() or None # 启动/刷新时同步管理系统基地(失败不阻断本机解析) try: from dao.base_sync import sync_bases_from_mgmt sync_bases_from_mgmt(force=force_refresh) except Exception as exc: print(f"[WARN] mgmt bases sync skipped: {exc}") if not is_server_available(): cached = _read_cached() cached['base_code'] = code or cached.get('base_code') cached['base_name'] = name or cached.get('base_name') cached['online'] = False cached['source'] = 'offline' print(f"[WARN] base registry offline, code={cached.get('base_code')}, id={cached.get('id')}") return cached try: with server_session_scope() as session: row = ( session.query(BaseInfo) .filter(BaseInfo.base_code == code) .first() ) now = datetime.datetime.now() if row: # 已从管理系统同步的基地:保留管理系统名称,除非环境变量显式覆盖默认名 if name and name != '本地基地' and name != code: row.base_name = name if region: row.region = region row.is_active = 1 row.last_heartbeat = now created = False else: row = BaseInfo( base_code=code, base_name=name, region=region, is_active=1, register_time=now, last_heartbeat=now, ) session.add(row) session.flush() created = True info = { 'id': row.id, 'base_code': row.base_code, 'base_name': row.base_name, 'region': row.region, 'contact': row.contact, 'external_id': getattr(row, 'external_id', None), 'branch_name': getattr(row, 'branch_name', None), 'online': True, 'source': 'mysql', 'created': created, } _cache_local( info['id'], info['base_code'], info['base_name'], region=info.get('region'), contact=info.get('contact'), ) action = 'registered' if info.get('created') else 'resolved' print(f"[OK] base {action}: {info['base_code']} (id={info['id']})") return info except Exception as exc: print(f"[WARN] base registry failed: {exc}") cached = _read_cached() cached['online'] = False cached['source'] = 'error' cached['error'] = str(exc) return cached def touch_base_heartbeat(base_id=None): """同步成功后刷新基地心跳。""" if base_id is None: cached = _read_cached() base_id = cached.get('id') if not base_id or not is_server_available(): return False try: with server_session_scope() as session: row = session.get(BaseInfo, base_id) if not row: return False row.last_heartbeat = datetime.datetime.now() code, name, region = row.base_code, row.base_name, row.region _cache_local(base_id, code, name, region=region) return True except Exception as exc: print(f"[WARN] base heartbeat failed: {exc}") return False def get_active_base(): """当前生效基地:优先本地已设默认,否则回落环境变量默认值。""" import dao.settings as cfg cached = _read_cached() return { 'id': cached.get('id'), 'base_code': (cached.get('base_code') or cfg.DISH_BASE_CODE or '').strip(), 'base_name': (cached.get('base_name') or cfg.DISH_BASE_NAME or '').strip(), 'online': bool(cached.get('online')), 'source': cached.get('source') or 'cache', } def apply_runtime_base(base_id=None, base_code=None, base_name=None): """把基地写入运行时 settings / 环境变量(不落库)。""" import dao.settings as cfg code = (base_code or '').strip() name = (base_name or '').strip() if code: cfg.DISH_BASE_CODE = code os.environ['DISH_BASE_CODE'] = code if name: cfg.DISH_BASE_NAME = name os.environ['DISH_BASE_NAME'] = name cfg.DISH_DEVICE_NAME = name os.environ['DISH_DEVICE_NAME'] = name if code: cfg.DISH_DEVICE_CODE = code os.environ['DISH_DEVICE_CODE'] = code try: import service.result_api as result_api if code: result_api.RESULT_API_BASE_ID = code if name: result_api.RESULT_API_BASE_NAME = name except Exception: pass return { 'id': base_id, 'base_code': code or cfg.DISH_BASE_CODE, 'base_name': name or cfg.DISH_BASE_NAME, } def set_default_base(base_id=None, base_code=None, base_name=None, region=None, contact=None): """设为默认基地:持久化本地 + 更新运行时配置。""" code = (base_code or '').strip() name = (base_name or '').strip() if not name and not code: raise ValueError('基地名称或编码不能为空') bid = None if base_id is not None and str(base_id).strip() != '': try: bid = int(base_id) except (TypeError, ValueError): bid = None _cache_local(bid, code or name, name or code, region=region, contact=contact) info = apply_runtime_base(base_id=bid, base_code=code or name, base_name=name or code) print(f"[OK] default base set: {info.get('base_name')} ({info.get('base_code')})") return info def bootstrap_default_base(): """启动时把本地已保存的默认基地应用到运行时。""" cached = _read_cached() name = (cached.get('base_name') or '').strip() code = (cached.get('base_code') or '').strip() # 仅当本地确实设过默认时覆盖 env 默认值 if name or (code and code != 'BASE_LOCAL_01'): return apply_runtime_base( base_id=cached.get('id'), base_code=code, base_name=name, ) return get_active_base() def get_base_status_text(base_info=None): info = base_info or _read_cached() code = info.get('base_code') or DISH_BASE_CODE name = info.get('base_name') or DISH_BASE_NAME or code bid = info.get('id') if info.get('online'): return f"基地:{code}({name}) ID={bid}" if bid: return f"基地:{code}({name}) ID={bid} [离线缓存]" return f"基地:{code}({name}) [未注册到总库]"