124 lines
4.5 KiB
Go
124 lines
4.5 KiB
Go
package api
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"log"
|
|
"mime"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"cnc-sales-backend/internal/auth"
|
|
)
|
|
|
|
// Server API 服务
|
|
type Server struct {
|
|
db *sql.DB
|
|
mux *http.ServeMux
|
|
}
|
|
|
|
// New 组装路由与中间件,返回 http.Handler
|
|
func New(db *sql.DB, staticDir string) http.Handler {
|
|
s := &Server{db: db, mux: http.NewServeMux()}
|
|
s.routes(staticDir)
|
|
return s.mux
|
|
}
|
|
|
|
func (s *Server) routes(staticDir string) {
|
|
// ---- 认证(公开) ----
|
|
s.mux.HandleFunc("POST /api/auth/register", s.register)
|
|
s.mux.HandleFunc("POST /api/auth/login", s.login)
|
|
|
|
// ---- 认证(JWT 保护) ----
|
|
s.mux.Handle("GET /api/auth/me", auth.Middleware(http.HandlerFunc(s.me)))
|
|
|
|
// ---- 客户档案 ----
|
|
s.mux.Handle("GET /api/customers", auth.Middleware(http.HandlerFunc(s.listCustomers)))
|
|
s.mux.Handle("POST /api/customers", auth.Middleware(http.HandlerFunc(s.createCustomer)))
|
|
s.mux.Handle("GET /api/customers/{id}", auth.Middleware(http.HandlerFunc(s.getCustomer)))
|
|
s.mux.Handle("PUT /api/customers/{id}", auth.Middleware(http.HandlerFunc(s.updateCustomer)))
|
|
s.mux.Handle("DELETE /api/customers/{id}", auth.Middleware(http.HandlerFunc(s.deleteCustomer)))
|
|
s.mux.Handle("POST /api/customers/{id}/contacts", auth.Middleware(http.HandlerFunc(s.addContact)))
|
|
s.mux.Handle("DELETE /api/customers/{id}/contacts/{cid}", auth.Middleware(http.HandlerFunc(s.deleteContact)))
|
|
s.mux.Handle("POST /api/customers/{id}/followups", auth.Middleware(http.HandlerFunc(s.addFollowUp)))
|
|
s.mux.Handle("DELETE /api/customers/{id}/followups/{fid}", auth.Middleware(http.HandlerFunc(s.deleteFollowUp)))
|
|
|
|
// ---- 任务 ----
|
|
s.mux.Handle("GET /api/tasks", auth.Middleware(http.HandlerFunc(s.listTasks)))
|
|
s.mux.Handle("POST /api/tasks", auth.Middleware(http.HandlerFunc(s.createTask)))
|
|
s.mux.Handle("PUT /api/tasks/{id}", auth.Middleware(http.HandlerFunc(s.updateTask)))
|
|
s.mux.Handle("DELETE /api/tasks/{id}", auth.Middleware(http.HandlerFunc(s.deleteTask)))
|
|
s.mux.Handle("POST /api/tasks/{id}/toggle", auth.Middleware(http.HandlerFunc(s.toggleTask)))
|
|
|
|
// ---- 资料库 ----
|
|
s.mux.Handle("GET /api/notes", auth.Middleware(http.HandlerFunc(s.listNotes)))
|
|
s.mux.Handle("POST /api/notes", auth.Middleware(http.HandlerFunc(s.createNote)))
|
|
s.mux.Handle("PUT /api/notes/{id}", auth.Middleware(http.HandlerFunc(s.updateNote)))
|
|
s.mux.Handle("DELETE /api/notes/{id}", auth.Middleware(http.HandlerFunc(s.deleteNote)))
|
|
|
|
// ---- 聚合视图 ----
|
|
s.mux.Handle("GET /api/pipeline/stages", auth.Middleware(http.HandlerFunc(s.pipelineStages)))
|
|
s.mux.Handle("GET /api/stats/overview", auth.Middleware(http.HandlerFunc(s.statsOverview)))
|
|
s.mux.Handle("GET /api/today", auth.Middleware(http.HandlerFunc(s.todayPanel)))
|
|
|
|
// ---- 备份 ----
|
|
s.mux.Handle("GET /api/backup", auth.Middleware(http.HandlerFunc(s.exportBackup)))
|
|
s.mux.Handle("POST /api/backup", auth.Middleware(http.HandlerFunc(s.importBackup)))
|
|
|
|
// ---- 静态资源(PWA 前端) ----
|
|
mime.AddExtensionType(".webmanifest", "application/manifest+json")
|
|
mime.AddExtensionType(".js", "text/javascript")
|
|
|
|
// 根路径重定向到工作台页面,其余路径交给静态文件服务
|
|
fileServer := http.FileServer(http.Dir(staticDir))
|
|
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/" {
|
|
http.Redirect(w, r, "/cnc-sales-workbench.html", http.StatusFound)
|
|
return
|
|
}
|
|
fileServer.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// ---- 通用工具 ----
|
|
|
|
func writeJSON(w http.ResponseWriter, code int, v any) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(code)
|
|
if err := json.NewEncoder(w).Encode(v); err != nil {
|
|
log.Printf("JSON 序列化失败: %v", err)
|
|
}
|
|
}
|
|
|
|
func writeErr(w http.ResponseWriter, code int, msg string) {
|
|
writeJSON(w, code, map[string]string{"error": msg})
|
|
}
|
|
|
|
func decodeJSON(r *http.Request, v any) error {
|
|
dec := json.NewDecoder(r.Body)
|
|
return dec.Decode(v)
|
|
}
|
|
|
|
func nowStr() string { return time.Now().Format("2006-01-02 15:04") }
|
|
func todayStr() string { return time.Now().Format("2006-01-02") }
|
|
|
|
// genID 生成与前端 uid() 兼容的 ID(前缀 + 时间戳36进制 + 随机数)
|
|
func genID() string {
|
|
var sb strings.Builder
|
|
sb.WriteString("id")
|
|
sb.WriteString(strconv.FormatInt(time.Now().UnixMilli(), 36))
|
|
b := make([]byte, 4)
|
|
if _, err := rand.Read(b); err == nil {
|
|
sb.WriteString(hex.EncodeToString(b))
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
func pathID(r *http.Request, key string) string {
|
|
return strings.TrimSpace(r.PathValue(key))
|
|
}
|