# -*- coding: utf-8 -*- """票 10-N3g:新端点真值采集(project/page + detail-head.projectCount + graph/detail 两字段)。 夹具:DB 预置 crm_project 一行(e2c-r3d-n3- 前缀,采集后删除); 图谱联系人 isInternal/giftRemark 临时改值(先存原值,采集后还原)。 产物:specimens-proj-truth.json('METHOD /path' → {request, response},与套件 specimens 同构)。 """ from __future__ import annotations import io import json import sys from pathlib import Path import pymysql import requests sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') BASE = 'http://localhost:8080' ADMIN = '739564171091247104' # 罗伟健 OUT = Path('.scratch/customer-integration-ready') PROJ_ID = 193400000000000001 PROJ_NAME = 'e2c-r3d-n3-项目甲' GRAPH_CUSTOMER = 753314901191032832 # 图谱 seed 客户(e2e-graph 夹具所属) specimens: dict[str, dict] = {} def capture(method, path, req, resp): specimens[f'{method} {path}'] = {'request': req, 'response': resp} def get_token(uid): r = requests.get(f'{BASE}/api/auth/debug/token', params={'userId': uid}, timeout=10) r.raise_for_status() return r.json()['data'] def api(method, path, params=None, form=None, step='', uid=ADMIN): headers = {'Authorization': f'Bearer {get_token(uid)}'} kw = {'headers': headers, 'timeout': 60} if method == 'POST': kw['data'] = form or {} else: kw['params'] = params or {} r = requests.request(method, BASE + path, **kw) try: body = r.json() except Exception: body = {'_raw': r.text[:300], '_status': r.status_code} print(f" [{step or path}] http={r.status_code} code={body.get('code') if isinstance(body, dict) else '?'}") return r, body 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 dbx(sql, args=None): with db() as conn, conn.cursor() as cur: cur.execute(sql, args) return cur.rowcount def cols(table): return {r['Field']: r for r in dbq(f'DESCRIBE {table}')} def main(): # 0) 前置健康:debug token 可用 print('== 0) token =', get_token(ADMIN)[:16], '...') # 1) 挑 seed 活跃客户(e2c- 前缀、owner=ADMIN) rows = dbq("SELECT id, customer_name FROM customer WHERE customer_name LIKE %s " "AND owner_user_id=%s ORDER BY id LIMIT 5", ('e2c%', ADMIN)) if not rows: print('!! 无 e2c- seed 客户'); return cust = rows[0] print(f"== 1) 夹具客户 {cust['id']} {cust['customer_name']}") # 2) DB 预置 crm_project(列名按 DESCRIBE 过滤,缺列跳过) want = { 'id': PROJ_ID, 'project_name': PROJ_NAME, 'project_stage': 0, 'project_status': 1, 'filing_status': 1, 'bid_result': 0, 'scheme_card_id': 193400000000000002, 'customer_id': cust['id'], 'owner_user_id': ADMIN, 'owner_name_snapshot': '罗伟健', 'project_amount': 123456.78, 'creator_id': ADMIN, 'updater_id': ADMIN, 'deleted': 0, } have = cols('crm_project') if 'create_time' in have: want['create_time'] = 'NOW()' if 'update_time' in have: want['update_time'] = 'NOW()' names, vals = [], [] for k, v in want.items(): if k not in have: print(f' (跳过不存在列 {k})'); continue names.append(k) vals.append(v) # NOW() 字符串由下方拼 SQL 处理 # NOW() 需要裸拼;其余参数化 col_sql = ', '.join(names) ph = ', '.join('NOW()' if isinstance(v, str) and v == 'NOW()' else '%s' for v in vals) real = [v for v in vals if not (isinstance(v, str) and v == 'NOW()')] dbx(f'INSERT INTO crm_project ({col_sql}) VALUES ({ph})', real) print(f'== 2) crm_project 夹具行 id={PROJ_ID}') try: # 3) 新端点:关联项目页签 _, b1 = api('GET', '/api/customer/project/page', params={'id': cust['id'], 'current': 1, 'size': 10}, step='project/page') capture('GET', '/api/customer/project/page', {'id': str(cust['id']), 'current': 1, 'size': 10}, b1) # 4) detail-head:projectCount 真值 _, b2 = api('GET', '/api/customer/detail-head', params={'id': cust['id']}, step='detail-head') print(' projectCount =', (b2.get('data') or {}).get('projectCount')) capture('GET', '/api/customer/detail-head', {'id': str(cust['id'])}, b2) # 5) 图谱两字段:联系人临时改值 → 采集 → 还原 contacts = dbq('SELECT id, is_internal, gift_remark FROM customer_contact ' 'WHERE customer_id=%s ORDER BY id LIMIT 1', (GRAPH_CUSTOMER,)) if contacts: c0 = contacts[0] orig = (c0['is_internal'], c0['gift_remark']) print(f"== 3) 图谱联系人 {c0['id']} 原值 is_internal={orig[0]} gift={orig[1]!r}") dbx('UPDATE customer_contact SET is_internal=1, gift_remark=%s WHERE id=%s', ('e2c-r3d-n3-礼品-龙井茶两盒', c0['id'])) try: _, b3 = api('GET', '/api/customer/contact/graph/detail', params={'customerId': GRAPH_CUSTOMER}, step='graph/detail') capture('GET', '/api/customer/contact/graph/detail', {'customerId': str(GRAPH_CUSTOMER)}, b3) finally: dbx('UPDATE customer_contact SET is_internal=%s, gift_remark=%s WHERE id=%s', (orig[0], orig[1], c0['id'])) print(' 已还原') else: print('!! 图谱客户无联系人,graph 真值跳过(E2E 重跑后另行回填)') finally: # 6) 清理夹具行 n = dbx('DELETE FROM crm_project WHERE id=%s', (PROJ_ID,)) print(f'== 6) 夹具清理 crm_project deleted={n}') (OUT / 'specimens-proj-truth.json').write_text( json.dumps(specimens, ensure_ascii=False, indent=2), encoding='utf-8') print('== specimens-proj-truth.json 落盘,', len(specimens), '条') if __name__ == '__main__': main()