refactor: video_sources使用整数ID主键,名字可自由修改

This commit is contained in:
tian 2026-07-26 12:34:35 +08:00
parent a4e0d1316f
commit c817b42ff7
4 changed files with 55 additions and 29 deletions

View File

@ -108,6 +108,7 @@ type AlarmServiceConfig struct {
}
type ConfigVideoSourceAsset struct {
ID int `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
SourceType string `json:"source_type"`
@ -824,7 +825,7 @@ func (s *ConfigPreviewService) GetVideoSource(name string) (*ConfigVideoSourceAs
return item, nil
}
func (s *ConfigPreviewService) SaveVideoSourceAsset(asset ConfigVideoSourceAsset) error {
func (s *ConfigPreviewService) SaveVideoSourceAsset(asset ConfigVideoSourceAsset, originalName string) error {
if s == nil || s.assets == nil {
return fmt.Errorf("基础配置仓库未初始化")
}
@ -836,8 +837,7 @@ func (s *ConfigPreviewService) SaveVideoSourceAsset(asset ConfigVideoSourceAsset
if sourceType == "" {
return fmt.Errorf("视频源类型不能为空")
}
urlValue := strings.TrimSpace(asset.Config.URL)
if urlValue == "" {
if strings.TrimSpace(asset.Config.URL) == "" {
return fmt.Errorf("视频源地址不能为空")
}
raw := map[string]any{
@ -860,7 +860,22 @@ func (s *ConfigPreviewService) SaveVideoSourceAsset(asset ConfigVideoSourceAsset
if err != nil {
return err
}
return s.assets.SaveVideoSource(name, sourceType, strings.TrimSpace(asset.Area), strings.TrimSpace(asset.Description), string(body))
if err := s.assets.SaveVideoSource(asset.ID, name, sourceType, strings.TrimSpace(asset.Area), strings.TrimSpace(asset.Description), string(body)); err != nil {
return err
}
// If renaming, update recognition units
originalName = strings.TrimSpace(originalName)
if originalName != "" && originalName != name {
units, _ := s.ListRecognitionUnits()
for _, u := range units {
if u.VideoSourceRef == originalName {
u.VideoSourceRef = name
ref := recognitionUnitRef(u.SceneTemplateName, u.Name)
s.SaveRecognitionUnit(u, ref)
}
}
}
return nil
}
func (s *ConfigPreviewService) RenameVideoSource(oldName, newName string) error {
@ -1708,6 +1723,7 @@ func videoSourceAssetFromRecord(record storage.VideoSourceRecord) (*ConfigVideoS
sourceMap = configMap
}
item := &ConfigVideoSourceAsset{
ID: record.ID,
Name: firstString(raw["name"], record.Name),
Path: repoAssetPath("video_sources", record.Name),
SourceType: firstString(record.SourceType, stringValue(raw["source_type"])),

View File

@ -29,6 +29,7 @@ type IntegrationServiceRecord struct {
}
type VideoSourceRecord struct {
ID int
Name string
SourceType string
Area string
@ -113,21 +114,23 @@ ON CONFLICT(name) DO UPDATE SET
return err
}
func (r *AssetsRepo) SaveVideoSource(name string, sourceType string, area string, description string, bodyJSON string) error {
func (r *AssetsRepo) SaveVideoSource(id int, name string, sourceType string, area string, description string, bodyJSON string) error {
if r == nil || r.db == nil {
return nil
}
now := time.Now().Format(time.RFC3339)
if id > 0 {
// Update existing by ID
_, err := r.db.Exec(`
UPDATE video_sources SET name=?, source_type=?, area=?, description=?, body_json=?, updated_at=?
WHERE id=?`, name, sourceType, area, description, bodyJSON, now, id)
return err
}
// Insert new
_, err := r.db.Exec(`
INSERT INTO video_sources(name, source_type, area, description, body_json, created_at, updated_at)
VALUES(?, ?, ?, ?, ?, COALESCE((SELECT created_at FROM video_sources WHERE name = ?), ?), ?)
ON CONFLICT(name) DO UPDATE SET
source_type=excluded.source_type,
area=excluded.area,
description=excluded.description,
body_json=excluded.body_json,
updated_at=excluded.updated_at
`, name, sourceType, area, description, bodyJSON, name, now, now)
VALUES(?, ?, ?, ?, ?, ?, ?)
`, name, sourceType, area, description, bodyJSON, now, now)
return err
}
@ -211,7 +214,7 @@ func (r *AssetsRepo) ListVideoSources() ([]VideoSourceRecord, error) {
return nil, nil
}
rows, err := r.db.Query(`
SELECT name, source_type, area, description, body_json, created_at, updated_at
SELECT id, name, source_type, area, description, body_json, created_at, updated_at
FROM video_sources
ORDER BY updated_at DESC, name ASC
`)
@ -223,7 +226,7 @@ ORDER BY updated_at DESC, name ASC
var out []VideoSourceRecord
for rows.Next() {
var item VideoSourceRecord
if err := rows.Scan(&item.Name, &item.SourceType, &item.Area, &item.Description, &item.BodyJSON, &item.CreatedAt, &item.UpdatedAt); err != nil {
if err := rows.Scan(&item.ID, &item.Name, &item.SourceType, &item.Area, &item.Description, &item.BodyJSON, &item.CreatedAt, &item.UpdatedAt); err != nil {
return nil, err
}
out = append(out, item)
@ -317,10 +320,10 @@ func (r *AssetsRepo) GetVideoSource(name string) (*VideoSourceRecord, error) {
}
var item VideoSourceRecord
err := r.db.QueryRow(`
SELECT name, source_type, area, description, body_json, created_at, updated_at
SELECT id, name, source_type, area, description, body_json, created_at, updated_at
FROM video_sources
WHERE name = ?
`, name).Scan(&item.Name, &item.SourceType, &item.Area, &item.Description, &item.BodyJSON, &item.CreatedAt, &item.UpdatedAt)
`, name).Scan(&item.ID, &item.Name, &item.SourceType, &item.Area, &item.Description, &item.BodyJSON, &item.CreatedAt, &item.UpdatedAt)
if err == sql.ErrNoRows {
return nil, nil
}

View File

@ -1488,7 +1488,7 @@ func (u *UI) actionWizardCreateSource(w http.ResponseWriter, r *http.Request) {
FPS: "25",
},
}
if err := u.preview.SaveVideoSourceAsset(asset); err != nil {
if err := u.preview.SaveVideoSourceAsset(asset, ""); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@ -3745,7 +3745,7 @@ func (u *UI) actionAssetVideoSourceSave(w http.ResponseWriter, r *http.Request)
MountAngle: strings.TrimSpace(r.FormValue("mount_angle")),
},
}
if err := u.preview.SaveVideoSourceAsset(asset); err != nil {
if err := u.preview.SaveVideoSourceAsset(asset, ""); err != nil {
data := u.assetPageData("video-sources")
data.Title = "资产管理"
data.Error = err.Error()
@ -3755,16 +3755,23 @@ func (u *UI) actionAssetVideoSourceSave(w http.ResponseWriter, r *http.Request)
u.render(w, r, "assets", data)
return
}
// Handle rename
originalName := strings.TrimSpace(r.FormValue("original_name"))
if originalName != "" && originalName != asset.Name {
if err := u.preview.RenameVideoSource(originalName, asset.Name); err != nil {
data := u.assetPageData("video-sources")
data.Error = err.Error()
u.render(w, r, "assets", data)
return
// Pass ID for update (0 = new)
if idStr := strings.TrimSpace(r.FormValue("id")); idStr != "" {
if id, err := strconv.Atoi(idStr); err == nil {
asset.ID = id
}
}
originalName := strings.TrimSpace(r.FormValue("original_name"))
if err := u.preview.SaveVideoSourceAsset(asset, originalName); err != nil {
data := u.assetPageData("video-sources")
data.Title = "资产管理"
data.Error = err.Error()
data.AssetVideoSource = &asset
data.AssetVideoSource.SourceTypeLabel = serviceVideoSourceTypeLabel(asset.SourceType)
data.AssetVideoSourceEditing = true
u.render(w, r, "assets", data)
return
}
http.Redirect(w, r, "/assets/video-sources?msg="+urlQueryEscape("视频源已保存")+"&name="+url.PathEscape(asset.Name), http.StatusFound)
}
@ -3887,7 +3894,7 @@ func (u *UI) actionVideoSourceCSVImport(w http.ResponseWriter, r *http.Request)
FPS: fps,
},
}
if err := u.preview.SaveVideoSourceAsset(asset); err != nil {
if err := u.preview.SaveVideoSourceAsset(asset, ""); err != nil {
skipped++
continue
}

View File

@ -232,7 +232,7 @@ toggleIntegrationFields('{{.AssetIntegration.Type}}');
</div>
{{if .AssetVideoSource}}
<form method="post" action="/assets/video-sources">
{{if .AssetVideoSource.Name}}<input type="hidden" name="original_name" value="{{.AssetVideoSource.Name}}">{{end}}
{{if .AssetVideoSource}}<input type="hidden" name="id" value="{{.AssetVideoSource.ID}}"><input type="hidden" name="original_name" value="{{.AssetVideoSource.Name}}">{{end}}
<div class="card editor-state {{if .AssetVideoSourceEditing}}editing{{else}}readonly{{end}}">
<div class="section-title">
<div>