| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- package fixture
- import (
- "context"
- "database/sql"
- "fmt"
- )
- // 本文件只发 SELECT(legacy 侧),且所有金额列一律 `CAST(... AS DECIMAL(20,3))`。
- //
- // 为什么不直接 `SELECT balance`:
- // balance 是 float(10,3)。go-sql-driver 会把它当 float64 交给 Go,
- // 于是 0.788 变成 0.78800000000000003,后面任何 Round 都在误差上做决策。
- // `CAST(x AS DECIMAL(20,3))` 让 MySQL 先把该列按「3 位小数的十进制」输出,
- // 我们再按字符串精确换成分——误差在源头就被掐掉。
- // extractExpense 读 1 个账户行。
- func extractExpense(ctx context.Context, db *sql.DB, userID int64) (*LegacyExpense, error) {
- const q = `
- SELECT userid,
- CAST(balance AS DECIMAL(20,3)),
- CAST(spend AS DECIMAL(20,3)),
- CAST(total_income AS DECIMAL(20,3)),
- CAST(total_expenditure AS DECIMAL(20,3)),
- CAST(total_recommended_amount AS DECIMAL(20,3)),
- CAST(special_expenses AS DECIMAL(20,3))
- FROM t_user_expense
- WHERE userid = ?`
- row := db.QueryRowContext(ctx, q, userID)
- var out LegacyExpense
- if err := row.Scan(&out.UserID, &out.Balance, &out.Spend, &out.TotalIncome,
- &out.TotalExpenditure, &out.TotalRecommendedAmount, &out.SpecialExpenses); err != nil {
- if err == sql.ErrNoRows {
- return nil, fmt.Errorf("legacy t_user_expense 无 userid=%d 的行", userID)
- }
- return nil, err
- }
- return &out, nil
- }
- // extractExpenseSchools 读该用户的全部校区账户行(按 legacy id 升序,保证可复现)。
- func extractExpenseSchools(ctx context.Context, db *sql.DB, userID int64) ([]LegacyExpenseSchool, error) {
- const q = `
- SELECT id, userid, schoolid, cast(ifnull(campusid,0) as signed),
- CAST(balance AS DECIMAL(20,3)),
- CAST(spend AS DECIMAL(20,3)),
- CAST(total_income AS DECIMAL(20,3)),
- CAST(total_expenditure AS DECIMAL(20,3)),
- CAST(total_recommended_amount AS DECIMAL(20,3)),
- CAST(special_expenses AS DECIMAL(20,3))
- FROM t_user_expense_school
- WHERE userid = ?
- ORDER BY id`
- rows, err := db.QueryContext(ctx, q, userID)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- var out []LegacyExpenseSchool
- for rows.Next() {
- var r LegacyExpenseSchool
- if err := rows.Scan(&r.ID, &r.UserID, &r.SchoolID, &r.CampusID,
- &r.Balance, &r.Spend, &r.TotalIncome, &r.TotalExpenditure,
- &r.TotalRecommendedAmount, &r.SpecialExpenses); err != nil {
- return nil, err
- }
- out = append(out, r)
- }
- return out, rows.Err()
- }
- // extractExpenseLogs 读该用户的全部流水(按 legacy id 升序)。
- func extractExpenseLogs(ctx context.Context, db *sql.DB, userID int64) ([]LegacyExpenseLog, error) {
- const q = `
- SELECT id, userid, cast(ifnull(type,0) as signed), logid,
- CAST(money AS DECIMAL(20,3)),
- create_userid, create_time, remark
- FROM t_user_expense_log
- WHERE userid = ?
- ORDER BY id`
- rows, err := db.QueryContext(ctx, q, userID)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- var out []LegacyExpenseLog
- for rows.Next() {
- var r LegacyExpenseLog
- if err := rows.Scan(&r.ID, &r.UserID, &r.Type, &r.LogID,
- &r.Money, &r.CreateUserID, &r.CreateTime, &r.Remark); err != nil {
- return nil, err
- }
- out = append(out, r)
- }
- return out, rows.Err()
- }
- // pickLegacyUser 在未显式指定用户时,按验收要求挑一个用户:
- // 有账户行 + 至少 1 个校区账户 + 至少 3 条流水 + balance 带 3 位小数(覆盖取整边界)。
- //
- // 只用 SELECT,且条件全部来自 information 之外的业务表本身。
- func pickLegacyUser(ctx context.Context, db *sql.DB) (int64, error) {
- const q = `
- SELECT e.userid
- FROM t_user_expense e
- JOIN (SELECT userid, COUNT(*) n FROM t_user_expense_school GROUP BY userid) s ON s.userid = e.userid
- JOIN (SELECT userid, COUNT(*) n FROM t_user_expense_log GROUP BY userid HAVING n >= 3) l ON l.userid = e.userid
- WHERE ABS(CAST(e.balance * 1000 AS DECIMAL(20,0))) % 100 >= 1
- AND ABS(e.balance) > 0
- ORDER BY e.userid
- LIMIT 1`
- var id int64
- if err := db.QueryRowContext(ctx, q).Scan(&id); err != nil {
- return 0, fmt.Errorf("自动挑选 legacy 用户失败(无满足条件的用户):%w", err)
- }
- return id, nil
- }
- // countLegacyRows 统计该用户在 3 张 legacy 表里的行数(报告里展示「搬了几行」)。
- func countLegacyRows(ctx context.Context, db *sql.DB, userID int64) (map[string]int, error) {
- out := make(map[string]int, 3)
- for _, q := range []struct {
- name string
- sql string
- }{
- {"t_user_expense", `SELECT COUNT(*) FROM t_user_expense WHERE userid = ?`},
- {"t_user_expense_school", `SELECT COUNT(*) FROM t_user_expense_school WHERE userid = ?`},
- {"t_user_expense_log", `SELECT COUNT(*) FROM t_user_expense_log WHERE userid = ?`},
- } {
- var n int
- if err := db.QueryRowContext(ctx, q.sql, userID).Scan(&n); err != nil {
- return nil, err
- }
- out[q.name] = n
- }
- return out, nil
- }
|