crm/migrate_to_mysql.go
2026-01-26 11:15:12 +08:00

388 lines
10 KiB
Go

package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"os"
"time"
_ "github.com/go-sql-driver/mysql"
)
// Customer 客户信息结构
type Customer struct {
ID string `json:"id"`
CreatedAt string `json:"createdAt"`
CustomerName string `json:"customerName"`
IntendedProduct string `json:"intendedProduct"`
Version string `json:"version"`
Description string `json:"description"`
Solution string `json:"solution"`
Type string `json:"type"`
Module string `json:"module"`
StatusProgress string `json:"statusProgress"`
Reporter string `json:"reporter"`
}
// Followup 客户跟进结构
type Followup struct {
ID string `json:"id"`
CreatedAt string `json:"createdAt"`
CustomerName string `json:"customerName"`
DealStatus string `json:"dealStatus"`
CustomerLevel string `json:"customerLevel"`
Industry string `json:"industry"`
FollowUpTime string `json:"followUpTime"`
NotificationSent bool `json:"notificationSent"`
}
// TrialPeriod 试用期结构
type TrialPeriod struct {
ID string `json:"id"`
CustomerName string `json:"customerName"`
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
IsTrial bool `json:"isTrial"`
CreatedAt string `json:"createdAt"`
}
// 数据库配置
type DBConfig struct {
Host string
Port int
User string
Password string
Database string
}
func main() {
// 默认数据库配置
config := DBConfig{
Host: "localhost",
Port: 3306,
User: "root",
Password: "", // 请修改为实际密码
Database: "crm_db",
}
// 从环境变量读取配置
if host := os.Getenv("DB_HOST"); host != "" {
config.Host = host
}
if user := os.Getenv("DB_USER"); user != "" {
config.User = user
}
if pwd := os.Getenv("DB_PASSWORD"); pwd != "" {
config.Password = pwd
}
if db := os.Getenv("DB_NAME"); db != "" {
config.Database = db
}
// JSON 文件路径
dataDir := "./data"
if len(os.Args) > 1 {
dataDir = os.Args[1]
}
// 连接数据库
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
config.User, config.Password, config.Host, config.Port, config.Database)
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("连接数据库失败: %v", err)
}
defer db.Close()
// 测试连接
if err := db.Ping(); err != nil {
log.Fatalf("数据库连接测试失败: %v", err)
}
log.Println("✅ 数据库连接成功")
// 创建表
if err := createTables(db); err != nil {
log.Fatalf("创建表失败: %v", err)
}
log.Println("✅ 数据表创建成功")
// 迁移客户数据
customersFile := fmt.Sprintf("%s/customers.json", dataDir)
if err := migrateCustomers(db, customersFile); err != nil {
log.Printf("⚠️ 迁移客户数据失败: %v", err)
} else {
log.Println("✅ 客户数据迁移成功")
}
// 迁移跟进数据
followupsFile := fmt.Sprintf("%s/followups.json", dataDir)
if err := migrateFollowups(db, followupsFile); err != nil {
log.Printf("⚠️ 迁移跟进数据失败: %v", err)
} else {
log.Println("✅ 跟进数据迁移成功")
}
// 迁移试用期数据
trialsFile := fmt.Sprintf("%s/trial_periods.json", dataDir)
if err := migrateTrialPeriods(db, trialsFile); err != nil {
log.Printf("⚠️ 迁移试用期数据失败: %v", err)
} else {
log.Println("✅ 试用期数据迁移成功")
}
log.Println("🎉 数据迁移完成!")
}
func createTables(db *sql.DB) error {
queries := []string{
`CREATE TABLE IF NOT EXISTS customers (
id VARCHAR(64) NOT NULL PRIMARY KEY,
created_at DATETIME NOT NULL,
customer_name VARCHAR(255) NOT NULL,
intended_product VARCHAR(100),
version VARCHAR(50),
description TEXT,
solution TEXT,
type VARCHAR(50),
module VARCHAR(100),
status_progress VARCHAR(50) DEFAULT '进行中',
reporter VARCHAR(100),
INDEX idx_customer_name (customer_name),
INDEX idx_created_at (created_at),
INDEX idx_status_progress (status_progress)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
`CREATE TABLE IF NOT EXISTS followups (
id VARCHAR(64) NOT NULL PRIMARY KEY,
created_at DATETIME NOT NULL,
customer_name VARCHAR(255) NOT NULL,
deal_status VARCHAR(50) DEFAULT '未成交',
customer_level VARCHAR(10) DEFAULT 'C',
industry VARCHAR(100),
follow_up_time DATETIME,
notification_sent TINYINT(1) DEFAULT 0,
INDEX idx_customer_name (customer_name),
INDEX idx_created_at (created_at),
INDEX idx_follow_up_time (follow_up_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
`CREATE TABLE IF NOT EXISTS trial_periods (
id VARCHAR(64) NOT NULL PRIMARY KEY,
customer_name VARCHAR(255) NOT NULL,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
is_trial TINYINT(1) DEFAULT 1,
created_at DATETIME NOT NULL,
INDEX idx_customer_name (customer_name),
INDEX idx_end_time (end_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
}
for _, query := range queries {
if _, err := db.Exec(query); err != nil {
return fmt.Errorf("执行SQL失败: %v\nSQL: %s", err, query)
}
}
return nil
}
func parseTime(timeStr string) (time.Time, error) {
// 尝试多种时间格式
formats := []string{
time.RFC3339,
time.RFC3339Nano,
"2006-01-02T15:04:05.999999999Z07:00",
"2006-01-02T15:04:05Z",
"2006-01-02 15:04:05",
"2006-01-02",
}
for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("无法解析时间: %s", timeStr)
}
func migrateCustomers(db *sql.DB, filepath string) error {
data, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf("读取文件失败: %v", err)
}
var customers []Customer
if err := json.Unmarshal(data, &customers); err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
stmt, err := db.Prepare(`
INSERT INTO customers (id, created_at, customer_name, intended_product, version,
description, solution, type, module, status_progress, reporter)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
customer_name = VALUES(customer_name),
intended_product = VALUES(intended_product),
version = VALUES(version),
description = VALUES(description),
solution = VALUES(solution),
type = VALUES(type),
module = VALUES(module),
status_progress = VALUES(status_progress),
reporter = VALUES(reporter)
`)
if err != nil {
return fmt.Errorf("准备SQL语句失败: %v", err)
}
defer stmt.Close()
successCount := 0
for _, c := range customers {
createdAt, _ := parseTime(c.CreatedAt)
_, err := stmt.Exec(
c.ID, createdAt, c.CustomerName, c.IntendedProduct, c.Version,
c.Description, c.Solution, c.Type, c.Module, c.StatusProgress, c.Reporter,
)
if err != nil {
log.Printf("插入客户记录失败 [%s]: %v", c.CustomerName, err)
continue
}
successCount++
}
log.Printf(" 已迁移 %d/%d 条客户记录", successCount, len(customers))
return nil
}
func migrateFollowups(db *sql.DB, filepath string) error {
data, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf("读取文件失败: %v", err)
}
var followups []Followup
if err := json.Unmarshal(data, &followups); err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
stmt, err := db.Prepare(`
INSERT INTO followups (id, created_at, customer_name, deal_status, customer_level,
industry, follow_up_time, notification_sent)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
customer_name = VALUES(customer_name),
deal_status = VALUES(deal_status),
customer_level = VALUES(customer_level),
industry = VALUES(industry),
follow_up_time = VALUES(follow_up_time),
notification_sent = VALUES(notification_sent)
`)
if err != nil {
return fmt.Errorf("准备SQL语句失败: %v", err)
}
defer stmt.Close()
successCount := 0
for _, f := range followups {
createdAt, _ := parseTime(f.CreatedAt)
followUpTime, _ := parseTime(f.FollowUpTime)
notificationSent := 0
if f.NotificationSent {
notificationSent = 1
}
_, err := stmt.Exec(
f.ID, createdAt, f.CustomerName, f.DealStatus, f.CustomerLevel,
f.Industry, followUpTime, notificationSent,
)
if err != nil {
log.Printf("插入跟进记录失败 [%s]: %v", f.CustomerName, err)
continue
}
successCount++
}
log.Printf(" 已迁移 %d/%d 条跟进记录", successCount, len(followups))
return nil
}
func migrateTrialPeriods(db *sql.DB, filepath string) error {
data, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf("读取文件失败: %v", err)
}
var trials []TrialPeriod
if err := json.Unmarshal(data, &trials); err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
stmt, err := db.Prepare(`
INSERT INTO trial_periods (id, customer_name, start_time, end_time, is_trial, created_at)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
customer_name = VALUES(customer_name),
start_time = VALUES(start_time),
end_time = VALUES(end_time),
is_trial = VALUES(is_trial)
`)
if err != nil {
return fmt.Errorf("准备SQL语句失败: %v", err)
}
defer stmt.Close()
successCount := 0
for _, t := range trials {
startTime, _ := parseTime(t.StartTime)
endTime, _ := parseTime(t.EndTime)
createdAt, _ := parseTime(t.CreatedAt)
isTrial := 0
if t.IsTrial {
isTrial = 1
}
_, err := stmt.Exec(
t.ID, t.CustomerName, startTime, endTime, isTrial, createdAt,
)
if err != nil {
log.Printf("插入试用期记录失败 [%s]: %v", t.CustomerName, err)
continue
}
successCount++
}
log.Printf(" 已迁移 %d/%d 条试用期记录", successCount, len(trials))
return nil
}
func init() {
// 设置日志格式
log.SetFlags(log.Ltime)
// 打印使用说明
if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
fmt.Println(`CRM JSON 到 MySQL 数据迁移工具
用法:
go run migrate_to_mysql.go [data目录路径]
环境变量:
DB_HOST - 数据库主机 (默认: localhost)
DB_USER - 数据库用户 (默认: root)
DB_PASSWORD - 数据库密码 (默认: 空)
DB_NAME - 数据库名称 (默认: crm_db)
示例:
# 使用默认配置
go run migrate_to_mysql.go ./data
# 使用环境变量配置
DB_HOST=127.0.0.1 DB_USER=root DB_PASSWORD=123456 go run migrate_to_mysql.go ./data
`)
os.Exit(0)
}
}