main.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. // Command ac15fixture 是 AC-15 阶段 2 前置件的 CLI 入口。
  2. //
  3. // 两批能力:
  4. //
  5. // 第一批(资金域 4 表:t_user_expense / _school / _log + t_user / t_legacy_mapping)
  6. // ddl 用 core-model-ddl.sql 补齐目标库结构(只 CREATE,不 DROP)
  7. // map --user 53691 抽样搬运
  8. // verify --user 53691 逐列核对(退出码 0=PASS,1=FAIL)
  9. // run --user 53691 map + verify
  10. // clean --user 53691 删除 fixture 数据并复核回 0 行
  11. // count 打印相关表当前行数
  12. //
  13. // 第二批(t_user_divide_into / _log / t_user_balance_request / t_expenditure_log
  14. // + 字典 t_expenditure,biz_no 口径首次落地)
  15. // map2 抽样搬运 4 表
  16. // verify2 逐列核对 + biz_no 四项 + D1 id 一致性
  17. // idem2 幂等实测(同一批跑两次,比对行数与 id)
  18. // run2 map2 + verify2 + idem2
  19. // clean2 清理并复核 7 张表回 0 行
  20. // count2 打印第二批涉及表当前行数
  21. //
  22. // 结构对齐(created_at / updated_at 补 DEFAULT):
  23. // align 逐表比对 DDL 与实库,打印 ALTER 清单并做 dry-run(不执行)
  24. // align --apply 清单经人工确认后执行
  25. // align --verify 只复核两列的 COLUMN_DEFAULT 是否全部非 NULL
  26. //
  27. // 环境变量(口令不落盘、不打印):
  28. //
  29. // AC15_DB_HOST / AC15_DB_PORT / AC15_DB_USER / AC15_DB_PASS / AC15_TARGET_DB
  30. // AC15_LEGACY_USER / AC15_LEGACY_PASS / AC15_LEGACY_DB
  31. // AC15_CORE_DDL core DDL 路径(默认取仓库外部的 02-data-model/core-model-ddl.sql)
  32. //
  33. // 退出码:0 = 成功/PASS;1 = 核对 FAIL;2 = 参数错误;3 = 环境或守卫失败。
  34. package main
  35. import (
  36. "context"
  37. "flag"
  38. "fmt"
  39. "os"
  40. "strconv"
  41. "strings"
  42. "yuansiwei/unified-backend/internal/academic/dualrun/fixture"
  43. )
  44. func main() {
  45. if len(os.Args) < 2 {
  46. usage()
  47. os.Exit(2)
  48. }
  49. cmd := os.Args[1]
  50. args := os.Args[2:]
  51. switch cmd {
  52. case "ddl":
  53. os.Exit(runDDL(args))
  54. case "map":
  55. os.Exit(runMap(args))
  56. case "verify":
  57. os.Exit(runVerify(args))
  58. case "run":
  59. os.Exit(runAll(args))
  60. case "clean":
  61. os.Exit(runClean(args))
  62. case "count":
  63. os.Exit(runCount(args))
  64. case "map2":
  65. os.Exit(runMap2(args))
  66. case "verify2":
  67. os.Exit(runVerify2(args))
  68. case "idem2":
  69. os.Exit(runIdem2(args))
  70. case "run2":
  71. os.Exit(runRun2(args))
  72. case "clean2":
  73. os.Exit(runClean2(args))
  74. case "count2":
  75. os.Exit(runCount2(args))
  76. case "align":
  77. os.Exit(runAlign(args))
  78. case "map3":
  79. os.Exit(runMap3(args))
  80. case "verify3":
  81. os.Exit(runVerify3(args))
  82. case "idem3":
  83. os.Exit(runIdem3(args))
  84. case "run3":
  85. os.Exit(runRun3(args))
  86. case "clean3":
  87. os.Exit(runClean3(args))
  88. case "count3":
  89. os.Exit(runCount3(args))
  90. case "-h", "--help", "help":
  91. usage()
  92. os.Exit(0)
  93. default:
  94. fmt.Fprintf(os.Stderr, "未知子命令:%s\n\n", cmd)
  95. usage()
  96. os.Exit(2)
  97. }
  98. }
  99. func usage() {
  100. fmt.Fprint(os.Stderr, `ac15fixture —— AC-15 阶段 2 前置件(资金域映射 + 逐列核对)
  101. 第一批(D1 口径为主):
  102. ddl 补齐 core-model-ddl.sql 的 13 张表(只 CREATE TABLE IF NOT EXISTS,禁止 DROP)
  103. map 抽样搬运一个 legacy 用户(账户 + 校区账户 + 流水)
  104. verify 逐列核对 4 张表 + AUTO_INCREMENT + 空值语义 + float 陷阱
  105. run map + verify
  106. clean 删除 fixture 数据并复核 4 张表回 0 行
  107. count 打印相关表当前行数
  108. 第二批(biz_no 口径首次落地):
  109. map2 搬运 t_user_divide_into / _log / t_user_balance_request / t_expenditure_log(+字典)
  110. verify2 逐列核对 + biz_no 四项 + D1 id 一致性
  111. idem2 幂等实测(同一批跑两次,比行数与 id)
  112. run2 map2 + verify2 + idem2
  113. clean2 清理并复核 7 张表回 0 行
  114. count2 打印第二批涉及表当前行数
  115. 第三批(org 域:t_institution / t_campus,D1 + 引用完整性):
  116. map3 迁移指定机构与其校区(默认 1,63317,63323,63324,63325,63332)
  117. verify3 逐列核对 + 机构集合 + 引用完整性(孤儿=0)+ R17 + bit(1)
  118. idem3 幂等实测(跑两次比行数与 id)
  119. run3 map3 + verify3 + idem3
  120. clean3 清理并复核两表回 0 行
  121. count3 打印 org 两表行数
  122. 参数:--ids 1,63317,... --all-campuses=true
  123. 结构对齐(created_at / updated_at 补 DEFAULT,只 MODIFY 不重建):
  124. align 逐表比对 DDL 与实库,打印 ALTER 清单 + dry-run(不执行)
  125. align --apply 清单确认后执行
  126. align --verify 只复核 COLUMN_DEFAULT 是否全部非 NULL
  127. 第一批参数:--user <legacy userid>(默认 53691), --ddl <path>
  128. 第二批参数:--root <分账树根 userid,默认 395>, --buser <充值工单用户,默认 31348>,
  129. --euser <收支记录创建人,默认 395>, --dict <是否迁移字典,默认 true>
  130. `)
  131. }
  132. // bootstrap 解析第一批公共参数、加载配置并跑守卫。
  133. func bootstrap(args []string, withUser bool) (fixture.Config, int64, string, int) {
  134. fs := flag.NewFlagSet("ac15fixture", flag.ContinueOnError)
  135. fs.SetOutput(os.Stderr)
  136. user := fs.Int64("user", fixture.DefaultLegacyUserID, "legacy userid")
  137. ddl := fs.String("ddl", "", "core DDL 路径(默认 AC15_CORE_DDL 或仓库外部默认路径)")
  138. if err := fs.Parse(args); err != nil {
  139. return fixture.Config{}, 0, "", 2
  140. }
  141. if !withUser {
  142. *user = fixture.DefaultLegacyUserID
  143. }
  144. cfg, code := loadConfig()
  145. if code != 0 {
  146. return fixture.Config{}, 0, "", code
  147. }
  148. return cfg, *user, *ddl, 0
  149. }
  150. // loadConfig 取工作目录 → 读配置 → 守卫 → 打印脱敏后的连接串。
  151. func loadConfig() (fixture.Config, int) {
  152. repoRoot, err := os.Getwd()
  153. if err != nil {
  154. fmt.Fprintf(os.Stderr, "FATAL: 取工作目录失败:%v\n", err)
  155. return fixture.Config{}, 3
  156. }
  157. cfg, err := fixture.LoadConfig(repoRoot)
  158. if err != nil {
  159. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  160. return fixture.Config{}, 3
  161. }
  162. if err := cfg.Guard(); err != nil {
  163. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  164. return fixture.Config{}, 3
  165. }
  166. fmt.Printf("target=%s legacy=%s\n\n", cfg.TargetMasked(), cfg.LegacyMasked())
  167. return cfg, 0
  168. }
  169. // bootstrap2 解析第二批参数。
  170. func bootstrap2(args []string) (fixture.Config, fixture.Batch2Options, int) {
  171. def := fixture.DefaultBatch2Options()
  172. fs := flag.NewFlagSet("ac15fixture-b2", flag.ContinueOnError)
  173. fs.SetOutput(os.Stderr)
  174. root := fs.Int64("root", def.DivideRootUserID, "分账树根 userid(取整棵子树)")
  175. buser := fs.Int64("buser", def.BalanceRequestUserID, "充值工单所属 userid")
  176. euser := fs.Int64("euser", def.ExpenditureLogUserID, "收支记录创建人 userid")
  177. dict := fs.Bool("dict", def.WithExpenditureDict, "是否同时 D1 迁移 t_expenditure 字典")
  178. if err := fs.Parse(args); err != nil {
  179. return fixture.Config{}, fixture.Batch2Options{}, 2
  180. }
  181. cfg, code := loadConfig()
  182. if code != 0 {
  183. return fixture.Config{}, fixture.Batch2Options{}, code
  184. }
  185. return cfg, fixture.Batch2Options{
  186. DivideRootUserID: *root,
  187. BalanceRequestUserID: *buser,
  188. ExpenditureLogUserID: *euser,
  189. WithExpenditureDict: *dict,
  190. }, 0
  191. }
  192. func runDDL(args []string) int {
  193. cfg, _, ddlPath, code := bootstrap(args, false)
  194. if code != 0 {
  195. return code
  196. }
  197. rep, err := fixture.ApplyCoreDDL(context.Background(), cfg, ddlPath)
  198. if rep != nil {
  199. fixture.PrintDDLReport(os.Stdout, rep)
  200. }
  201. if err != nil {
  202. fmt.Fprintf(os.Stderr, "\nFATAL: %v\n", err)
  203. return 3
  204. }
  205. return 0
  206. }
  207. func runMap(args []string) int {
  208. cfg, user, _, code := bootstrap(args, true)
  209. if code != 0 {
  210. return code
  211. }
  212. rep, err := fixture.PrepareAndLoad(context.Background(), cfg, user)
  213. if err != nil {
  214. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  215. return 3
  216. }
  217. fixture.PrintMapReport(os.Stdout, rep)
  218. return 0
  219. }
  220. func runVerify(args []string) int {
  221. cfg, user, _, code := bootstrap(args, true)
  222. if code != 0 {
  223. return code
  224. }
  225. rep, err := fixture.Verify(context.Background(), cfg, user)
  226. if err != nil {
  227. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  228. return 3
  229. }
  230. fixture.PrintVerifyReport(os.Stdout, rep)
  231. if !rep.OK() {
  232. return 1
  233. }
  234. return 0
  235. }
  236. func runAll(args []string) int {
  237. if code := runMap(args); code != 0 {
  238. return code
  239. }
  240. fmt.Println()
  241. return runVerify(args)
  242. }
  243. func runClean(args []string) int {
  244. cfg, user, _, code := bootstrap(args, true)
  245. if code != 0 {
  246. return code
  247. }
  248. rep, err := fixture.Cleanup(context.Background(), cfg, user)
  249. if err != nil {
  250. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  251. return 3
  252. }
  253. fixture.PrintCleanReport(os.Stdout, rep)
  254. return 0
  255. }
  256. func runCount(args []string) int {
  257. cfg, _, _, code := bootstrap(args, false)
  258. if code != 0 {
  259. return code
  260. }
  261. counts, err := fixture.CountTargetTables(context.Background(), cfg)
  262. if err != nil {
  263. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  264. return 3
  265. }
  266. fixture.PrintCounts(os.Stdout, "第一批目标表行数", counts,
  267. []string{"t_user", "t_legacy_mapping", "t_user_expense", "t_user_expense_school", "t_user_expense_log"})
  268. return 0
  269. }
  270. // ---------- 第二批 ----------
  271. func runMap2(args []string) int {
  272. cfg, opts, code := bootstrap2(args)
  273. if code != 0 {
  274. return code
  275. }
  276. rep, err := fixture.PrepareBatch2(context.Background(), cfg, opts)
  277. if err != nil {
  278. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  279. return 3
  280. }
  281. fixture.PrintBatch2Report(os.Stdout, rep)
  282. return 0
  283. }
  284. func runVerify2(args []string) int {
  285. cfg, opts, code := bootstrap2(args)
  286. if code != 0 {
  287. return code
  288. }
  289. rep, err := fixture.VerifyBatch2(context.Background(), cfg, opts)
  290. if err != nil {
  291. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  292. return 3
  293. }
  294. fixture.PrintBatch2Verify(os.Stdout, rep)
  295. if !rep.OK() {
  296. return 1
  297. }
  298. return 0
  299. }
  300. func runIdem2(args []string) int {
  301. cfg, opts, code := bootstrap2(args)
  302. if code != 0 {
  303. return code
  304. }
  305. rep, err := fixture.IdempotencyCheck(context.Background(), cfg, opts)
  306. if err != nil {
  307. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  308. return 3
  309. }
  310. fixture.PrintIdempotency(os.Stdout, rep)
  311. if !rep.AllOK() {
  312. return 1
  313. }
  314. return 0
  315. }
  316. func runRun2(args []string) int {
  317. if code := runMap2(args); code != 0 {
  318. return code
  319. }
  320. fmt.Println()
  321. if code := runVerify2(args); code != 0 {
  322. return code
  323. }
  324. fmt.Println()
  325. return runIdem2(args)
  326. }
  327. func runClean2(args []string) int {
  328. cfg, opts, code := bootstrap2(args)
  329. if code != 0 {
  330. return code
  331. }
  332. rep, err := fixture.CleanupBatch2(context.Background(), cfg, opts)
  333. if err != nil {
  334. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  335. return 3
  336. }
  337. fixture.PrintBatch2Clean(os.Stdout, rep)
  338. return 0
  339. }
  340. func runAlign(args []string) int {
  341. fs := flag.NewFlagSet("align", flag.ContinueOnError)
  342. fs.SetOutput(os.Stderr)
  343. apply := fs.Bool("apply", false, "执行清单里的 ALTER(默认只打印 + dry-run)")
  344. verifyOnly := fs.Bool("verify", false, "只复核列默认值")
  345. modelDir := fs.String("model", "", "DDL 目录(默认 AC15_MODEL_DIR 或内置路径)")
  346. columns := fs.String("columns", "created_at,updated_at",
  347. "参与对齐的列:created_at,updated_at | create_time | all")
  348. algorithm := fs.String("algorithm", "instant",
  349. "ALGORITHM 子句:instant|inplace|copy|none(MySQL 8.0.23 不支持 instant,需显式 none)")
  350. fixNullable := fs.Bool("fix-nullable", false, "按 DDL 修正 NULL 性(默认只报告不改)")
  351. if err := fs.Parse(args); err != nil {
  352. return 2
  353. }
  354. cfg, code := loadConfig()
  355. if code != 0 {
  356. return code
  357. }
  358. ctx := context.Background()
  359. cols, err := parseColumns(*columns)
  360. if err != nil {
  361. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  362. return 2
  363. }
  364. if *verifyOnly {
  365. total, missing, err := fixture.VerifyAlign(ctx, cfg, cols)
  366. if err != nil {
  367. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  368. return 3
  369. }
  370. fmt.Printf("== 复核 %v:共 %d 列 ==\n", cols, total)
  371. if len(missing) == 0 {
  372. fmt.Printf("COLUMN_DEFAULT 全部非 NULL:PASS\n")
  373. return 0
  374. }
  375. fmt.Printf("仍有 %d 列为 NULL:\n", len(missing))
  376. for _, m := range missing {
  377. fmt.Printf(" - %s\n", m)
  378. }
  379. return 1
  380. }
  381. opts := fixture.AlignOptions{
  382. ModelDir: *modelDir, Columns: cols,
  383. Algorithm: *algorithm, FixNullable: *fixNullable,
  384. }
  385. rep, err := fixture.BuildAlignReport(ctx, cfg, opts)
  386. if err != nil {
  387. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  388. return 3
  389. }
  390. if err := fixture.DryRunAlign(ctx, cfg, rep); err != nil {
  391. fmt.Fprintf(os.Stderr, "FATAL: dry-run 失败:%v\n", err)
  392. return 3
  393. }
  394. if *apply {
  395. if err := fixture.ApplyAlign(ctx, cfg, rep); err != nil {
  396. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  397. return 3
  398. }
  399. }
  400. fixture.PrintAlignReport(os.Stdout, rep)
  401. if *apply {
  402. total, missing, err := fixture.VerifyAlign(ctx, cfg, cols)
  403. if err != nil {
  404. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  405. return 3
  406. }
  407. fmt.Printf("\n== 执行后复核 %v:共 %d 列 ==\n", cols, total)
  408. if len(missing) == 0 {
  409. fmt.Printf("COLUMN_DEFAULT 全部非 NULL:PASS\n")
  410. } else {
  411. fmt.Printf("仍有 %d 列为 NULL:\n", len(missing))
  412. for _, m := range missing {
  413. fmt.Printf(" - %s\n", m)
  414. }
  415. }
  416. if rep.Failed > 0 {
  417. return 3
  418. }
  419. }
  420. return 0
  421. }
  422. // parseColumns 解析 --columns:all = 审计列 + 业务列。
  423. func parseColumns(s string) ([]string, error) {
  424. s = strings.TrimSpace(s)
  425. switch s {
  426. case "", "audit":
  427. return fixture.ColumnsAudit, nil
  428. case "business":
  429. return fixture.ColumnsBusiness, nil
  430. case "all":
  431. return append(append([]string{}, fixture.ColumnsAudit...), fixture.ColumnsBusiness...), nil
  432. }
  433. var out []string
  434. for _, p := range strings.Split(s, ",") {
  435. p = strings.TrimSpace(p)
  436. if p == "" {
  437. continue
  438. }
  439. out = append(out, p)
  440. }
  441. if len(out) == 0 {
  442. return nil, fmt.Errorf("--columns 为空")
  443. }
  444. return out, nil
  445. }
  446. // ---------- 第三批 ----------
  447. func bootstrap3(args []string) (fixture.Config, fixture.OrgOptions, int) {
  448. def := fixture.DefaultOrgOptions()
  449. fs := flag.NewFlagSet("ac15fixture-b3", flag.ContinueOnError)
  450. fs.SetOutput(os.Stderr)
  451. ids := fs.String("ids", "", "逗号分隔的机构 id(留空=默认 6 个)")
  452. allCampuses := fs.Bool("all-campuses", def.WithAllCampuses, "迁移所选机构的全部校区(否则只迁数据里出现过的)")
  453. orgDB := fs.String("org-db", def.OrgSourceDB, "org 源库(默认 yuansiwei_i)")
  454. if err := fs.Parse(args); err != nil {
  455. return fixture.Config{}, fixture.OrgOptions{}, 2
  456. }
  457. opts := def
  458. if strings.TrimSpace(*ids) != "" {
  459. opts.InstitutionIDs = nil
  460. for _, p := range strings.Split(*ids, ",") {
  461. p = strings.TrimSpace(p)
  462. if p == "" {
  463. continue
  464. }
  465. v, err := strconv.ParseInt(p, 10, 64)
  466. if err != nil {
  467. fmt.Fprintf(os.Stderr, "FATAL: 非法机构 id %q\n", p)
  468. return fixture.Config{}, fixture.OrgOptions{}, 2
  469. }
  470. opts.InstitutionIDs = append(opts.InstitutionIDs, v)
  471. }
  472. }
  473. opts.WithAllCampuses = *allCampuses
  474. opts.OrgSourceDB = *orgDB
  475. cfg, code := loadConfig()
  476. if code != 0 {
  477. return fixture.Config{}, fixture.OrgOptions{}, code
  478. }
  479. fmt.Printf("org 源=%s\n", cfg.OrgMasked())
  480. return cfg, opts, 0
  481. }
  482. func runMap3(args []string) int {
  483. cfg, opts, code := bootstrap3(args)
  484. if code != 0 {
  485. return code
  486. }
  487. rep, err := fixture.PrepareOrg(context.Background(), cfg, opts)
  488. if err != nil {
  489. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  490. return 3
  491. }
  492. fixture.PrintOrgReport(os.Stdout, rep)
  493. return 0
  494. }
  495. func runVerify3(args []string) int {
  496. cfg, opts, code := bootstrap3(args)
  497. if code != 0 {
  498. return code
  499. }
  500. rep, err := fixture.VerifyOrg(context.Background(), cfg, opts)
  501. if err != nil {
  502. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  503. return 3
  504. }
  505. fixture.PrintOrgVerify(os.Stdout, rep)
  506. if !rep.OK() {
  507. return 1
  508. }
  509. return 0
  510. }
  511. func runIdem3(args []string) int {
  512. cfg, opts, code := bootstrap3(args)
  513. if code != 0 {
  514. return code
  515. }
  516. rep, err := fixture.IdempotencyCheckOrg(context.Background(), cfg, opts)
  517. if err != nil {
  518. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  519. return 3
  520. }
  521. fixture.PrintOrgIdem(os.Stdout, rep)
  522. if !rep.AllOK() {
  523. return 1
  524. }
  525. return 0
  526. }
  527. func runRun3(args []string) int {
  528. if code := runMap3(args); code != 0 {
  529. return code
  530. }
  531. fmt.Println()
  532. if code := runVerify3(args); code != 0 {
  533. return code
  534. }
  535. fmt.Println()
  536. return runIdem3(args)
  537. }
  538. func runClean3(args []string) int {
  539. cfg, opts, code := bootstrap3(args)
  540. if code != 0 {
  541. return code
  542. }
  543. rep, err := fixture.CleanupOrg(context.Background(), cfg, opts)
  544. if err != nil {
  545. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  546. return 3
  547. }
  548. fixture.PrintOrgClean(os.Stdout, rep)
  549. return 0
  550. }
  551. func runCount3(args []string) int {
  552. cfg, _, code := bootstrap3(args)
  553. if code != 0 {
  554. return code
  555. }
  556. counts, err := fixture.CountOrgTables(context.Background(), cfg)
  557. if err != nil {
  558. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  559. return 3
  560. }
  561. fixture.PrintCounts(os.Stdout, "第三批 org 表行数", counts, []string{"t_institution", "t_campus"})
  562. return 0
  563. }
  564. func runCount2(args []string) int {
  565. cfg, _, code := bootstrap2(args)
  566. if code != 0 {
  567. return code
  568. }
  569. counts, err := fixture.CountBatch2Tables(context.Background(), cfg)
  570. if err != nil {
  571. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  572. return 3
  573. }
  574. fixture.PrintCounts(os.Stdout, "第二批目标表行数", counts, fixture.Batch2Tables())
  575. return 0
  576. }