fix(deploy): m0014补m0012静默丢失的llm_usage_status FAILED字典行(INSERT IGNORE先于legacy挪移+CI排序规则撞唯一索引被跳过)+import_init单行冲突告警跳过不中止全量(计算id与legacy行CI撞PRIMARY实测)
This commit is contained in:
parent
20a6fd8283
commit
44e6f5b18a
11
deploy/migrations/m0014_llm_usage_status_failed_dict.json
Normal file
11
deploy/migrations/m0014_llm_usage_status_failed_dict.json
Normal file
@ -0,0 +1,11 @@
|
||||
{"id": "m0014",
|
||||
"title": "补 m0012 静默丢失的 llm_usage_status FAILED 字典行——m0012 的 INSERT IGNORE 先于 legacy 挪移 UPDATE 执行,CI 排序规则下 k='FAILED' 与旧行 k='failed' 撞 (parentid,k) 唯一索引被 IGNORE 跳过,随后旧行挪去 legacy 导致新枚举行缺失;import_init 补插又因计算 id 与 legacy 行 CI 撞 PRIMARY 而中止",
|
||||
"backup_tables": ["appcodes_kv"],
|
||||
"up": [
|
||||
{"op": "sql",
|
||||
"sql": "INSERT IGNORE INTO appcodes_kv (id,parentid,k,v) VALUES ('llumg_FAILED','llm_usage_status','FAILED','失败')"}
|
||||
],
|
||||
"down": [
|
||||
{"op": "sql",
|
||||
"sql": "DELETE FROM appcodes_kv WHERE id='llumg_FAILED' AND parentid='llm_usage_status'"}
|
||||
]}
|
||||
@ -14,6 +14,8 @@ ROOT_DIR = os.path.dirname(SCRIPT_DIR)
|
||||
sys.path.insert(0, os.path.join(ROOT_DIR, 'py3', 'lib', 'python3.10', 'site-packages'))
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
from pymysql.err import IntegrityError
|
||||
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.folderUtils import ProgramPath
|
||||
@ -64,6 +66,24 @@ def normalize(data):
|
||||
return raw_ac, data.get('appcodes_kv', []), data.get('organization', [])
|
||||
|
||||
|
||||
async def _safe_insert(sor, tbl, row, total, tkey):
|
||||
"""插入一行,主键/唯一键冲突时告警跳过而非中止全量导入。
|
||||
|
||||
实测事故(2026-09-07):appcodes_kv 的 CI 排序规则下,计算 id
|
||||
'llm_usage_status_FAILED' 与 legacy 行 id 'llm_usage_status_failed' PRIMARY 撞车,
|
||||
(parentid,k) 检查通过但 INSERT 抛 1062 → 后续所有模块的导入整段中止。
|
||||
单行冲突属数据态问题(迁移/历史数据),不应阻断其余行导入。
|
||||
"""
|
||||
try:
|
||||
await sor.C(tbl, row)
|
||||
total[tkey] = total.get(tkey, 0) + 1
|
||||
except IntegrityError as e:
|
||||
if e.args[0] in (1062,):
|
||||
print(f' WARN: {tbl} 行冲突跳过(已存在等值行): id={row.get("id")} {e.args[1][:80]}')
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
async def main():
|
||||
config = getConfig(ROOT_DIR, NS={'workdir': ROOT_DIR, 'ProgramPath': ProgramPath()})
|
||||
DBPools(config.databases)
|
||||
@ -83,12 +103,11 @@ async def main():
|
||||
for ac in appcodes:
|
||||
recs = await sor.R('appcodes', {'id': ac['id']})
|
||||
if not recs:
|
||||
await sor.C('appcodes', {
|
||||
await _safe_insert(sor, 'appcodes', {
|
||||
'id': ac['id'],
|
||||
'name': ac.get('name', ''),
|
||||
'hierarchy_flg': ac.get('hierarchy_flg', '0'),
|
||||
})
|
||||
total['appcodes'] += 1
|
||||
}, total, 'appcodes')
|
||||
for kv in appcodes_kv:
|
||||
# 幂等检查用 (parentid, k) 组合(appcodes_kv 有 parentid+k 唯一索引,
|
||||
# 且历史数据 id 是 UUID 非 parentid_k 组合键,用 id 检查会漏判 → 唯一索引冲突)
|
||||
@ -96,31 +115,28 @@ async def main():
|
||||
"SELECT id FROM appcodes_kv WHERE parentid=${p}$ AND k=${k}$",
|
||||
{"p": kv.get('parentid', ''), "k": kv.get('k', '')})
|
||||
if not recs:
|
||||
await sor.C('appcodes_kv', {
|
||||
await _safe_insert(sor, 'appcodes_kv', {
|
||||
'id': kv['id'],
|
||||
'parentid': kv.get('parentid', ''),
|
||||
'k': kv.get('k', ''),
|
||||
'v': kv.get('v', ''),
|
||||
})
|
||||
total['appcodes_kv'] += 1
|
||||
}, total, 'appcodes_kv')
|
||||
for org in organization:
|
||||
recs = await sor.R('organization', {'id': org['id']})
|
||||
if not recs:
|
||||
await sor.C('organization', {
|
||||
await _safe_insert(sor, 'organization', {
|
||||
'id': org['id'],
|
||||
'orgname': org.get('orgname', ''),
|
||||
})
|
||||
total['organization'] += 1
|
||||
}, total, 'organization')
|
||||
# RBAC 角色(如 app_audit 的 owner.audit 审计独立角色),幂等按主键 id 检查
|
||||
for r in data.get('roles', []):
|
||||
recs = await sor.R('role', {'id': r['id']})
|
||||
if not recs:
|
||||
await sor.C('role', {
|
||||
await _safe_insert(sor, 'role', {
|
||||
'id': r['id'],
|
||||
'orgtypeid': r.get('orgtypeid', '0'),
|
||||
'name': r.get('name', ''),
|
||||
})
|
||||
total['roles'] += 1
|
||||
}, total, 'roles')
|
||||
# 记账配置表(accounting 模块:subject/account_config/accounting_config/currency/exchange_rate)
|
||||
# 幂等按主键 id 检查,字段名与表结构一致
|
||||
for tbl, recs_data in [
|
||||
@ -135,8 +151,7 @@ async def main():
|
||||
if not existing:
|
||||
# 去掉 None 值字段(DB 用 NULL 而非 None)
|
||||
clean = {k: v for k, v in rec.items() if v is not None}
|
||||
await sor.C(tbl, clean)
|
||||
total[tbl] = total.get(tbl, 0) + 1
|
||||
await _safe_insert(sor, tbl, clean, total, tbl)
|
||||
summary = ' '.join(f'{k} {v}' for k, v in total.items())
|
||||
print(f'import_init: {summary}')
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user