You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
7.4 KiB
Go
272 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
_ "github.com/denisenkom/go-mssqldb"
|
|
_ "github.com/go-sql-driver/mysql"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// DatabaseConfig 数据库配置结构
|
|
type DatabaseConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Type string `json:"type"` // mysql 或 sqlserver
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
Database string `json:"database"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
SSLMode string `json:"ssl_mode,omitempty"` // for mysql
|
|
}
|
|
|
|
// Camera 摄像头数据库模型
|
|
type Camera struct {
|
|
ID string `json:"id" db:"id"`
|
|
Name string `json:"name" db:"name"`
|
|
IP string `json:"ip" db:"ip"`
|
|
Username string `json:"username" db:"username"`
|
|
Password string `json:"password" db:"password"`
|
|
RTSPURL string `json:"rtsp_url" db:"rtsp_url"`
|
|
Enabled bool `json:"enabled" db:"enabled"`
|
|
OnDemand bool `json:"on_demand" db:"on_demand"`
|
|
Audio bool `json:"audio" db:"audio"`
|
|
Debug bool `json:"debug" db:"debug"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// DatabaseManager 数据库管理器
|
|
type DatabaseManager struct {
|
|
db *sql.DB
|
|
config DatabaseConfig
|
|
}
|
|
|
|
// NewDatabaseManager 创建数据库管理器
|
|
func NewDatabaseManager(config DatabaseConfig) (*DatabaseManager, error) {
|
|
if !config.Enabled {
|
|
return nil, nil
|
|
}
|
|
|
|
var dsn string
|
|
switch config.Type {
|
|
case "mysql":
|
|
dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
|
config.Username, config.Password, config.Host, config.Port, config.Database)
|
|
if config.SSLMode != "" {
|
|
dsn += "&tls=" + config.SSLMode
|
|
}
|
|
case "sqlserver":
|
|
dsn = fmt.Sprintf("server=%s;port=%d;database=%s;user id=%s;password=%s",
|
|
config.Host, config.Port, config.Database, config.Username, config.Password)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported database type: %s", config.Type)
|
|
}
|
|
|
|
db, err := sql.Open(config.Type, dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open database: %v", err)
|
|
}
|
|
|
|
// 测试连接
|
|
if err := db.Ping(); err != nil {
|
|
return nil, fmt.Errorf("failed to ping database: %v", err)
|
|
}
|
|
|
|
// 设置连接池参数
|
|
db.SetMaxOpenConns(25)
|
|
db.SetMaxIdleConns(5)
|
|
db.SetConnMaxLifetime(5 * time.Minute)
|
|
|
|
dm := &DatabaseManager{
|
|
db: db,
|
|
config: config,
|
|
}
|
|
|
|
// 初始化数据库表
|
|
if err := dm.initTables(); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize tables: %v", err)
|
|
}
|
|
|
|
return dm, nil
|
|
}
|
|
|
|
// initTables 初始化数据库表
|
|
func (dm *DatabaseManager) initTables() error {
|
|
var createTableSQL string
|
|
|
|
switch dm.config.Type {
|
|
case "mysql":
|
|
createTableSQL = `
|
|
CREATE TABLE IF NOT EXISTS cameras (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
ip VARCHAR(45) NOT NULL,
|
|
username VARCHAR(100),
|
|
password VARCHAR(100),
|
|
rtsp_url TEXT NOT NULL,
|
|
enabled BOOLEAN DEFAULT TRUE,
|
|
on_demand BOOLEAN DEFAULT FALSE,
|
|
audio BOOLEAN DEFAULT TRUE,
|
|
debug BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
INDEX idx_enabled (enabled),
|
|
INDEX idx_ip (ip)
|
|
)`
|
|
case "sqlserver":
|
|
createTableSQL = `
|
|
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='cameras' AND xtype='U')
|
|
CREATE TABLE cameras (
|
|
id NVARCHAR(36) PRIMARY KEY,
|
|
name NVARCHAR(255) NOT NULL,
|
|
ip NVARCHAR(45) NOT NULL,
|
|
username NVARCHAR(100),
|
|
password NVARCHAR(100),
|
|
rtsp_url NTEXT NOT NULL,
|
|
enabled BIT DEFAULT 1,
|
|
on_demand BIT DEFAULT 0,
|
|
audio BIT DEFAULT 1,
|
|
debug BIT DEFAULT 0,
|
|
created_at DATETIME2 DEFAULT GETDATE(),
|
|
updated_at DATETIME2 DEFAULT GETDATE()
|
|
)`
|
|
}
|
|
|
|
_, err := dm.db.Exec(createTableSQL)
|
|
return err
|
|
}
|
|
|
|
// GetAllCameras 获取所有摄像头
|
|
func (dm *DatabaseManager) GetAllCameras() ([]Camera, error) {
|
|
query := `SELECT id, name, ip, username, password, rtsp_url, enabled, on_demand, audio, debug, created_at, updated_at FROM cameras WHERE enabled = ?`
|
|
|
|
rows, err := dm.db.Query(query, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var cameras []Camera
|
|
for rows.Next() {
|
|
var camera Camera
|
|
err := rows.Scan(&camera.ID, &camera.Name, &camera.IP, &camera.Username,
|
|
&camera.Password, &camera.RTSPURL, &camera.Enabled, &camera.OnDemand,
|
|
&camera.Audio, &camera.Debug, &camera.CreatedAt, &camera.UpdatedAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cameras = append(cameras, camera)
|
|
}
|
|
|
|
return cameras, nil
|
|
}
|
|
|
|
// GetCameraByID 根据ID获取摄像头
|
|
func (dm *DatabaseManager) GetCameraByID(id string) (*Camera, error) {
|
|
query := `SELECT id, name, ip, username, password, rtsp_url, enabled, on_demand, audio, debug, created_at, updated_at FROM cameras WHERE id = ?`
|
|
|
|
var camera Camera
|
|
err := dm.db.QueryRow(query, id).Scan(&camera.ID, &camera.Name, &camera.IP,
|
|
&camera.Username, &camera.Password, &camera.RTSPURL, &camera.Enabled,
|
|
&camera.OnDemand, &camera.Audio, &camera.Debug, &camera.CreatedAt, &camera.UpdatedAt)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return &camera, nil
|
|
}
|
|
|
|
// CreateCamera 创建摄像头
|
|
func (dm *DatabaseManager) CreateCamera(camera *Camera) error {
|
|
if camera.ID == "" {
|
|
camera.ID = uuid.New().String()
|
|
}
|
|
camera.CreatedAt = time.Now()
|
|
camera.UpdatedAt = time.Now()
|
|
|
|
query := `INSERT INTO cameras (id, name, ip, username, password, rtsp_url, enabled, on_demand, audio, debug, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
|
|
_, err := dm.db.Exec(query, camera.ID, camera.Name, camera.IP, camera.Username,
|
|
camera.Password, camera.RTSPURL, camera.Enabled, camera.OnDemand,
|
|
camera.Audio, camera.Debug, camera.CreatedAt, camera.UpdatedAt)
|
|
|
|
return err
|
|
}
|
|
|
|
// UpdateCamera 更新摄像头
|
|
func (dm *DatabaseManager) UpdateCamera(camera *Camera) error {
|
|
camera.UpdatedAt = time.Now()
|
|
|
|
query := `UPDATE cameras SET name=?, ip=?, username=?, password=?, rtsp_url=?,
|
|
enabled=?, on_demand=?, audio=?, debug=?, updated_at=? WHERE id=?`
|
|
|
|
_, err := dm.db.Exec(query, camera.Name, camera.IP, camera.Username,
|
|
camera.Password, camera.RTSPURL, camera.Enabled, camera.OnDemand,
|
|
camera.Audio, camera.Debug, camera.UpdatedAt, camera.ID)
|
|
|
|
return err
|
|
}
|
|
|
|
// DeleteCamera 删除摄像头
|
|
func (dm *DatabaseManager) DeleteCamera(id string) error {
|
|
query := `DELETE FROM cameras WHERE id = ?`
|
|
_, err := dm.db.Exec(query, id)
|
|
return err
|
|
}
|
|
|
|
// Close 关闭数据库连接
|
|
func (dm *DatabaseManager) Close() error {
|
|
if dm.db != nil {
|
|
return dm.db.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CameraToStream 将摄像头转换为流配置
|
|
func CameraToStream(camera Camera) StreamST {
|
|
return StreamST{
|
|
Name: camera.Name,
|
|
Channels: map[string]ChannelST{
|
|
"0": {
|
|
Name: camera.Name,
|
|
URL: camera.RTSPURL,
|
|
OnDemand: camera.OnDemand,
|
|
Debug: camera.Debug,
|
|
Audio: camera.Audio,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// StreamToCamera 将流配置转换为摄像头
|
|
func StreamToCamera(id string, stream StreamST) Camera {
|
|
camera := Camera{
|
|
ID: id,
|
|
Name: stream.Name,
|
|
Enabled: true,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
// 获取第一个通道的配置
|
|
if len(stream.Channels) > 0 {
|
|
for _, channel := range stream.Channels {
|
|
camera.RTSPURL = channel.URL
|
|
camera.OnDemand = channel.OnDemand
|
|
camera.Debug = channel.Debug
|
|
camera.Audio = channel.Audio
|
|
break
|
|
}
|
|
}
|
|
|
|
return camera
|
|
}
|