diff --git a/API_USAGE.md b/API_USAGE.md new file mode 100644 index 0000000..e6464c8 --- /dev/null +++ b/API_USAGE.md @@ -0,0 +1,371 @@ +# 摄像头管理 API 使用说明 + +本文档说明如何使用摄像头管理相关的 REST API 接口。 + +## 基础配置 + +### 服务器地址 +``` +http://localhost:8083 # 默认地址,根据实际配置调整 +``` + +### 认证 +如果启用了 HTTP 认证,需要在请求头中添加 Basic Auth: +``` +Authorization: Basic +``` + +## API 接口列表 + +### 1. 获取所有摄像头列表 + +**请求方式:** `GET` +**请求路径:** `/cameras` +**描述:** 获取所有未删除的摄像头列表 + +**响应示例:** +```json +{ + "cameras": [ + { + "camera_id": 1, + "ip": "192.168.1.100", + "port": 554, + "username": "admin", + "password": "password", + "url": "rtsp://admin:password@192.168.1.100:554/stream1", + "camera_produce": "海康威视", + "camera_name": "前门摄像头", + "device_type": "网络摄像头", + "unit_code": "UNIT001", + "nvr_produce": "海康威视", + "nvr_path": "/stream1", + "play_back": "支持", + "del_flag": "0", + "create_by": "admin", + "create_time": "2024-01-01T10:00:00Z", + "update_by": "admin", + "update_time": "2024-01-01T10:00:00Z", + "user_id": 1, + "dept_id": 1, + "stream_status": "online" + } + ], + "total": 1 +} +``` + +### 2. 根据单位代码获取摄像头列表 + +**请求方式:** `GET` +**请求路径:** `/cameras/unit/{unitcode}` +**描述:** 根据单位代码获取对应的摄像头列表 + +**路径参数:** +- `unitcode`: 单位代码(必填) + +**响应示例:** +```json +{ + "cameras": [ + { + "camera_id": 1, + "ip": "192.168.1.100", + "port": 554, + "username": "admin", + "password": "password", + "url": "rtsp://admin:password@192.168.1.100:554/stream1", + "camera_produce": "海康威视", + "camera_name": "前门摄像头", + "device_type": "网络摄像头", + "unit_code": "UNIT001", + "nvr_produce": "海康威视", + "nvr_path": "/stream1", + "play_back": "支持", + "del_flag": "0", + "create_by": "admin", + "create_time": "2024-01-01T10:00:00Z", + "update_by": "admin", + "update_time": "2024-01-01T10:00:00Z", + "user_id": 1, + "dept_id": 1, + "stream_status": "online" + } + ], + "total": 1, + "unit_code": "UNIT001" +} +``` + +### 3. 其他摄像头管理接口 + +- `POST /camera/add` - 添加摄像头 +- `GET /camera/{uuid}` - 获取单个摄像头信息 +- `PUT /camera/{uuid}` - 更新摄像头信息 +- `DELETE /camera/{uuid}` - 删除摄像头 +- `POST /cameras/refresh` - 刷新摄像头列表 +- `GET /database/status` - 获取数据库状态 + +## 前端调用示例 + +### JavaScript (原生) + +```javascript +// 获取所有摄像头 +async function getAllCameras() { + try { + const response = await fetch('http://localhost:8083/cameras', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + // 如果需要认证,添加以下头部 + // 'Authorization': 'Basic ' + btoa('username:password') + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log('所有摄像头:', data.cameras); + return data; + } catch (error) { + console.error('获取摄像头列表失败:', error); + } +} + +// 根据单位代码获取摄像头 +async function getCamerasByUnitCode(unitCode) { + try { + const response = await fetch(`http://localhost:8083/cameras/unit/${unitCode}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + // 如果需要认证,添加以下头部 + // 'Authorization': 'Basic ' + btoa('username:password') + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log(`单位 ${unitCode} 的摄像头:`, data.cameras); + return data; + } catch (error) { + console.error('获取摄像头列表失败:', error); + } +} + +// 使用示例 +getAllCameras(); +getCamerasByUnitCode('UNIT001'); +``` + +### jQuery + +```javascript +// 获取所有摄像头 +function getAllCameras() { + $.ajax({ + url: 'http://localhost:8083/cameras', + type: 'GET', + dataType: 'json', + // 如果需要认证 + // beforeSend: function(xhr) { + // xhr.setRequestHeader('Authorization', 'Basic ' + btoa('username:password')); + // }, + success: function(data) { + console.log('所有摄像头:', data.cameras); + // 处理数据 + }, + error: function(xhr, status, error) { + console.error('获取摄像头列表失败:', error); + } + }); +} + +// 根据单位代码获取摄像头 +function getCamerasByUnitCode(unitCode) { + $.ajax({ + url: `http://localhost:8083/cameras/unit/${unitCode}`, + type: 'GET', + dataType: 'json', + // 如果需要认证 + // beforeSend: function(xhr) { + // xhr.setRequestHeader('Authorization', 'Basic ' + btoa('username:password')); + // }, + success: function(data) { + console.log(`单位 ${unitCode} 的摄像头:`, data.cameras); + // 处理数据 + }, + error: function(xhr, status, error) { + console.error('获取摄像头列表失败:', error); + } + }); +} +``` + +### Axios + +```javascript +import axios from 'axios'; + +// 配置基础URL和认证 +const api = axios.create({ + baseURL: 'http://localhost:8083', + headers: { + 'Content-Type': 'application/json', + // 如果需要认证 + // 'Authorization': 'Basic ' + btoa('username:password') + } +}); + +// 获取所有摄像头 +export const getAllCameras = async () => { + try { + const response = await api.get('/cameras'); + return response.data; + } catch (error) { + console.error('获取摄像头列表失败:', error); + throw error; + } +}; + +// 根据单位代码获取摄像头 +export const getCamerasByUnitCode = async (unitCode) => { + try { + const response = await api.get(`/cameras/unit/${unitCode}`); + return response.data; + } catch (error) { + console.error('获取摄像头列表失败:', error); + throw error; + } +}; +``` + +### Vue.js 组件示例 + +```vue + + + + + +``` + +## 错误处理 + +### 常见错误码 + +- `400 Bad Request` - 请求参数错误 +- `401 Unauthorized` - 认证失败 +- `500 Internal Server Error` - 服务器内部错误 +- `503 Service Unavailable` - 数据库未启用 + +### 错误响应格式 + +```json +{ + "error": "错误描述信息" +} +``` + +## 注意事项 + +1. **数据库配置**:确保在 `config.json` 中正确配置了数据库连接信息 +2. **认证**:如果启用了 HTTP 认证,所有请求都需要包含认证头部 +3. **CORS**:如果前端和后端不在同一域名下,需要处理跨域问题 +4. **流状态**:`stream_status` 字段表示摄像头的实时流状态(online/offline) +5. **单位代码**:`unit_code` 字段用于按组织单位过滤摄像头 +6. **删除标记**:只返回 `del_flag != '1'` 的摄像头(未删除的摄像头) \ No newline at end of file diff --git a/RTSPtoWeb.exe b/RTSPtoWeb.exe index e72d860..585a794 100644 Binary files a/RTSPtoWeb.exe and b/RTSPtoWeb.exe differ diff --git a/apiHTTPCamera.go b/apiHTTPCamera.go index b33faa8..f735f8c 100644 --- a/apiHTTPCamera.go +++ b/apiHTTPCamera.go @@ -1,11 +1,11 @@ package main import ( + "fmt" "net/http" "time" "github.com/gin-gonic/gin" - "github.com/google/uuid" "github.com/sirupsen/logrus" ) @@ -19,21 +19,32 @@ type CameraRequest struct { Enabled bool `json:"enabled"` } -// CameraResponse 摄像头响应结构 +// CameraResponse 摄像头响应结构 - 适配qsc_camera表 type CameraResponse struct { - ID string `json:"id"` - Name string `json:"name"` - IP string `json:"ip"` - Username string `json:"username"` - Password string `json:"password,omitempty"` // 密码在响应中可选 - RTSPURL string `json:"rtsp_url"` - Enabled bool `json:"enabled"` - Status string `json:"status"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + CameraID int `json:"camera_id"` + IP string `json:"ip"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password,omitempty"` + URL string `json:"url"` + CameraProduce string `json:"camera_produce"` + CameraName string `json:"camera_name"` + DeviceType string `json:"device_type"` + UnitCode string `json:"unit_code"` + NvrProduce string `json:"nvr_produce"` + NvrPath string `json:"nvr_path"` + PlayBack string `json:"play_back"` + DelFlag string `json:"del_flag"` + CreateBy string `json:"create_by"` + CreateTime time.Time `json:"create_time"` + UpdateBy string `json:"update_by"` + UpdateTime time.Time `json:"update_time"` + UserID int64 `json:"user_id"` + DeptID int64 `json:"dept_id"` + StreamStatus string `json:"stream_status"` } -// HTTPAPIServerCameras 获取所有摄像头列表 +// HTTPAPIServerCameras 获取所有摄像头 func HTTPAPIServerCameras(c *gin.Context) { if !Storage.Server.DatabaseEnabled { c.JSON(http.StatusServiceUnavailable, gin.H{ @@ -42,19 +53,86 @@ func HTTPAPIServerCameras(c *gin.Context) { return } - if Storage.dbManager == nil { + cameras, err := Storage.dbManager.GetAllCameras() + if err != nil { + log.WithFields(logrus.Fields{ + "module": "api", + "func": "HTTPAPIServerCameras", + "call": "GetAllCameras", + }).Errorln(err.Error()) c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Database manager not initialized", + "error": err.Error(), }) return } - cameras, err := Storage.dbManager.GetAllCameras() + // 转换为响应格式 + var responses []CameraResponse + for _, camera := range cameras { + // 检查流状态 + streamID := fmt.Sprintf("%d", camera.CameraID) + streamStatus := "offline" + if _, exists := Storage.Streams[streamID]; exists { + if Storage.StreamChannelExist(streamID, "0") { + streamStatus = "online" + } + } + + response := CameraResponse{ + CameraID: camera.CameraID, + IP: camera.IP, + Port: camera.Port, + Username: camera.Username, + Password: camera.Password, + URL: camera.URL, + CameraProduce: camera.CameraProduce, + CameraName: camera.CameraName, + DeviceType: camera.DeviceType, + UnitCode: camera.UnitCode, + NvrProduce: camera.NvrProduce, + NvrPath: camera.NvrPath, + PlayBack: camera.PlayBack, + DelFlag: camera.DelFlag, + CreateBy: camera.CreateBy, + CreateTime: camera.CreateTime, + UpdateBy: camera.UpdateBy, + UpdateTime: camera.UpdateTime, + UserID: camera.UserID, + DeptID: camera.DeptID, + StreamStatus: streamStatus, + } + responses = append(responses, response) + } + + c.JSON(http.StatusOK, gin.H{ + "cameras": responses, + "total": len(responses), + }) +} + +// HTTPAPIServerCamerasByUnitCode 根据unitcode获取摄像头列表 +func HTTPAPIServerCamerasByUnitCode(c *gin.Context) { + if !Storage.Server.DatabaseEnabled { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Database is not enabled", + }) + return + } + + unitCode := c.Param("unitcode") + if unitCode == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Unit code is required", + }) + return + } + + cameras, err := Storage.dbManager.GetCamerasByUnitCode(unitCode) if err != nil { log.WithFields(logrus.Fields{ "module": "api", - "func": "HTTPAPIServerCameras", - "call": "GetAllCameras", + "func": "HTTPAPIServerCamerasByUnitCode", + "call": "GetCamerasByUnitCode", }).Errorln(err.Error()) c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), @@ -63,37 +141,47 @@ func HTTPAPIServerCameras(c *gin.Context) { } // 转换为响应格式 - var response []CameraResponse + var responses []CameraResponse for _, camera := range cameras { - status := "offline" - if stream, exists := Storage.Streams[camera.ID]; exists { - if len(stream.Channels) > 0 { - for _, channel := range stream.Channels { - if channel.runLock { - status = "online" - break - } - } + // 检查流状态 + streamID := fmt.Sprintf("%d", camera.CameraID) + streamStatus := "offline" + if _, exists := Storage.Streams[streamID]; exists { + if Storage.StreamChannelExist(streamID, "0") { + streamStatus = "online" } } - response = append(response, CameraResponse{ - ID: camera.ID, - Name: camera.Name, - IP: camera.IP, - Username: camera.Username, - // Password: camera.Password, // 不返回密码 - RTSPURL: camera.RTSPURL, - Enabled: camera.Enabled, - Status: status, - CreatedAt: camera.CreatedAt, - UpdatedAt: camera.UpdatedAt, - }) + response := CameraResponse{ + CameraID: camera.CameraID, + IP: camera.IP, + Port: camera.Port, + Username: camera.Username, + Password: camera.Password, + URL: camera.URL, + CameraProduce: camera.CameraProduce, + CameraName: camera.CameraName, + DeviceType: camera.DeviceType, + UnitCode: camera.UnitCode, + NvrProduce: camera.NvrProduce, + NvrPath: camera.NvrPath, + PlayBack: camera.PlayBack, + DelFlag: camera.DelFlag, + CreateBy: camera.CreateBy, + CreateTime: camera.CreateTime, + UpdateBy: camera.UpdateBy, + UpdateTime: camera.UpdateTime, + UserID: camera.UserID, + DeptID: camera.DeptID, + StreamStatus: streamStatus, + } + responses = append(responses, response) } c.JSON(http.StatusOK, gin.H{ - "cameras": response, - "count": len(response), + "cameras": responses, + "total": len(responses), + "unit_code": unitCode, }) } @@ -114,20 +202,23 @@ func HTTPAPIServerCameraAdd(c *gin.Context) { return } - // 生成新的UUID - cameraID := uuid.New().String() - - // 创建摄像头记录 + // 创建摄像头记录 - 适配qsc_camera表 camera := Camera{ - ID: cameraID, - Name: req.Name, - IP: req.IP, - Username: req.Username, - Password: req.Password, - RTSPURL: req.RTSPURL, - Enabled: req.Enabled, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + IP: req.IP, + Port: 554, // 默认RTSP端口 + Username: req.Username, + Password: req.Password, + URL: req.RTSPURL, + CameraName: req.Name, + DeviceType: "网络摄像头", + UnitCode: "DEFAULT", // 默认单位代码 + DelFlag: "0", + CreateBy: "admin", + CreateTime: time.Now(), + UpdateBy: "admin", + UpdateTime: time.Now(), + UserID: 1, + DeptID: 1, } // 保存到数据库 @@ -144,9 +235,18 @@ func HTTPAPIServerCameraAdd(c *gin.Context) { return } + // 检查流是否已存在 + streamID := fmt.Sprintf("%d", camera.CameraID) + if _, exists := Storage.Streams[streamID]; exists { + c.JSON(http.StatusConflict, gin.H{ + "error": "Stream already exists", + }) + return + } + // 转换为流配置并添加到内存 stream := CameraToStream(camera) - err = Storage.StreamAdd(cameraID, stream) + err = Storage.StreamAdd(streamID, stream) if err != nil { log.WithFields(logrus.Fields{ "module": "api", @@ -161,7 +261,7 @@ func HTTPAPIServerCameraAdd(c *gin.Context) { c.JSON(http.StatusCreated, gin.H{ "message": "Camera added successfully", - "camera_id": cameraID, + "camera_id": camera.CameraID, }) } @@ -211,18 +311,15 @@ func HTTPAPIServerCameraUpdate(c *gin.Context) { return } - // 更新摄像头信息 - updatedCamera := Camera{ - ID: cameraID, - Name: req.Name, - IP: req.IP, - Username: req.Username, - Password: req.Password, - RTSPURL: req.RTSPURL, - Enabled: req.Enabled, - CreatedAt: existingCamera.CreatedAt, - UpdatedAt: time.Now(), - } + // 更新摄像头信息 - 适配qsc_camera表 + updatedCamera := *existingCamera // 复制现有摄像头信息 + updatedCamera.IP = req.IP + updatedCamera.Username = req.Username + updatedCamera.Password = req.Password + updatedCamera.URL = req.RTSPURL + updatedCamera.CameraName = req.Name + updatedCamera.UpdateBy = "admin" + updatedCamera.UpdateTime = time.Now() // 更新数据库 err = Storage.dbManager.UpdateCamera(&updatedCamera) @@ -354,7 +451,8 @@ func HTTPAPIServerCameraGet(c *gin.Context) { // 检查流状态 status := "offline" - if stream, exists := Storage.Streams[camera.ID]; exists { + streamID := fmt.Sprintf("%d", camera.CameraID) + if stream, exists := Storage.Streams[streamID]; exists { if len(stream.Channels) > 0 { for _, channel := range stream.Channels { if channel.runLock { @@ -366,16 +464,20 @@ func HTTPAPIServerCameraGet(c *gin.Context) { } response := CameraResponse{ - ID: camera.ID, - Name: camera.Name, - IP: camera.IP, - Username: camera.Username, + CameraID: camera.CameraID, + CameraName: camera.CameraName, + IP: camera.IP, + Port: camera.Port, + Username: camera.Username, // Password: camera.Password, // 不返回密码 - RTSPURL: camera.RTSPURL, - Enabled: camera.Enabled, - Status: status, - CreatedAt: camera.CreatedAt, - UpdatedAt: camera.UpdatedAt, + URL: camera.URL, + CameraProduce: camera.CameraProduce, + DeviceType: camera.DeviceType, + UnitCode: camera.UnitCode, + NvrProduce: camera.NvrProduce, + StreamStatus: status, + CreateTime: camera.CreateTime, + UpdateTime: camera.UpdateTime, } c.JSON(http.StatusOK, response) diff --git a/apiHTTPRouter.go b/apiHTTPRouter.go index 1d73019..04b6234 100644 --- a/apiHTTPRouter.go +++ b/apiHTTPRouter.go @@ -91,6 +91,7 @@ func HTTPAPIServer() { */ privat.GET("/cameras", HTTPAPIServerCameras) + privat.GET("/cameras/unit/:unitcode", HTTPAPIServerCamerasByUnitCode) privat.POST("/camera/add", HTTPAPIServerCameraAdd) privat.GET("/camera/:uuid", HTTPAPIServerCameraGet) privat.PUT("/camera/:uuid", HTTPAPIServerCameraUpdate) diff --git a/config.json b/config.json index 7e38d3a..6a51648 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,6 @@ { "server": { + "database_enabled": true, "debug": true, "http_debug": false, "http_demo": true, @@ -24,25 +25,13 @@ } }, "database": { - "enabled": false, + "enabled": true, "type": "mysql", "host": "localhost", "port": 3306, "username": "root", - "password": "password", - "database": "rtsp_cameras", - "table_name": "cameras" - }, - "streams": { - "27aec28e-6181-4753-9acd-0456a75f0289": { - "channels": { - "0": { - "url": "rtmp://171.25.232.10/12d525bc9f014e209c1280bc0d46a87e", - "debug": false, - "audio": true - } - }, - "name": "111111111" - } + "password": "123456", + "database": "ziyun", + "table_name": "qsc_camera" } } \ No newline at end of file diff --git a/database.go b/database.go index 3567e49..4d8c7f9 100644 --- a/database.go +++ b/database.go @@ -3,11 +3,11 @@ package main import ( "database/sql" "fmt" + "strconv" "time" _ "github.com/denisenkom/go-mssqldb" _ "github.com/go-sql-driver/mysql" - "github.com/google/uuid" ) // DatabaseConfig 数据库配置结构 @@ -22,20 +22,28 @@ type DatabaseConfig struct { SSLMode string `json:"ssl_mode,omitempty"` // for mysql } -// Camera 摄像头数据库模型 +// Camera 摄像头数据库模型 - 适配qsc_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"` + CameraID int `json:"camera_id" db:"camera_id"` + IP string `json:"ip" db:"ip"` + Port int `json:"port" db:"port"` + Username string `json:"username" db:"username"` + Password string `json:"password" db:"password"` + URL string `json:"url" db:"url"` + CameraProduce string `json:"camera_produce" db:"camera_produce"` + CameraName string `json:"camera_name" db:"camera_name"` + DeviceType string `json:"device_type" db:"device_type"` + UnitCode string `json:"unit_code" db:"unit_code"` + NvrProduce string `json:"nvr_produce" db:"nvr_produce"` + NvrPath string `json:"nvr_path" db:"nvr_path"` + PlayBack string `json:"play_back" db:"play_back"` + DelFlag string `json:"del_flag" db:"del_flag"` + CreateBy string `json:"create_by" db:"create_by"` + CreateTime time.Time `json:"create_time" db:"create_time"` + UpdateBy string `json:"update_by" db:"update_by"` + UpdateTime time.Time `json:"update_time" db:"update_time"` + UserID int64 `json:"user_id" db:"user_id"` + DeptID int64 `json:"dept_id" db:"dept_id"` } // DatabaseManager 数据库管理器 @@ -139,11 +147,14 @@ func (dm *DatabaseManager) initTables() error { return err } -// GetAllCameras 获取所有摄像头 +// GetAllCameras 获取所有摄像头 - 适配qsc_camera表 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 = ?` + query := `SELECT camera_id, ip, port, username, password, url, camera_produce, + camera_name, device_type, unit_code, nvr_produce, nvr_path, play_back, + del_flag, create_by, create_time, update_by, update_time, user_id, dept_id + FROM qsc_camera WHERE del_flag != '1'` - rows, err := dm.db.Query(query, true) + rows, err := dm.db.Query(query) if err != nil { return nil, err } @@ -152,9 +163,11 @@ func (dm *DatabaseManager) GetAllCameras() ([]Camera, error) { 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) + err := rows.Scan(&camera.CameraID, &camera.IP, &camera.Port, &camera.Username, + &camera.Password, &camera.URL, &camera.CameraProduce, &camera.CameraName, + &camera.DeviceType, &camera.UnitCode, &camera.NvrProduce, &camera.NvrPath, + &camera.PlayBack, &camera.DelFlag, &camera.CreateBy, &camera.CreateTime, + &camera.UpdateBy, &camera.UpdateTime, &camera.UserID, &camera.DeptID) if err != nil { return nil, err } @@ -164,14 +177,49 @@ func (dm *DatabaseManager) GetAllCameras() ([]Camera, error) { return cameras, nil } -// GetCameraByID 根据ID获取摄像头 +// GetCamerasByUnitCode 根据unitcode获取摄像头列表 - 适配qsc_camera表 +func (dm *DatabaseManager) GetCamerasByUnitCode(unitCode string) ([]Camera, error) { + query := `SELECT camera_id, ip, port, username, password, url, camera_produce, + camera_name, device_type, unit_code, nvr_produce, nvr_path, play_back, + del_flag, create_by, create_time, update_by, update_time, user_id, dept_id + FROM qsc_camera WHERE unit_code = ? AND del_flag != '1'` + + rows, err := dm.db.Query(query, unitCode) + if err != nil { + return nil, err + } + defer rows.Close() + + var cameras []Camera + for rows.Next() { + var camera Camera + err := rows.Scan(&camera.CameraID, &camera.IP, &camera.Port, &camera.Username, + &camera.Password, &camera.URL, &camera.CameraProduce, &camera.CameraName, + &camera.DeviceType, &camera.UnitCode, &camera.NvrProduce, &camera.NvrPath, + &camera.PlayBack, &camera.DelFlag, &camera.CreateBy, &camera.CreateTime, + &camera.UpdateBy, &camera.UpdateTime, &camera.UserID, &camera.DeptID) + if err != nil { + return nil, err + } + cameras = append(cameras, camera) + } + + return cameras, nil +} + +// GetCameraByID 根据ID获取摄像头 - 适配qsc_camera表 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 = ?` + query := `SELECT camera_id, ip, port, username, password, url, camera_produce, + camera_name, device_type, unit_code, nvr_produce, nvr_path, play_back, + del_flag, create_by, create_time, update_by, update_time, user_id, dept_id + FROM qsc_camera WHERE camera_id = ? AND del_flag = '0'` 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) + err := dm.db.QueryRow(query, id).Scan(&camera.CameraID, &camera.IP, &camera.Port, + &camera.Username, &camera.Password, &camera.URL, &camera.CameraProduce, + &camera.CameraName, &camera.DeviceType, &camera.UnitCode, &camera.NvrProduce, + &camera.NvrPath, &camera.PlayBack, &camera.DelFlag, &camera.CreateBy, + &camera.CreateTime, &camera.UpdateBy, &camera.UpdateTime, &camera.UserID, &camera.DeptID) if err != nil { if err == sql.ErrNoRows { @@ -183,41 +231,44 @@ func (dm *DatabaseManager) GetCameraByID(id string) (*Camera, error) { return &camera, nil } -// CreateCamera 创建摄像头 +// CreateCamera 创建摄像头 - 适配qsc_camera表 func (dm *DatabaseManager) CreateCamera(camera *Camera) error { - if camera.ID == "" { - camera.ID = uuid.New().String() - } - camera.CreatedAt = time.Now() - camera.UpdatedAt = time.Now() + camera.CreateTime = time.Now() + camera.UpdateTime = time.Now() - query := `INSERT INTO cameras (id, name, ip, username, password, rtsp_url, enabled, on_demand, audio, debug, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + query := `INSERT INTO qsc_camera (ip, port, username, password, url, camera_produce, + camera_name, device_type, unit_code, nvr_produce, nvr_path, play_back, + del_flag, create_by, create_time, update_by, update_time, user_id, dept_id) + 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) + _, err := dm.db.Exec(query, camera.IP, camera.Port, camera.Username, camera.Password, + camera.URL, camera.CameraProduce, camera.CameraName, camera.DeviceType, + camera.UnitCode, camera.NvrProduce, camera.NvrPath, camera.PlayBack, + camera.DelFlag, camera.CreateBy, camera.CreateTime, camera.UpdateBy, + camera.UpdateTime, camera.UserID, camera.DeptID) return err } -// UpdateCamera 更新摄像头 +// UpdateCamera 更新摄像头 - 适配qsc_camera表 func (dm *DatabaseManager) UpdateCamera(camera *Camera) error { - camera.UpdatedAt = time.Now() + camera.UpdateTime = time.Now() - query := `UPDATE cameras SET name=?, ip=?, username=?, password=?, rtsp_url=?, - enabled=?, on_demand=?, audio=?, debug=?, updated_at=? WHERE id=?` + query := `UPDATE qsc_camera SET ip=?, port=?, username=?, password=?, url=?, + camera_produce=?, camera_name=?, device_type=?, unit_code=?, nvr_produce=?, + nvr_path=?, play_back=?, update_by=?, update_time=? WHERE camera_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) + _, err := dm.db.Exec(query, camera.IP, camera.Port, camera.Username, camera.Password, + camera.URL, camera.CameraProduce, camera.CameraName, camera.DeviceType, + camera.UnitCode, camera.NvrProduce, camera.NvrPath, camera.PlayBack, + camera.UpdateBy, camera.UpdateTime, camera.CameraID) return err } -// DeleteCamera 删除摄像头 +// DeleteCamera 删除摄像头 - 适配qsc_camera表 func (dm *DatabaseManager) DeleteCamera(id string) error { - query := `DELETE FROM cameras WHERE id = ?` + query := `UPDATE qsc_camera SET del_flag = '1' WHERE camera_id = ?` _, err := dm.db.Exec(query, id) return err } @@ -230,39 +281,61 @@ func (dm *DatabaseManager) Close() error { return nil } -// CameraToStream 将摄像头转换为流配置 +// CameraToStream 将摄像头转换为流配置 - 适配qsc_camera表 func CameraToStream(camera Camera) StreamST { + // 构建RTSP URL + rtspURL := camera.URL + if rtspURL == "" { + // 如果URL为空,根据IP、端口、用户名密码构建RTSP URL + if camera.Username != "" && camera.Password != "" { + rtspURL = fmt.Sprintf("rtsp://%s:%s@%s:%d/stream1", + camera.Username, camera.Password, camera.IP, camera.Port) + } else { + rtspURL = fmt.Sprintf("rtsp://%s:%d/stream1", camera.IP, camera.Port) + } + } + return StreamST{ - Name: camera.Name, + Name: camera.CameraName, Channels: map[string]ChannelST{ "0": { - Name: camera.Name, - URL: camera.RTSPURL, - OnDemand: camera.OnDemand, - Debug: camera.Debug, - Audio: camera.Audio, + Name: camera.CameraName, + URL: rtspURL, + OnDemand: false, // 根据需要设置 + Debug: false, // 根据需要设置 + Audio: true, // 根据需要设置 }, }, } } -// StreamToCamera 将流配置转换为摄像头 +// StreamToCamera 将流配置转换为摄像头 - 适配qsc_camera表 func StreamToCamera(id string, stream StreamST) Camera { + // 创建基础摄像头对象 - 适配qsc_camera表 + cameraID, _ := strconv.Atoi(id) camera := Camera{ - ID: id, - Name: stream.Name, - Enabled: true, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + CameraID: cameraID, + CameraName: stream.Name, + IP: "", // 从URL中解析 + Port: 554, // 默认RTSP端口 + Username: "", // 从URL中解析 + Password: "", // 从URL中解析 + URL: "", // 将从第一个通道获取 + DeviceType: "网络摄像头", + UnitCode: "DEFAULT", + DelFlag: "0", + CreateBy: "system", + CreateTime: time.Now(), + UpdateBy: "system", + UpdateTime: time.Now(), + UserID: 1, + DeptID: 1, } // 获取第一个通道的配置 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 + camera.URL = channel.URL break } } diff --git a/dbconfig.go b/dbconfig.go new file mode 100644 index 0000000..cfec91c --- /dev/null +++ b/dbconfig.go @@ -0,0 +1,64 @@ +package main + +import ( + "database/sql" + "fmt" + "os" + + _ "github.com/go-sql-driver/mysql" + "github.com/sirupsen/logrus" +) + +var db *sql.DB + +type DBConfig struct { + Host string `json:"host"` + Port int `json:"port"` + User string `json:"user"` + Password string `json:"password"` + Database string `json:"database"` +} + +func initDB() error { + cfg := DBConfig{ + Host: os.Getenv("DB_HOST"), + Port: 3306, + User: os.Getenv("DB_USER"), + Password: os.Getenv("DB_PASSWORD"), + Database: os.Getenv("DB_NAME"), + } + + if cfg.Host == "" { + cfg.Host = "localhost" + } + if cfg.User == "" { + return fmt.Errorf("database user not configured") + } + if cfg.Password == "" { + return fmt.Errorf("database password not configured") + } + if cfg.Database == "" { + cfg.Database = "rtsp_config" + } + + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true", + cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Database) + + var err error + db, err = sql.Open("mysql", dsn) + if err != nil { + return fmt.Errorf("failed to connect to database: %v", err) + } + + err = db.Ping() + if err != nil { + return fmt.Errorf("failed to ping database: %v", err) + } + + log.WithFields(logrus.Fields{ + "module": "database", + "func": "initDB", + }).Info("Database connection established") + + return nil +} diff --git a/storageDatabase.go b/storageDatabase.go index 053b920..0552f12 100644 --- a/storageDatabase.go +++ b/storageDatabase.go @@ -66,12 +66,13 @@ func (obj *StorageST) LoadStreamsFromDatabase() error { // 将摄像头转换为流配置 for _, camera := range cameras { stream := CameraToStream(camera) - obj.Streams[camera.ID] = stream + streamID := fmt.Sprintf("%d", camera.CameraID) + obj.Streams[streamID] = stream log.WithFields(logrus.Fields{ "module": "database", "func": "LoadStreamsFromDatabase", - "camera": camera.Name, - "id": camera.ID, + "camera": camera.CameraName, + "id": camera.CameraID, }).Infoln("Loaded camera from database") } @@ -114,11 +115,11 @@ func (obj *StorageST) SyncStreamToDatabase(streamID string, stream StreamST) err "module": "database", "func": "SyncStreamToDatabase", "id": streamID, - "name": camera.Name, + "name": camera.CameraName, }).Infoln("Created camera in database") } else { // 更新现有摄像头 - camera.CreatedAt = existingCamera.CreatedAt // 保持创建时间 + camera.CreateTime = existingCamera.CreateTime // 保持创建时间 err = obj.dbManager.UpdateCamera(&camera) if err != nil { log.WithFields(logrus.Fields{ @@ -133,7 +134,7 @@ func (obj *StorageST) SyncStreamToDatabase(streamID string, stream StreamST) err "module": "database", "func": "SyncStreamToDatabase", "id": streamID, - "name": camera.Name, + "name": camera.CameraName, }).Infoln("Updated camera in database") }