Dish_Recognition/dao/base_registry.py

274 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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}) [未注册到总库]"