Files
WorkBuddy/cnc-sales-backend/internal/api/backup.go
T
2026-08-16 16:29:06 +08:00

211 lines
6.7 KiB
Go

package api
import (
"net/http"
"cnc-sales-backend/internal/auth"
"cnc-sales-backend/internal/models"
)
// exportBackup 全量导出(与前端数据中心 JSON 备份格式兼容)
func (s *Server) exportBackup(w http.ResponseWriter, r *http.Request) {
uid := auth.UserID(r)
// 客户
customers := []models.Customer{}
rows, err := s.db.Query("SELECT "+customerCols+" FROM customers WHERE owner_id=? AND deleted_at IS NULL ORDER BY created_at", uid)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导出失败")
return
}
for rows.Next() {
c := models.Customer{}
if err := rows.Scan(&c.ID, &c.Company, &c.Industry, &c.Address, &c.Level, &c.Stage,
&c.MachineModel, &c.Budget, &c.WorkpieceNeeds, &c.PainPoints,
&c.NextFollowDate, &c.Source, &c.Remark, &c.CreatedAt, &c.UpdatedAt); err != nil {
continue
}
c.Contacts, _ = s.loadContacts(c.ID)
c.FollowUps, _ = s.loadFollowUps(c.ID)
customers = append(customers, c)
}
rows.Close()
// 任务
tasks := []models.Task{}
rows, err = s.db.Query("SELECT "+taskCols+" FROM tasks t LEFT JOIN customers c ON c.id=t.customer_id WHERE t.owner_id=? AND t.deleted_at IS NULL", uid)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导出失败")
return
}
for rows.Next() {
if t, ok := scanTask(rows); ok {
tasks = append(tasks, *t)
}
}
rows.Close()
// 资料
notes := []models.Note{}
rows, err = s.db.Query("SELECT id, cat, title, content, updated_at FROM notes WHERE owner_id=? AND deleted_at IS NULL", uid)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导出失败")
return
}
for rows.Next() {
var n models.Note
if err := rows.Scan(&n.ID, &n.Cat, &n.Title, &n.Content, &n.UpdatedAt); err == nil {
notes = append(notes, n)
}
}
rows.Close()
writeJSON(w, http.StatusOK, models.Backup{
SchemaVersion: 1,
ExportedAt: nowStr(),
Customers: customers,
Tasks: tasks,
Notes: notes,
})
}
// importBackup 全量导入(按 id upsert,数据归属当前用户)
func (s *Server) importBackup(w http.ResponseWriter, r *http.Request) {
uid := auth.UserID(r)
var b models.Backup
if err := decodeJSON(r, &b); err != nil {
writeErr(w, http.StatusBadRequest, "请求格式错误")
return
}
tx, err := s.db.Begin()
if err != nil {
writeErr(w, http.StatusInternalServerError, "导入失败")
return
}
defer tx.Rollback()
imported := map[string]int{"customers": 0, "tasks": 0, "notes": 0}
for _, c := range b.Customers {
c.Level = models.NormalizeLevel(c.Level)
c.Stage = models.NormalizeStage(c.Stage)
if c.ID == "" {
c.ID = genID()
}
if c.CreatedAt == "" {
c.CreatedAt = nowStr()
}
if c.UpdatedAt == "" {
c.UpdatedAt = nowStr()
}
res, err := tx.Exec(`UPDATE customers SET company=?, industry=?, address=?, level=?, stage=?,
machine_model=?, budget=?, workpiece_needs=?, pain_points=?, next_follow_date=?,
source=?, remark=?, updated_at=? WHERE id=? AND owner_id=?`,
c.Company, c.Industry, c.Address, c.Level, c.Stage, c.MachineModel, c.Budget,
c.WorkpieceNeeds, c.PainPoints, c.NextFollowDate, c.Source, c.Remark, c.UpdatedAt, c.ID, uid)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导入客户失败")
return
}
affected, _ := res.RowsAffected()
if affected == 0 {
_, err = tx.Exec(`INSERT INTO customers (id, owner_id, company, industry, address, level, stage,
machine_model, budget, workpiece_needs, pain_points, next_follow_date, source, remark,
created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
c.ID, uid, c.Company, c.Industry, c.Address, c.Level, c.Stage, c.MachineModel,
c.Budget, c.WorkpieceNeeds, c.PainPoints, c.NextFollowDate, c.Source, c.Remark,
c.CreatedAt, c.UpdatedAt)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导入客户失败")
return
}
}
if _, err := tx.Exec("DELETE FROM contacts WHERE customer_id=?", c.ID); err != nil {
writeErr(w, http.StatusInternalServerError, "导入失败")
return
}
for _, ct := range c.Contacts {
if _, err := tx.Exec("INSERT INTO contacts (customer_id, name, role, phone) VALUES (?,?,?,?)",
c.ID, ct.Name, ct.Role, ct.Phone); err != nil {
writeErr(w, http.StatusInternalServerError, "导入失败")
return
}
}
if _, err := tx.Exec("DELETE FROM follow_ups WHERE customer_id=?", c.ID); err != nil {
writeErr(w, http.StatusInternalServerError, "导入失败")
return
}
for _, f := range c.FollowUps {
if _, err := tx.Exec("INSERT INTO follow_ups (customer_id, time, content) VALUES (?,?,?)",
c.ID, f.Time, f.Content); err != nil {
writeErr(w, http.StatusInternalServerError, "导入失败")
return
}
}
imported["customers"]++
}
for _, t := range b.Tasks {
t.Priority = models.NormalizePriority(t.Priority)
t.Status = models.NormalizeStatus(t.Status)
t.Repeat = models.NormalizeRepeat(deref(t.Repeat))
if t.ID == "" {
t.ID = genID()
}
if t.CreatedAt == "" {
t.CreatedAt = nowStr()
}
res, err := tx.Exec(`UPDATE tasks SET title=?, customer_id=?, due_date=?, priority=?, status=?,
repeat=? WHERE id=? AND owner_id=?`,
t.Title, nullOrEmpty(t.CustomerID), t.DueDate, t.Priority, t.Status,
nullOrEmpty(t.Repeat), t.ID, uid)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导入任务失败")
return
}
if affected, _ := res.RowsAffected(); affected == 0 {
_, err = tx.Exec(`INSERT INTO tasks (id, owner_id, title, customer_id, due_date, priority,
status, repeat, created_at) VALUES (?,?,?,?,?,?,?,?,?)`,
t.ID, uid, t.Title, nullOrEmpty(t.CustomerID), t.DueDate, t.Priority, t.Status,
nullOrEmpty(t.Repeat), t.CreatedAt)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导入任务失败")
return
}
}
imported["tasks"]++
}
for _, n := range b.Notes {
n.Cat = models.NormalizeNoteCat(n.Cat)
if n.ID == "" {
n.ID = genID()
}
if n.UpdatedAt == "" {
n.UpdatedAt = nowStr()
}
res, err := tx.Exec("UPDATE notes SET cat=?, title=?, content=?, updated_at=? WHERE id=? AND owner_id=?",
n.Cat, n.Title, n.Content, n.UpdatedAt, n.ID, uid)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导入资料失败")
return
}
if affected, _ := res.RowsAffected(); affected == 0 {
_, err = tx.Exec("INSERT INTO notes (id, owner_id, cat, title, content, updated_at) VALUES (?,?,?,?,?,?)",
n.ID, uid, n.Cat, n.Title, n.Content, n.UpdatedAt)
if err != nil {
writeErr(w, http.StatusInternalServerError, "导入资料失败")
return
}
}
imported["notes"]++
}
if err := tx.Commit(); err != nil {
writeErr(w, http.StatusInternalServerError, "导入提交失败")
return
}
writeJSON(w, http.StatusOK, map[string]any{"imported": imported})
}