You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
6.6 KiB

17 hours ago
# -*- coding: utf-8 -*-
"""D-07 assignable 前置探测 + D-05/D-04 DB 列证据(customer-defectfix effort)。
用法py -X utf8 .scratch/customer-defectfix/probe-assignable.py
产物.scratch/customer-defectfix/probe-assignable-result.json
- assignable?id= 计时timeout=180s双次修复前后各跑一轮对比
- SHOW COLUMNS 留证customer.is_biz_negotiated / customer_import_fail.fail_reason /
customer_import_task.fail_reason
- collab 样例e2c-协同-样例owner 现状heavy 复跑副作用盲区核对
"""
import json
import os
import sys
import time
import pymysql
import requests
BASE = 'http://localhost:8080'
ADMIN = '739564171091247104'
OUT = os.path.dirname(os.path.abspath(__file__))
RESULT = os.path.join(OUT, 'probe-assignable-result.json')
TIMEOUT = 180
COLLAB_OWNER_EXPECT = ('744842318024015872', '曾偲青', '744841292483133440', '职员部')
def db():
return pymysql.connect(host='8.129.84.155', port=3306, user='root', password='Itc@123456',
database='crm', charset='utf8mb4', autocommit=True,
connect_timeout=10, cursorclass=pymysql.cursors.DictCursor)
def dbq(sql, args=None):
with db() as conn, conn.cursor() as cur:
cur.execute(sql, args)
return cur.fetchall()
def main():
ev = {'phase': sys.argv[1] if len(sys.argv) > 1 else 'unknown',
'ts': time.strftime('%Y-%m-%d %H:%M:%S')}
# ---- 1. DB 列证据(D-05 / D-04)----
cols = {}
for table, like in (('customer', 'is_biz_negotiated'), ('customer', 'is_child'),
('customer_import_fail', 'fail_reason'),
('customer_import_task', 'fail_reason')):
rows = dbq(f"SHOW COLUMNS FROM {table} LIKE '{like}'")
c = rows[0] if rows else None
cols[f'{table}.{like}'] = ({k: str(v) for k, v in c.items()} if c else None)
ev['columns'] = cols
print('== SHOW COLUMNS 证据 ==')
for k, v in cols.items():
print(f" {k}: type={v and v.get('Type')} null={v and v.get('Null')} default={v and v.get('Default')}")
# ---- 2. collab 样例 owner 现状(heavy 副作用盲区)----
collab = dbq("SELECT id, customer_name, owner_user_id, owner_user_name_snapshot, "
"owner_dept_id, owner_dept_name_snapshot FROM customer "
"WHERE customer_name='e2c-协同-样例' AND deleted=0")
ev['collab'] = [{k: str(v) for k, v in r.items()} for r in collab]
print('== collab 样例 owner 现状 ==')
for r in collab:
drift = str(r['owner_user_id']) != COLLAB_OWNER_EXPECT[0]
print(f" id={r['id']} owner={r['owner_user_id']}({r['owner_user_name_snapshot']}) "
f"dept={r['owner_dept_id']}({r['owner_dept_name_snapshot']}) "
f"{'⚠ 漂移' if drift else '正常(曾偲青)'}")
# ---- 3. 最新交接单(assignable 探测目标)----
transfers = dbq("SELECT id, transfer_no, status, from_user_id, to_director_id, total_count, "
"assigned_count FROM customer_transfer ORDER BY id DESC LIMIT 5")
ev['transfers'] = [{k: str(v) for k, v in r.items()} for r in transfers]
print('== 最近交接单 ==')
for r in transfers:
print(f" id={r['id']} no={r['transfer_no']} status={r['status']} "
f"director={r['to_director_id']} total={r['total_count']}")
if not transfers:
print('!! 无交接单可用,无法探测 assignable')
json.dump(ev, open(RESULT, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
return
tid = transfers[0]['id']
# ---- 4. admin token + assignable 计时(双次)----
tok = None
for _ in range(3):
try:
r = requests.get(f'{BASE}/api/auth/debug/token', params={'userId': ADMIN}, timeout=30)
if r.status_code == 200:
d = r.json().get('data')
tok = d if isinstance(d, str) else (d or {}).get('token')
if tok:
break
except Exception as e:
print(f' token 重试: {e}')
time.sleep(2)
if not tok:
print('!! 服务未就绪(debug/token 失败)——请先起服')
json.dump(ev, open(RESULT, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
return
probes = []
for i in range(2):
t0 = time.time()
try:
r = requests.get(f'{BASE}/api/customer/transfer/assignable', params={'id': tid},
headers={'Authorization': f'Bearer {tok}'}, timeout=TIMEOUT)
elapsed = time.time() - t0
body = r.json() if r.status_code == 200 else {}
n = len(body.get('data') or [])
probes.append({'attempt': i + 1, 'elapsed_sec': round(elapsed, 2),
'http': r.status_code, 'code': body.get('code'), 'users': n})
print(f" assignable #{i+1}: {elapsed:.2f}s http={r.status_code} users={n}")
except Exception as e:
elapsed = time.time() - t0
probes.append({'attempt': i + 1, 'elapsed_sec': round(elapsed, 2), 'error': str(e)[:120]})
print(f" assignable #{i+1}: 异常 @{elapsed:.2f}s {str(e)[:100]}")
time.sleep(2)
ev['assignable_probes'] = probes
ev['assignable_target_transfer'] = str(tid)
# ---- 5. N+1 规模参考:总监子树用户/部门数(WITH RECURSIVE)----
try:
drow = dbq("SELECT dept_id FROM crm_auth_user WHERE id=%s", (transfers[0]['to_director_id'],))
if drow and drow[0]['dept_id']:
root = drow[0]['dept_id']
sub = dbq("WITH RECURSIVE t AS (SELECT id FROM sys_dept WHERE id=%s "
"UNION ALL SELECT d.id FROM sys_dept d JOIN t ON d.parent_id=t.id) "
"SELECT COUNT(*) dept_cnt FROM t", (root,))
ucnt = dbq("WITH RECURSIVE t AS (SELECT id FROM sys_dept WHERE id=%s "
"UNION ALL SELECT d.id FROM sys_dept d JOIN t ON d.parent_id=t.id) "
"SELECT COUNT(*) user_cnt FROM crm_auth_user u "
"WHERE u.deleted=0 AND u.dept_id IN (SELECT id FROM t)", (root,))
ev['subtree'] = {'root_dept': str(root), 'dept_cnt': sub[0]['dept_cnt'],
'user_cnt': ucnt[0]['user_cnt']}
print(f" 总监子树规模:部门 {sub[0]['dept_cnt']} / 用户 {ucnt[0]['user_cnt']}"
f"(N+1 往返基数)")
except Exception as e:
print(f' 子树规模统计跳过:{e}')
json.dump(ev, open(RESULT, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
print(f"证据已落盘 {RESULT}")
if __name__ == '__main__':
main()