388 lines
10 KiB
Go
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)
|
|
}
|
|
}
|