feat: manage persons in app.db, auto-rebuild face_gallery.db

This commit is contained in:
tian 2026-05-08 10:45:35 +08:00
parent c0fa9d0700
commit d4fe5c8459
2 changed files with 53 additions and 44 deletions

View File

@ -9,7 +9,7 @@ import (
type PersonRecord struct {
ID int `json:"id"`
Name string `json:"name"`
PhotoURL string `json:"photo_url,omitempty"`
PhotoCount int `json:"photo_count"`
CreatedAt string `json:"created_at"`
}
@ -24,27 +24,25 @@ func NewFaceGalleryRepo(dbPath string) *FaceGalleryRepo {
func (r *FaceGalleryRepo) open() (*sql.DB, error) {
db, err := sql.Open("sqlite", r.dbPath)
if err != nil {
return nil, fmt.Errorf("open face gallery: %w", err)
return nil, fmt.Errorf("open app db: %w", err)
}
// Ensure schema
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS person (
CREATE TABLE IF NOT EXISTS face_persons (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT '',
extra TEXT NOT NULL DEFAULT ''
updated_at TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS embedding (
CREATE TABLE IF NOT EXISTS face_photos (
id INTEGER PRIMARY KEY,
person_id INTEGER NOT NULL,
embedding BLOB NOT NULL,
created_at TEXT NOT NULL DEFAULT '',
FOREIGN KEY (person_id) REFERENCES person(id)
photo_path TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT ''
);
`)
if err != nil {
db.Close()
return nil, fmt.Errorf("ensure schema: %w", err)
return nil, fmt.Errorf("ensure face tables: %w", err)
}
return db, nil
}
@ -56,8 +54,8 @@ func (r *FaceGalleryRepo) ListPersons() ([]PersonRecord, error) {
}
defer db.Close()
rows, err := db.Query(`SELECT p.id, p.name, p.created_at, COUNT(e.id) AS photo_count
FROM person p LEFT JOIN embedding e ON e.person_id = p.id
rows, err := db.Query(`SELECT p.id, p.name, p.created_at, COUNT(ph.id) AS photo_count
FROM face_persons p LEFT JOIN face_photos ph ON ph.person_id = p.id
GROUP BY p.id ORDER BY p.name`)
if err != nil {
return nil, err
@ -67,8 +65,7 @@ GROUP BY p.id ORDER BY p.name`)
var out []PersonRecord
for rows.Next() {
var p PersonRecord
var photoCount int
if err := rows.Scan(&p.ID, &p.Name, &p.CreatedAt, &photoCount); err != nil {
if err := rows.Scan(&p.ID, &p.Name, &p.CreatedAt, &p.PhotoCount); err != nil {
continue
}
out = append(out, p)
@ -86,30 +83,51 @@ func (r *FaceGalleryRepo) AddPerson(name string) error {
}
defer db.Close()
now := time.Now().Format(time.RFC3339)
_, err = db.Exec(`INSERT INTO person(name, created_at) VALUES(?, ?)`, name, now)
_, err = db.Exec(`INSERT INTO face_persons(name, created_at, updated_at) VALUES(?, ?, ?)`, name, now, now)
return err
}
func (r *FaceGalleryRepo) AddPhoto(personID int, photoPath string) error {
db, err := r.open()
if err != nil {
return err
}
defer db.Close()
now := time.Now().Format(time.RFC3339)
_, err = db.Exec(`INSERT INTO face_photos(person_id, photo_path, created_at) VALUES(?, ?, ?)`, personID, photoPath, now)
return err
}
func (r *FaceGalleryRepo) FindOrCreatePerson(name string) (int, error) {
db, err := r.open()
if err != nil {
return 0, err
}
defer db.Close()
var id int
err = db.QueryRow(`SELECT id FROM face_persons WHERE name = ?`, name).Scan(&id)
if err == nil {
return id, nil
}
now := time.Now().Format(time.RFC3339)
res, err := db.Exec(`INSERT INTO face_persons(name, created_at, updated_at) VALUES(?, ?, ?)`, name, now, now)
if err != nil {
return 0, err
}
lid, _ := res.LastInsertId()
return int(lid), nil
}
func (r *FaceGalleryRepo) DeletePerson(id int) error {
db, err := r.open()
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(`DELETE FROM embedding WHERE person_id = ?`, id)
_, err = db.Exec(`DELETE FROM face_photos WHERE person_id = ?`, id)
if err != nil {
return err
}
_, err = db.Exec(`DELETE FROM person WHERE id = ?`, id)
return err
}
func (r *FaceGalleryRepo) RenamePerson(id int, name string) error {
db, err := r.open()
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(`UPDATE person SET name = ? WHERE id = ?`, name, id)
_, err = db.Exec(`DELETE FROM face_persons WHERE id = ?`, id)
return err
}

View File

@ -667,7 +667,6 @@ func (u *UI) Routes() (chi.Router, error) {
r.Post("/face-gallery/build", u.actionFaceGalleryBuild)
r.Post("/face-gallery/add", u.actionFaceGalleryAdd)
r.Post("/face-gallery/delete", u.actionFaceGalleryDelete)
r.Post("/face-gallery/rename", u.actionFaceGalleryRename)
r.Get("/monitor", u.pageMonitor)
r.Get("/hls/*", u.proxyHLS)
r.Get("/api/monitor/channels", u.apiMonitorChannels)
@ -4009,10 +4008,11 @@ func (u *UI) actionFaceGalleryBuild(w http.ResponseWriter, r *http.Request) {
func (u *UI) pageFaceGallery(w http.ResponseWriter, r *http.Request) {
data := PageData{Title: "人脸库管理"}
data.Message = r.URL.Query().Get("msg")
dbPath := filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db")
if repo := storage.NewFaceGalleryRepo(dbPath); repo != nil {
if persons, err := repo.ListPersons(); err == nil {
data.FaceGalleryPersons = persons
if strings.TrimSpace(u.dbPath) != "" {
if repo := storage.NewFaceGalleryRepo(u.dbPath); repo != nil {
if persons, err := repo.ListPersons(); err == nil {
data.FaceGalleryPersons = persons
}
}
}
u.render(w, r, "face_gallery", data)
@ -4025,22 +4025,13 @@ func (u *UI) actionFaceGalleryDelete(w http.ResponseWriter, r *http.Request) {
return
}
u.rebuildFaceGallery()
repo := storage.NewFaceGalleryRepo(filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db"))
if strings.TrimSpace(u.dbPath) != "" { repo := storage.NewFaceGalleryRepo(u.dbPath)
repo.DeletePerson(id)
u.rebuildFaceGallery()
}
http.Redirect(w, r, "/ui/face-gallery", http.StatusFound)
}
func (u *UI) actionFaceGalleryRename(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.FormValue("id"))
name := strings.TrimSpace(r.FormValue("name"))
if id <= 0 || name == "" {
http.Error(w, "invalid", http.StatusBadRequest)
return
}
repo := storage.NewFaceGalleryRepo(filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db"))
repo.RenamePerson(id, name)
http.Redirect(w, r, "/ui/face-gallery", http.StatusFound)
}
func (u *UI) actionFaceGalleryImport(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(100 << 20); err != nil {